nestjs 项目测试
Author: Housu Zhang
简介
代码测试是通过人工或自动化手段对软件进行验证的过程,旨在找出 bug,降低商业风险,确认程序逻辑与产品需求的一致性,并确保用户得到无误的程序应用。
我们的项目一般主要由单元测试(unit test),端对端(e2e test)组成,另外有 playwright 自动化测试。
这是一个针对 nestjs 测试的教程。
单元测试:
- 检查独立工作单元的行为。
- 验证程序的基本组成部分按预期工作。
- 单元规模较小,容易隔离和定位错误。
- 使用多种测试技术进行细致测试 。
e2e 测试:
- 模拟用户操作验证业务页面功能的基本可用性。
- 常用于测试完整的应用流程。
- 确认用户交互与系统整体功能的一致性 。
Playwright 测试:
- 自动化浏览器测试
- 模拟用户的各种操作和浏览器特定事件 。
Mock
测试环境通常会缺少一些环境变量,缺少一些包。这种情况下,我们需要通过 mock 来模拟这些缺失的东西。比如在 e2e 测试中,我们缺少一些环境变量,可以通过创建一个 mock class 来模拟这些变量。
文件参考: XXX.e2e-spec.ts
example:
class MockConfigService {
config: { [key: string]: any } = {
PORT: parseInt(process.env.PORT) || 3340,
HOST: process.env.HOST || "localhost",
// 其他环境变量 ...
JWT_SECRET: process.env.JWT_SECRET,
};
onInit = jest.fn().mockImplementation(() => {});
}
具体遇到的 mock 相关应用请参考 jest mock 官网。
单元测试
环境搭建:
- 要使用 github 组织通用的包,需要先认证你的 github
export GITHUB_TOKEN=your_token
。 - 如果你在组织中,则可以成功更新包:
pnpm i
或者pnpm install
- 要使用 github 组织通用的包,需要先认证你的 github
测试文件:
- nestj 项目主要包含 moudle, controller 和 service 三个部分。只需要测试 controller 和 service 即可。
- serverice 测试,包含每个函数的测试(覆盖率越高越好),分别要测试成功和不成功等情况。
- controller 测试,只需要测试能够成功调用 service 即可。
测试命令:
pnpm test
e2e 测试
环境搭建:
- 同上单元测试的两步。
- 有的项目需要启动测试链,将合约部署到测试链上。通过
pnpm blockchain:up
开启测试链。(测试链本质是 hardhat network,细节可以在 hardhat.config.js 中配置)。 - 启动 docker 镜像。通过
pnpm docker:up
启动项目所需要的镜像(本地数据库等)。 - 注意,以上提到的命令,都可以在
package.json
文件中查到。
测试文件:
- e2e 测试针对项目某个特定的模块(对包含完整 controller,service 和 module 进行测试),意在调用 controller 里的 api 实现 service 逻辑,验证代码。原则上 e2e 测试只能通过看 api 文档进行测试,如果发现 bug,一般由测试人员在源码中 debug。
测试运行配置:
- 在右上角的运行 configuratino 处,点击三个点,edit 进入进行配置。
- 创建 jest 配置,选择测试 configration file 为你的 jest-e2e.json 文件。
- jest options 是你的测试文件 e.g.
collection.e2e-spec.ts
- 在 Environment variables 那里填入你的环境变量。
单元+e2e 测试规范
- beforeall 中配置所需的启动项目的代码。
- aftereach 和 afterall 中删除使用过的数据库,断开链接等。
- 一个函数一个 describe,需要尽可能测试全面。例如函数执行成功的情况,检测返回值等。函数执行错误时,能正确返回相应错误。
example: collection 中 findone 函数的测试
describe("findOne", () => {
it("should return a collection /(GET): 200", async () => {
const data = {
name: "My Collection",
cover: "http://xxxxx.com/xxx.png",
symbol: "MC",
// 其他数据
};
const collection = await prismaService.collections.create({ data });
await request(app.getHttpServer())
.get(`/collections/find-one?id=${collection.id}`)
.expect(200) // get方法成功返回200 (post方法成功返回201)
.expect((res) => {
expect(res.body.id).toBe(collection.id);
// 检测其他返回值
});
});
// 这个函数的其他情况(成功或者失败)
});
playwright 测试
单元测试和 e2e 测试的不足
单元测试,和 e2e 测试还不能保证在生成环境中不会出现问题。例如,更新代码以后,在部署的生成环境没有即使更新环境变量。这种情况下,单元测试和 e2e 测试能够通过,但是生成环境会崩溃。因此,我们往往需要手动检测生成的 preview url(预览这个 pull request 部署上 vercel 上的接口,如果合入 mian 分支,那么这个预览的 api 将进入生产环境)。
playwright 的作用
playwright 可以帮助我们自动实现检测预览环境的状态,测试预览环境的接口,进一步提高测试的覆盖率,保证生产环境的健康。
环境搭建:
- 同样需要配置
GITHUB_TOKEN
通过pnpm i
拉取所需要的包。
- 同样需要配置
测试文件:
- 为了和其他测试区分(spec),playwright 的测试文件命名都用 test,e.g.
XXX.playwright.test.ts
- 为了和其他测试区分(spec),playwright 的测试文件命名都用 test,e.g.
运行:
- 运行全部的 playwright 测试文件,
pnpm test:playwright
或者pnpm playwright test ./XXX
这里的 XXX 是你 playwright 测试所在的文件夹名 - 运行单个 playwright 测试文件,
pnpm playwright test XXX.playwright.test.ts
- 运行全部的 playwright 测试文件,
测试代码规范
- 注意我们大部分场景都是测试接口,因此测试函数的输入参数一般都是
request
而不是page
。 - 通过环境变量配置 url,在本地运行测试的时候,root url 就是我们的 localhost,因此测试前要先启动项目(start:dev)。在 github 的 ci 中运行时,则会测试 preview,root url 是 vercel 上部署的 preview url。因此必须通过 AppConfig.PREVIEW_URL 来导入 url。
- 通过 get,post 等方法调用接口,并测试返回值。注意,这里会用到真实的测试数据库,为了真实模拟用户的使用,我们也不能删除之前生产的测试数据。因此,一个测试需要模拟一个完整的测试流程。比如:用户创生成约,用户用生成的合约 id 创建里一个 collection,用户用这个 collectionid 再铸造一个 nft。我们需要用一个测试函数,完整的测试这个流程。
example:测试 health,检测部署上 vercel 上的 preview url 的状态。
import { test, expect } from "@playwright/test";
import { AppConfig } from "../src/config";
test("should display service statuses", async ({ request }) => {
const url = new URL(AppConfig.PREVIEW_URL + "/health");
const response = await request.get(url.toString());
expect(await response.json()).toEqual({
status: "ok",
name: "App",
services: [
{
status: "ok",
name: "redis",
services: [],
},
{
status: "ok",
name: "Database",
services: [],
},
],
version: "dev",
});
});
注意事项
本教程主要基于 NFTMarketPlace-Server 项目,examples 可以去 github 上观看学习。
测试覆盖率指南
测试覆盖率(Test Coverage),亦称代码覆盖率(Code Coverage),是一个度量软件测试完整性的重要指标,表示了被自动化测试覆盖的代码量,通常用百分比来表示。
覆盖率类型
以下是几种常见的测试覆盖率类型:
1. 语句覆盖率(Statement Coverage)
- 测试了多少个单独的代码声明。
2. 分支覆盖率(Branch Coverage)
- 测试了代码中的多少个分支路径。
3. 函数覆盖率(Function Coverage)
- 测试了多少个独立的函数或方法。
4. 行覆盖率(Line Coverage)
- 测试了多少行代码。
5. 条件覆盖率(Condition Coverage)
- 测试了多少个布尔表达式的每个可能结果。
6. 路径覆盖率(Path Coverage)
- 测试了多少种可能的执行路径。
覆盖率的重要性
- 高覆盖率可以提高代码质量的信心,但并不意味着没有缺陷。
- 100% 的覆盖率是理想的,但并不总是实际或必要的。
- 覆盖率应与其他质量度量和实践结合使用,以确保测试的有效性和效率。