让 TypeScript 开源项目支持单元测试覆盖率
很多小伙伴在访问 Github 的开源项目时,总能看到一些与项目有关的徽章,比如代码测试覆盖率、代码质量、下载次数等等。前段时间有朋友问我该如何设置,我想干脆就写一篇小短文来分享一下吧,万一恰好还有朋友需要呢。
徽章其实并不是什么稀罕的东西。在打开 Github 项目时,默认会将 README.md 文件的内容渲染成网页,而徽章就是通过 Markdown 语法来实现的。说白了,徽章仅仅只是个图片而已。你可以使用 Markdown 语法来插入任何你想要的图片,并不仅仅局限于徽章。但是如果是想要插入与自己项目有关的、动态的数据 —— 如代码测试覆盖率、代码质量、下载次数等等,那又该怎么办呢?总不可能每次上传代码时,再去修改图片或图片地址吧。
本文将以单元测试覆盖率作为示例,来和大家一起探讨如何为 TypeScript 项目实施单元测试,并添加单元测试覆盖率徽章。
提到单元测试覆盖率,首先要先从单元测试说起。
Mocha
mocha 是一个非常优秀的单元测试框架,它能较好的支持 TypeScript,并且非常容易使用。但与一般的 Nodejs 项目一样,它默认只能运行 JavaScript 代码,所以我们还需要采取一些其他的手段来让其支持 TypeScript。
我们以我项目中正在使用的 10.1.0 版本为例,你只需要安装 ts-node,并在 .mocharc.json 文件中配置 require 节点的值为 ts-node/register,便可以使用 TypeScript 来编写单元测试了。
单元测试的代码通常都放在 test 目录下,所有单元测试的代码均以 .spec.ts 为后缀。mocha 会自动查找 test 目录下的所有 .spec.ts 文件,并执行其中的测试代码。
测试代码由 describe 和 it 两个函数组成,分别表示测试用例的组和测试用例。一个普通的测试代码就像下面这样:
/* tslint:disable */
import { expect } from "chai";
import DEFAULT, { Enumerable, List, Dictionary, ReadOnlyCollection, ReadOnlyList } from "../src/index";
describe("./index.ts", function () {
it("存在 默认导出", function () {
expect(DEFAULT).not.null;
expect(DEFAULT).not.undefined;
expect(typeof DEFAULT).to.equal("function");
});
// ............
// 其他测试用例,此处略。
// ............
});
它在运行后的结果类似下图:

在上述代码中,可以看到引入的一个库叫做 chai,它是一个非常优秀的断言库,可以让我们在测试代码中方便的编写断言。其他常见的断言库还有 should、assert 等等,它们各有特点,但都是为了编写断言,这里就不做特别介绍了。
对很多人来说,正确配置 mocha 以让它支持 TypeScript 是一个很麻烦的事情。所以我将在下面贴出我的 .mocharc.json 及 tsconfig.json 文件供大家参考:
.mocharc.json
{
"bail": true,
"timeout": 0,
"recursive": true,
"require": "ts-node/register",
"extension": "ts"
}
tsconfig.json
{
"include": ["src/**/*"],
"compilerOptions": {
"esModuleInterop": true,
"module": "commonjs",
"target": "es5",
"sourceMap": true,
"declaration": true,
"downlevelIteration": true,
"lib": ["es5", "es2015", "dom"],
"outDir": "dist"
}
}
当你在项目代码中写入了大量的单元测试代码后,mocha 会依次执行。但只有这些并不够,我们只能保证我们写的用例能不能正常执行,但还不能表明已经走完了项目中的全部分支。
只有测试用例覆盖了全部的项目代码、经过了全部分支条件,才能保证代码的质量。单纯使用 mocha 是没有办法做到这一点的,所以我们需要引入 nyc。
NYC
nyc 的前身是 istanbul,它是非常知名的代码覆盖率工具。它不仅可以支持 JavaScript,还能通过 mapping 的方式统计出 TypeScript 项目的单元测试覆盖率。
它最让人喜欢的特性之一是 —— 它不仅仅能够统计出单元测试覆盖率的结果,更能以生成格式精美的 HTML 报告,方便我们查漏补缺,就像下图一样。

追求更高的代码覆盖率(甚至是 100%)是每个项目的理想目标,但完善所有代码路径的覆盖是一件颇具挑战性的工作,尤其是在大型项目中。所以,我们通常只需要保证项目中的代码覆盖率不低于一定值即可。我一般会将代码覆盖率统一设置为 95%,对于特别难走到,或者是确定不需要测试的代码,可以追加 /* istanbul ignore next */ 或 /* istanbul ignore else */ 这样的注释来忽略部分代码。
* 更多的 istanbul 注释请参考官方文档。
正如同执行 mocha 的命令仅需要 mocha 非常的简单,执行 nyc 也并不复杂。在配置好 .nycrc 规则文件后,只需要执行 nyc mocha 即可。
我也将我的 .nycrc 文件贴出来供大家参考:
.nycrc
{
"check-coverage": true,
"cache": false,
"lines": 95,
"statements": 95,
"functions": 95,
"branches": 95,
"extension": [".ts"],
"reporter": ["html", "lcov", "text", "text-summary"],
"include": ["src"]
}
按照我的设置,除了上面截图中的 html 报告之外,在控制台中还会以文字的形式打印出测试覆盖率的结果,就像下面这样:


现在本地已经能正确的生成单元测试覆盖率报告了,甚至连 lcov 格式的报告文件已经有了,我们便可以将其上传至 coveralls.io 进行外显了。
Coveralls
Coveralls 是一个知名的代码覆盖率站点,它支持多种语言,包括 C、C++、C#、Java、JavaScript、Objective-C、PHP、Python、Ruby、Scala 等等。我们只需要在项目中进行单元测试,并将测试结果以特定的格式上传,即可在站点中看到详实的代码覆盖率数据页面,就像下图一样:

使用 Coveralls 非常简单,只需要打开 coveralls.io,使用 GITHUB SIGN IN 登录 (或其他你更喜欢的注册登录方式),然后在左边栏中点击 + 号,选择 Add Repos,即可通过切换 SWITCH 按钮的方式添加你的开源项目。
* 私有项目也是可以添加的,但为了防止无意间泄漏代码,我没有开启这个能力。
添加完成后,便可以通过 Detail 按钮跳转到项目的代码测试覆盖率页面,但新配置的项目肯定是不会显示出任何数据的,下面让我介绍下如何进行上传。
很显然,我们不可能选择手动上传,因为这太麻烦了。不过好在 Coveralls 提供了多种自动化的上传方式,我们可以使用脚本的方式完成上传动作。曾经最简单的方式是使用 Travis CI 来执行脚本,但它在前两年改变了自己的策略,当你的额度用完后便无法免费使用了,所以现在更推荐使用的是 Github Actions。
Github Actions
Github Actions 是 Github 提供的自动化构建、测试、部署、发布等能力的工具,它通过你在项目中配置的 .github/workflows 文件来执行相应的动作。它的配置有点像 Jenkins 的感觉,但与 Jenkins 还是有不少不同之处。
我在我的示例项目中配置了两个 workflow,分别是 ci-test.yml 用来执行自动化测试,和 npm-publish.yml 用来将项目构建后发布到 npm。这里将以 ci-test.yml 为例,介绍如何配置自动化测试及将测试结果上传至 Coveralls。
其实 Coveralls 官方文档中已经写了标准方案,在 https://docs.coveralls.io/integrations#official-integrations 中有 Coveralls Github Action 的集成说明,点开后则是 Github Actions 的插件说明:https://github.com/marketplace/actions/coveralls-github-action。在最傻瓜的情况下,你只需要把它文档中 Standard Example 的代码复制到你项目中即可。
我的项目是很多年前的,在写这篇文章时,我发现插件都已经有了更新的版本。但为了能帮助大家进行对比,我也会把我的配置文件贴出来供大家参考,你可以可以直接使用官方示例中的最新版本。
name: CI-TEST
on: [push, pull_request]
jobs:
ci-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x, 18.x, 20.x, 22.x]
steps:
- uses: actions/checkout@v2
- name: Set Nodejs Version
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install Dependencies
run: npm i
- name: Unit Tests
run: npm test
- name: Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
我的这个配置允许我在将代码推送到 Github 仓库、或创建 PR 后,自动分别在 NodeJS 14.x ~ 22.x 不同版本的环境下,分别独立的执行 npm test 命令,然后将测试结果上传至 Coveralls。
能看到这里的肯定都是程序员,我想我不需要针对配置文件进行过多的解释了。
当 CI-TEST 成功运行结束后,你便可以在 Coveralls 中看到本次提交的测试结果了。
Badges
完成这一切之后,我们可以在 Coveralls 中拷贝出 Badges 的代码,然后插入到 README.md 文件中,让我们的项目看起来更加专业。徽章代码如下:
[](https://coveralls.io/github/LuckyStarry/luckystarry-collections?branch=master)
被渲染之后的结果就像下面图中一样。

全文完
如果你想完整的参考我的项目,可以访问我的 Github 仓库 https://github.com/LuckyStarry/luckystarry-collections。
这是一个为 NodeJS 代码的集合对象(
Array)赋予像.NET那样的LINQ查询能力的库,由于没有依赖特定的环境,所以在浏览器端或服务器端都可以使用,感兴趣的小伙伴也欢迎使用。

