增加TS测试

增加js测试和增加ts测试没有本质区别,在测试框架用法上是一样的。唯一增加的就是ts引入类型,所以需要额外做类型测试。

使用Node.js内置测试框架

Node.js诞生自2009年,在v18之前的13年时间里都没有内置任何测试框架。一直都是使用npm生态。像狼书系列卷三中提到的几个测试框架,都已经有5年以上的历史了。

测试框架当前主要版本年限
mochav1011
tapv1611
tapev510
avav59
jestv277

Node.js遵循与JavaScript本身相同的"最小核心"原则。因此,像代码检查工具、代码格式化工具和测试运行器这样的工具最好作为第三方工具提供。虽然这是一个很好的想法很长一段时间,但现在没有标准测试工具的任何语言都显得有些奇怪。Deno、Rust和Go - 它们都有自己内置的测试运行器。

目前各个测试框架和Node.js内置测试框架差异对比如下,参考自https://glebbahmutov.com/blog/trying-node-test-runner/

特性MochaAvaJestNode.js TR推荐
内置在Node中🚫🚫🚫🎉
Watch 模式🎉
Reporterslotsvia TAPlotsvia TAP
Assertionsvia Chai ✅weak😑
Snapshots🚫🚫
Hooks
grep support
spy and stubvia Sinon ✅via Sinon ✅✅✅
parallel execution
code coveragevia nycvia c8👍
TS supportvia ts-nodevia ts-nodevia ts-jestvia ts-node🐢

在Node.js v18开始内置了测试框架,在Node.js v20版本中,已经被标记为Stable能力,大家可以放心使用。

import {test, describe} from 'node:test';
import assert from 'node:assert';

import { HelloWorld } from "./helloworld.mjs";

describe("test suite", function() {
 test("test if works correctly", function() {
   // run some test
   assert.strictEqual(1, 1);
 })
})

此时,执行npm test或node —test —watch就可以了。

$ npm test

> node20@1.0.0 test
> node --watch --test .

▶ test suite
  ✔ test if works correctly (0.104958ms)
▶ test suite (0.894917ms)

使用Tsd类型测试

写了类型,最好写好类型测试,在ts生态里,可以使用tsd来做类型测试。

创建index.test-d.ts文件

import { expectType } from "tsd";

import { IPerson } from ".";
import { HelloWorld } from "./src/helloworld";

const cli: IPerson = new HelloWorld();

expectType<Promise<void>>(cli.sayHi("use TypeScript to write Node.js"));

执行

$ npx tsd

如果没有任何显示,说明测试通过。如果有错误就会有下面这样的显示。

Untitled

再思考一下

  • 有代码,就要测试
  • 有类型,就要有类型测试

如果想规范的写,就会比较麻烦一点。如果什么都不写,也没啥问题,只是不标准而已。像Rails这种追求极致效率,又想标准的项目,不用ts是正常的。

测试规范

在测试领域中,规范(specification, spec) 包含了用例的描述以及针对它们的测试,如下所示:

import { it, describe } from "node:test";
import assert from "node:assert";

import { HelloWorld } from "../src/helloworld";
import { IPerson } from "..";

describe("test suite", function () {
  it("test if works correctly", async function (t) {
    const log = t.mock.method(global.console, "log");

    assert.strictEqual(log.mock.callCount(), 0);
    // call hello world say method
    const cli: IPerson = new HelloWorld();
    await cli.sayHi("liangqi");

    assert.strictEqual(log.mock.callCount(), 1);
  });

  it("test if works incorrectly", async function () {
    const cli: IPerson = new HelloWorld();
    assert.rejects(async () => await cli.sayHi(), new Error("fail"));
  });
});

正如你所看到的,一个规范包含三个主要的模块:

1、describe("title", function() { ... }) 表示我们正在描述的功能是什么,相当于一个group。用于组织“工人(workers)” —— it 代码块。

2、it("use case description", function() { ... }) it 里面的描述部分,我们以一种易于理解 的方式描述特定的用例,第二个参数是用于对其进行测试的函数。从Node.js v18开始,it()被引入并且被正式定义为test()别名,因此两者提供的功能完全相同。我们在describe中使用it有助于增加代码的可读性,表示这是"一系列测试"中的一项,相当于item,如何测试?测试逻辑?都是在it的回调函数中实现的。

3、assert.equal(value1, value2) it 块中的代码,如果实现是正确的,它应该在执行的时候不产生任何错误。 assert.* 函数用于检查 测试 函数是否按照预期工作。在这里我们使用了其中之一 —— assert.equal,它会对参数进行比较,如果它们不相等则会抛出一个错误。这里它检查了 pow(2, 3) 的值是否等于 8。还有其他类型的比较和检查,我们将在后面介绍到。

规范可以被执行,它将运行在 it 块中指定的测试。我们稍后会看到。

开发流程通常看起来像这样:

  1. 编写初始规范,测试最基本的功能。
  2. 创建一个最初始的实现。
  3. 检查它是否工作,我们运行测试框架(很快会有更多细节)来运行测试。当功能未完成时,将显示错误。我们持续修正直到一切都能工作。
  4. 现在我们有一个带有测试的能工作的初步实现。
  5. 我们增加更多的用例到规范中,或许目前的程序实现还不支持。无法通过测试。
  6. 回到第 3 步,更新程序直到测试不会抛出错误。
  7. 重复第 3 步到第 6 步,直到功能完善。

如此来看,开发就是不断地 迭代。我们写规范,实现它,确保测试通过,然后写更多的测试,确保它们工作等等。最后,我们有了一个能工作的实现和针对它的测试。

让我们在我们的开发案例中看看这个开发流程吧。

在我们的案例中,第一步已经完成了:我们有一个针对 helloworld 的初始规范。因此让我们来实现它吧。但在此之前,让我们用一些 JavaScript 库来运行测试,就是看看测试是通过了还是失败了。