发布于 2026-01-06 2 阅读
0

第九部分:打卡系统:测试:后端测试

第九部分:打卡系统:测试:后端测试

这篇文章是我撰写的一系列文章的一部分,旨在介绍打卡系统。如果您想了解更多信息,可以阅读以下文章:

介绍

这是我关于测试的第一篇博文,也可以说是关于质量评估(QA)的第一篇博文。这个项目并非从一开始就采用测试驱动开发(TDD),但我目前正在进行测试阶段。得益于测试阶段,我发现了很多小bug,如果项目已经上线,这些bug可能会造成很大的问题。但实际上,项目将在接下来的几周内正式上线。这些测试对于修复在此期间发现的几个bug将非常有帮助。

测试的第一步是决定测试什么。任何人都可能会告诉你,你必须测试整个应用程序,并且必须达到接近 100% 的覆盖率,但实际上你并不需要测试整个应用程序,而只需要测试软件中最关键的部分。这些关键部分的覆盖率可能接近 90% 或 70%,具体取决于你的应用程序。

在本例中,我将说明我们应该进行哪些测试:

  • 服务:
  1. app.service.

  2. 用户服务。

  3. 身份验证服务。

  • 控制器:
  1. app.controller。

  2. 用户.控制器。

因此,在我们的项目中,没有必要对 DTO、常量、实体和模块进行测试,因为这些测试很难,而且价值很小。

后端采用 NestJS 框架开发,并使用 Jest 作为测试工具。此外,NestJS 还包含一个强大的测试包,可以模拟类似于 Angular 测试包的环境。

服务测试

在这篇文章中,我将介绍服务单元测试。这类测试是测试金字塔中最简单的测试。我建议测试新手从服务单元测试入手,因为服务是功能单一、易于隔离的小函数。正因如此,它们也是最简单、最容易测试的。

应用服务

我们要测试的第一个服务是 app.service.ts,它使用了两个服务:AuthService 和 UserService。因此,我们的测试套件必须检查 app.service 是否能使用正确的参数调用这两个服务。

第一步是为每个待开发的测试进行初始配置。因此,`app.service.ts` 的构造函数需要两个服务(`AuthService` 和 `UserService`),它们将作为间谍对象。`@nestjs/testing` 中的 `Test` 包提供了 `createTestingModule` 方法,用于创建一个测试模块。在这个 `testingModule` 中,`providers` 数组由 `AppService` 和两个使用工厂创建的间谍对象组成。以下代码展示了此初始配置:

下一步是确定我们要测试的内容。主要思路是独立测试每个函数/方法。因此,以下方法是 app.service.ts 代码的一部分。

authIn 和 authOut 方法应该检查 authService 是否使用正确的参数调用。在本例中,这是一个单元测试,因此不应该使用实际的函数/方法调用 this.authService.authIn 和 this.authService.authOut 方法,这就是我们使用 spy 来监控这些方法的原因。测试这些函数的代码如下:

在之前的测试中,您可以注意到期望值与 `authIn` 和 `authOut` 方法相关,这些方法用于检查这些方法是否被调用以及参数是否正确。在这些方法中,`authIn` 或 `authOut` 方法抛出的错误与此无关,因为这些方法中的职责已委托给其他服务。

与 usersTicketing 方法相关的测试如下:

在这种情况下,我们创建了一个间谍对象,用于在执行 `now from Date` 函数时进行测试。在这种情况下,测试结果必须始终返回同一天(测试必须是纯函数,不能依赖于外部因素)。因此,在这个测试中,我们需要检查 `getUsersMustBeWorkingNow` 方法是否已被调用,以及 `usersTicketing` 方法的返回值是否为一个对象,该对象包含键为 `users` 的对象,其值为间谍对象 `UserService` 中提供的值,时间戳为模拟日期的时间戳。

用户服务

测试用户服务的步骤与 app.service.ts 中使用的步骤相同。因此,第一步是创建测试模块,其中包含将在后续测试中使用的 spy 和服务。

第一种方法非常简单,因为它使用的技术与 app.service.ts 中的技术相同。因此,要测试的代码如下:

它的测试套件只检查是否使用正确的参数(用户原型和初始参数)调用了 save 方法,如下面的代码所示:

接下来要测试的方法是调用 TypeORM ORM,如下所示:

在这个测试中,我们需要使用链式责任来监视 usersRepository 中的每个方法。为此,我们使用 Jest 提供的工厂方法。

如您所见,我们正在检查 TypeORM 中每个被调用的方法以及调用时使用的参数,简单快捷。

以下方法可能存在一个著名的代码异味(方法过长),但如果您仔细阅读该方法,您会发现它很好地调用了数据库查询,并且代码本身并没有代码异味

查询语句有多种参数组合,但测试内容相同,因此我们需要一个包含输入输出的表格来进行测试。Jest 提供了一个名为 each 的参数,可用于参数化我们的测试。

表格如下:

您可以看到,我们表格中用于测试的参数如下:

  • 年份:与我们想要测试用户是否在建筑物内的时间对应的年份。

  • 月份:与我们想要测试用户是否在建筑物内的时间对应的月份。

  • day:与我们想要测试用户是否在建筑物内的时刻对应的日期。

  • 小时:与我们想要测试用户是否在建筑物内的时刻对应的小时数。

  • 分钟:对应于我们要测试用户是否在建筑物内的时刻的分钟数。

  • :对应于我们想要测试用户是否在建筑物内的时刻的秒数。

  • hourNowExpected:小时,应该使用其他参数列表返回该方法。

  • dayNowExpected:应该使用其他参数列表返回该方法的日期。

我们的测试需要大量的间谍函数来测试 ORM,并使用表中的预期值来检查私有方法是否返回了 ORM 查询所需的值。如果私有方法是公共的,测试会更容易,但测试绝不应该修改原始代码(除非发现 bug)。

测试的第一部分是创建间谍程序,以检查是否使用了正确的参数调用该方法。然后,调用 `service.getUsersMustBeWorkingNow()` 方法。最后,还有一系列预期任务,用于检查 ORM 的方法是否使用了正确的参数调用。

因此,该测试的最终代码如下:

身份验证服务

最后一个要测试的服务是 auth.service.ts。测试方法与之前的测试类似。因此,第一步是在每个测试中进行初始配置。

待测试的代码如下:

您可以看到,有一些私有方法无法直接测试,因为它们类似于将代码复制粘贴到公共方法中。因此,这些方法没有测试套件。

私有方法如下:

在我们的 authIn 和 authOut 方法测试套件中,有三个不同的测试,代表了一种场景,如下所示。

  1. 身份验证。
  • 应该具备身份验证功能并返回问候语。

  • 当找不到用户时,应返回错误。

  • 当出现意外错误时,应返回错误信息。

  1. authOut。
  • 应该保存身份验证信息并返回再见。

  • 当找不到用户时,应返回错误。

  • 当出现意外错误时,应返回错误信息。

身份验证代码如下:

authOut 代码如下:

结论

本文介绍了如何使用 Jest 和 NestJS 框架测试后端服务。这段代码最有趣的地方在于,我们可以使用 spy 来隔离测试用例,并创建一个输入输出表,从而自动化执行许多相同但参数不同的测试。

下一篇文章中,我将向您展示如何对控制器进行单元测试。

原文发表于www.carloscaballero.io,日期为 2019 年 3 月 15 日。

文章来源:https://dev.to/carlillo/part-9-clock-in-out-system-testing-backend-testing-3imk