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);
}