Skip to main content

nestjs 项目测试

Author: Housu Zhang

简介

代码测试是通过人工或自动化手段对软件进行验证的过程,旨在找出 bug,降低商业风险,确认程序逻辑与产品需求的一致性,并确保用户得到无误的程序应用。

我们的项目一般主要由单元测试(unit test),端对端(e2e test)组成,另外有 playwright 自动化测试。

这是一个针对 nestjs 测试的教程。

  1. 单元测试

    • 检查独立工作单元的行为。
    • 验证程序的基本组成部分按预期工作。
    • 单元规模较小,容易隔离和定位错误。
    • 使用多种测试技术进行细致测试 ​​。
  2. e2e 测试

    • 模拟用户操作验证业务页面功能的基本可用性。
    • 常用于测试完整的应用流程。
    • 确认用户交互与系统整体功能的一致性 ​。
  3. 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 官网。

单元测试

  1. 环境搭建:

    • 要使用 github 组织通用的包,需要先认证你的 github export GITHUB_TOKEN=your_token
    • 如果你在组织中,则可以成功更新包: pnpm i 或者 pnpm install
  2. 测试文件:

    • nestj 项目主要包含 moudle, controller 和 service 三个部分。只需要测试 controller 和 service 即可。
    • serverice 测试,包含每个函数的测试(覆盖率越高越好),分别要测试成功和不成功等情况。
    • controller 测试,只需要测试能够成功调用 service 即可。
  3. 测试命令:

    • pnpm test

e2e 测试

  1. 环境搭建:

    • 同上单元测试的两步。
    • 有的项目需要启动测试链,将合约部署到测试链上。通过 pnpm blockchain:up 开启测试链。(测试链本质是 hardhat network,细节可以在 hardhat.config.js 中配置)。
    • 启动 docker 镜像。通过 pnpm docker:up 启动项目所需要的镜像(本地数据库等)。
    • 注意,以上提到的命令,都可以在 package.json 文件中查到。
  2. 测试文件:

    • e2e 测试针对项目某个特定的模块(对包含完整 controller,service 和 module 进行测试),意在调用 controller 里的 api 实现 service 逻辑,验证代码。原则上 e2e 测试只能通过看 api 文档进行测试,如果发现 bug,一般由测试人员在源码中 debug。
  3. 测试运行配置:

    • 在右上角的运行 configuratino 处,点击三个点,edit 进入进行配置。
    • 创建 jest 配置,选择测试 configration file 为你的 jest-e2e.json 文件。
    • jest options 是你的测试文件 e.g. collection.e2e-spec.ts
    • 在 Environment variables 那里填入你的环境变量。

单元+e2e 测试规范

  1. beforeall 中配置所需的启动项目的代码。
  2. aftereach 和 afterall 中删除使用过的数据库,断开链接等。
  3. 一个函数一个 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 可以帮助我们自动实现检测预览环境的状态,测试预览环境的接口,进一步提高测试的覆盖率,保证生产环境的健康。

  1. 环境搭建:

    • 同样需要配置 GITHUB_TOKEN 通过 pnpm i 拉取所需要的包。
  2. 测试文件:

    • 为了和其他测试区分(spec),playwright 的测试文件命名都用 test,e.g. XXX.playwright.test.ts
  3. 运行:

    • 运行全部的 playwright 测试文件, pnpm test:playwright 或者 pnpm playwright test ./XXX 这里的 XXX 是你 playwright 测试所在的文件夹名
    • 运行单个 playwright 测试文件,pnpm playwright test XXX.playwright.test.ts

测试代码规范

  1. 注意我们大部分场景都是测试接口,因此测试函数的输入参数一般都是 request 而不是 page
  2. 通过环境变量配置 url,在本地运行测试的时候,root url 就是我们的 localhost,因此测试前要先启动项目(start:dev)。在 github 的 ci 中运行时,则会测试 preview,root url 是 vercel 上部署的 preview url。因此必须通过 AppConfig.PREVIEW_URL 来导入 url。
  3. 通过 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% 的覆盖率是理想的,但并不总是实际或必要的。
  • 覆盖率应与其他质量度量和实践结合使用,以确保测试的有效性和效率。

我们的项目主要关注行覆盖率和函数覆盖率。