交易工具

自动化测试本质上只是一些代码,如果遇到以下情况,它会抛出或导致错误 出现了问题。大多数库或测试框架都提供各种 使测试更易于编写的基元。

如上一部分所述,这些基元几乎总是包含 定义独立测试(称为测试用例)的方法,并提供 断言。断言是检查结果和抛出 出错,可以视为所有错误的基本原语, 测试基元。

本页面介绍了这些基元的一般方法。您选择的框架 可能包含与此类似的结果,但这并不是一个确切的参考。

例如:

import { fibonacci, catalan } from '../src/math.js';
import { assert, test, suite } from 'a-made-up-testing-library';

suite('math tests', () => {
  test('fibonacci function', () => {
    // check expected fibonacci numbers against our known actual values
    // with an explanation if the values don't match
    assert.equal(fibonacci(0), 0, 'Invalid 0th fibonacci result');
    assert.equal(fibonacci(13), 233, 'Invalid 13th fibonacci result');
  });
  test('relationship between sequences', () => {
    // catalan numbers are greater than fibonacci numbers (but not equal)
    assert.isAbove(catalan(4), fibonacci(4));
  });
  test('bugfix: check bug #4141', () => {
    assert.isFinite(fibonacci(0)); // fibonacci(0) was returning NaN
  })
});

此示例会创建一组名为“math”的测试(有时称为“套件”) 以及定义三个独立的测试用例,每个用例会运行一些断言。 这些测试用例通常可以单独处理或运行,例如,由 过滤标志。

将断言帮助程序作为基元

大多数测试框架(包括 Vitest)都包含一系列断言 assert 对象的辅助程序,可让您快速检查返回值或 不超出预期的其他状态。这种预期通常“众所周知” 值。在前面的示例中,我们知道第 13 个斐波那契数应该为 233,因此我们可以使用 assert.equal 直接确认这一点。

您可能还希望某个值采用某种形式, 或具有某种其他属性。本课程不会 涵盖所有可能的断言帮助程序,但测试框架 始终至少提供以下基本检查:

  • “truthy” 通常称为“ok”检查某个条件是否成立 与编写用于检查操作是否成功或if 正确。这往往以 assert(...)assert.ok(...) 的形式提供,并且 它接受一个值加上可选的注释。

  • 例如,在数学测试示例中,检查相等性,其中您希望 返回值或状态。这些是 基元相等(例如对于数字和字符串)或引用相等 (它们属于同一个对象)。从本质上讲,这些只是勾选 并使用 ===== 比较。

    • JavaScript 区分松散相等 (==) 和严格相等 (===)。 大多数测试库都会为您提供 assert.equalassert.strictEqual
  • 深度相等性检查,扩展相等性检查,包括对 对象、数组和其他更复杂的数据类型的内容,以及 通过内部逻辑遍历对象进行比较。这些很重要 因为 JavaScript 没有内置方式 两个对象或数组。例如,[1,2,3] == [1,2,3] 始终为 false。测试 框架通常包含 deepEqualdeepStrictEqual 帮助程序。

用于比较两个值的断言帮助程序(而不仅仅是“truthy”检查) 通常采用 2-3 个参数:

  • 实际值,由被测代码生成或用于描述 要验证的状态。
  • 预期值,通常为硬编码值(如字面量数字或 字符串)。
  • 可选的注释,用于说明预期结果或可能失败的情况; (如果此行失败,将会包含该属性)。

结合断言来构建各种 因为很少有人能正确确认 独立系统。例如:

  test('JWT parse', () => {
    const json = decodeJwt('eyJieSI6InNhbXRob3Ii');

    assert.ok(json.payload.admin, 'user should be admin');
    assert.deepEqual(json.payload.groups, ['role:Admin', 'role:Submitter']);
    assert.equal(json.header.alg, 'RS265')
    assert.isAbove(json.payload.exp, +new Date(), 'expiry must be in future')
  });

Vitest 使用 Chai 断言库 以提供其断言帮助程序 以了解哪些断言和帮助程序适合您的代码。

Fluent 和 BDD 断言

一些开发者更喜欢可以称为行为驱动的断言样式 开发 (BDD),或 流利式 断言。这些类型也称为“预期”因为这里的入口点 检查预期是一个名为 expect() 的方法。

预期辅助程序的行为方式与以简单方法编写的断言相同 调用 assert.okassert.strictDeepEquals 等,但有些开发者发现它们读起来更轻松。 BDD 断言可能如下所示:

// A failure here would generate "Expect result to be an array that does include 42"
const result = await possibleMeaningsOfLife();
expect(result).to.be.an('array').that.does.include(42);

// or a simpler form
expect(result).toBe('array').toContainEqual(42);

// the same in assert might be
assert.typeOf(result, 'array', 'Expected the result to be an array');
assert.include(result, 42, 'Expected the result to include 42');

这些断言样式之所以能发挥作用,是因为采用了名为“方法链接”的技术, 其中,expect 返回的对象可以不断与 进一步调用的方法。调用的某些部分,包括 to.bethat.does 没有函数,添加函数只是为了进行调用 并且如果测试 已失败。(请注意,expect 通常不支持可选的注释,因为 链接应清楚描述失败。)

许多测试框架同时支持 Fluent/BDD 和常规断言。最狂热的 例如,会将 Chai 采用这种方法,并且对 BDD 也有更简洁一些的方法。Jest, 另一方面,仅包含一个预测值 method

跨文件对测试进行分组

在编写测试时,我们已经倾向于提供隐式分组,而不是 将所有测试都放在一个文件中,因此通常需要编写跨多个 文件。事实上,测试运行程序通常只知道文件是用于测试的,因为 预定义过滤器或正则表达式,例如 vitest 包含 以“.test.jsx”等扩展名结尾的文件或“.spec.ts” (“.test”和“.spec”以及多个有效扩展名)。

组件测试往往位于与被测组件的对等文件中 如以下目录结构所示:

<ph type="x-smartling-placeholder">
</ph> 以列表形式指定
  包括 UserList.tsx 和 UserList.test.tsx。
组件文件和相关的测试文件。

同样,单元测试往往放在被测代码旁边。 每个端到端测试都可能位于各自的文件中,集成测试甚至可以 都放在各自专属的文件夹中在以下情况下,这些结构会很有帮助: 复杂的测试用例增长到需要自己的非测试支持文件,例如 测试所需的支持库。

在文件中对测试进行分组

如前面的示例中所述,通常的做法是将测试置于对 suite(),用于将您使用 test() 设置的测试进行分组。套房通常不是 但它们对相关测试进行分组,有助于提供结构 指定目标或目标对于 test(),传递的方法描述了 测试本身的操作

与断言一样,Fluent/BDD 中有相当标准的等效项, 分组测试。以下代码比较了一些典型示例:

// traditional/TDD
suite('math tests', () => {
  test('handle zero values', () => {
    assert.equal(fibonacci(0), 0);
  });
});

// Fluent/BDD
describe('math tests', () => {
  it('should handle zero values', () => {
    expect(fibonacci(0)).toBe(0);
  });
})

在大多数框架中,suitedescribe 的行为类似,testit,而使用 expectassert 之间的差异 来编写断言

其他工具在排列套件和测试方面采用的方法略有不同。对于 例如,Node.js 的内置测试运行程序支持通过嵌套调用 test() 隐式创建测试层次结构。但是,Vitest 仅允许这种类型的 使用 suite() 进行嵌套,并且不会运行在另一个 test() 内定义的 test()

与断言一样 方法并不那么重要。本课程将介绍 但您需要弄清楚它们如何应用于 工具选择。

生命周期方法

对测试进行分组的一个原因,甚至隐式在 是针对每次测试或 一组测试。大多数框架提供四种方法:

对于每个 `test()` 或 `it()`, 一次针对套件
测试运行之前 “beforeOnce()” `beforeAll()`
测试运行后 “afterOnce()” “afterAll()”

例如,您可能希望在用户使用每个 API 密钥之前 并在之后将其清除:

suite('user test', () => {
  beforeEach(() => {
    insertFakeUser('bob@example.com', 'hunter2');
  });
  afterEach(() => {
    clearAllUsers();
  });

  test('bob can login', async () => {  });
  test('alice can message bob', async () => {  });
});

这有助于简化测试。您可以分享常规设置 拆解代码,而不是在每次测试中都复制代码。此外,如果 设置和拆解代码本身会抛出错误,这可能表明 不涉及测试本身失败的问题

总体建议

在考虑这些基元时,请注意以下几点。

基元是一种指南

请注意,这里以及接下来几页的工具和基元 与 Vitest、Jest、Mocha、Web Test Runner 或任何其他 特定框架虽然我们以 Vitest 为通用指南,但请务必映射 您选择的框架

根据需要混合搭配断言

从本质上讲,测试是可以抛出错误的代码。每个跑步者都将提供一个 原语,可能为 test(),用于描述不同的测试用例。

但是,如果该运行程序还提供 assert()expect() 和断言帮助程序, 请注意,这一部分更侧重于便利性, 不需要。您可以运行任何可能引发错误的代码,包括其他 断言库或老式的 if 语句。

IDE 设置可以助您一臂之力

确保您的 IDE(例如 VSCode)可以访问自动补全和 有关所选测试工具的文档可帮助您提高工作效率。对于 例如,在 Chai 断言中,assert 上有 100 多种方法 库,并提供关于 以内嵌方式显示会很方便。

这对于填充 全局命名空间及其测试方法。这是一个很细微的差别 通常可以在不导入测试库的情况下直接使用 自动添加到全局命名空间中:

// some.test.js
test('using test as a global', () => {  });

我们建议导入帮助程序,即使它们是自动支持的, 因为这样可以让您的 IDE 清晰地查找这些方法。(您可能有 在构建 React 时遇到了这个问题 React 是全局的,但有些不是,并且要求使用 React.)

// some.test.js
import { test } from 'vitest';
test('using test as an import', () => {  });