如何进行单元测试

42 阅读3分钟

1. 什么是单元测试

单元测试是一种试验单个软件组件或方法(也称为“工作单元”)的测试。 单元测试仅应测试开发人员控件内的代码。 它们不测试基础结构问题。 基础结构问题包括与数据库、文件系统和网络资源的交互。

2. 单元测试的好处

2.1 比执行功能测试节省时间

功能测试费用高。 它们通常涉及打开应用程序并执行一系列你(或其他人)必须遵循的步骤,以验证预期的行为。 测试人员可能并不总是了解这些步骤,这意味着为了执行测试,他们必须联系更熟悉该领域的人。 对于细微更改,测试本身可能需要几秒钟,对于较大更改,可能需要几分钟。 最后,在系统中所做的每项更改都必须重复此过程。

而单元测试只需按一下按钮即可运行,只需要几毫秒时间,且无需测试人员了解整个系统。 测试通过与否取决于测试运行程序,而非测试人员。

2.2 防止回归

回归缺陷是在对应用程序进行更改时引入的缺陷。 测试人员通常不仅测试新功能,还要测试预先存在的功能,以验证先前实现的功能是否仍按预期运行。

使用单元测试,可在每次生成后,甚至在更改一行代码后重新运行整套测试。 让你确信新代码不会破坏现有功能。

2.3 可执行文档

在给定某个输入的情况下,特定方法的作用或行为可能并不总是很明显。 你可能会想知道:如果我将空白字符串传递给它,此方法会有怎样的行为? Null?

如果你有一套命名正确的单元测试,每个测试应能够清楚地解释给定输入的预期输出。 此外,它应该能够验证其确实有效。

2.4 减少耦合代码

当代码紧密耦合时,可能难以进行单元测试。 如果不为编写的代码创建单元测试,则耦合可能不太明显。

为代码编写测试会自然地解耦代码,因为采用其他方法测试会更困难。

3. 如何做单元测试

3.1 哪些模块的代码需要做单元测试

  • 建议 目前后端微服务分为 API、Service、Repository三层结构。其中API主要是定义接口,Repository作为仓储层操作数据库,这两部分一般 都没有很多业务逻辑。大部分业务逻辑集中在Service层,所以应该主要对Service层进行单元测试;
  • 必须 所有的Common、Util类的静态方法必须进行用例全面的单元测试;
  • 建议 一般不对私有方法单独进行单元测试。

3.2 构建单元测试项目(xUnit & Moq)

单元测试代码应该在test文件夹下面的XXX.XXX.Tests项目中进行。

├── src

│ ├── XXX.XXX.API

│ ├── XXX.XXX.Service

│ ├── XXX.XXX.Repository

└── test

└── XXX.XXX.Tests

我们只需添加下Moq组件:dotnet add package Moq

Moq组件可用来mock需要的数据

3.3 书写单元测试

3.3.1 命名规范

  • 必须 类命名:{要测试的类名}_Test.cs,如:XXXQueryService_Test.cs
  • 必须 方法命名:{要测试方法名}{条件}{期望结果},如:GetDetail_WhenCannotFoundRecord_ShouldThrowNotFoundException,Add_MultipleNumbers_ReturnsSumOfNumbers

3.3.2 3A模式

每个单元测试的代码应该有以下几个部分组成

  • Arrange:安排对象,根据需要对其进行创建和设置
  • Act:作用于对象
  • Assert:断言某些项按预期进行

3.3.3 一些原则

  • 建议 Assert断言只有一个,尽量避免多个断言
  • 必须 避免魔幻字符串,让测试代码可读性提高
  • 建议 避免逻辑,手动字符串串联和逻辑条件,例如 if、while、for 和 switch 等等
  • 必须 保证测试代码本身无Bug

3.3.4 举例说明

        [Fact]
        public async Task GetDetail_WhenCannotFoundRecord_ShouldThrowNotFoundException()
        {
            // Arrange
            // create IXXXRepository mock object
            var mockXXXRepository = new Mock<IXXXRepository>();
            // mock: return null when input 0
            mockXXXRepository.Setup(it => it.GetDetail(0)).Returns(Task.FromResult((object)null));
            var XXXQueryService = new XXXQueryService(mockXXXRepository.Object);

            // Act
            var exception = await Record.ExceptionAsync(() => XXXQueryService.GetDetail(0));

            // Assert
            Assert.IsType<NotFoundException>(exception);
        }