单元测试在软件测试中是最基础的测试,它通常在程序员完成编码以后完成,如果是测试驱动的开发 (TDD) 那么更需要在编码完成之前就写好单元测试。单元测试是白盒测试,它只测试软件中的某块代码,而不像集成测试那样需要和软件组件之外的其他组件联合测试,在持续集成 (CI/CD) 中,软件构建 (build) 完成以后的第一个测试可能就是单元测试。
那么程序员在写单元测试的时候要达到什么要求呢? FIRST 原则就是一个指导编写单元测试的原则。FIRST 是下面几个单词的缩写:
Fast,
Isolated/Independent,
Repeatable,
Self-Validating,
Timely/Thorough.
和 SOLID 设计原则一样, FIRST 经常会在程序员之间的交流中被引用到。下面我们简要介绍一下什么是 FIRST。
Fast 快
程序员的每个单元测试应该很快能够被执行完,如果要给一个标准的话,应该是秒级的。单元测试只测试自己的代码,不应该像集成测试那样,连接外部的组件。如果是和时间相关的测试,不应该真的等待时间流逝再看结果,而应该使用 Mock 来模拟时间。
比如下面的代码,我们通过 IDateTimeProvider 接口,模拟了时间,就不用等一个小时才能拿到结果。这个测试显然能够在 1 秒之内结束。
public interface IDateTimeProvider
{
/// <summary>
/// Get current date time.
/// </summary>
DateTime Now { get; }
}
[TestClass]
public class DateTest {
[TestMethod]
void Weekday_should_be_Monday_after_sunday(){
var datetimeMock = new Mock<IDateTimeProvider>();
var car = new MyCar(dateTimeMock) //
car.Speed = 30; // 30千米/小时
var now = DateTime.Now;
datetimeMock.Setup(o => o.Now).Returns(now);
car.Start();
datetimeMock.Setup(o => o.Now).Returns(now.AddHours(1));
int d1 = car.Distance;
Assert.AreEqual(30, d1);
}
}
Isolated / Independent 隔离 / 独立
每个单元测试不应该依赖于其他的单元测试结果。它应该能够独立运行,而不是要求某些单元测试完成以后才能完成这个单元测试。同样的某个单元测试的结果,也不应该影响其他单元测试的运行结果。如果需要一些配置,这些配置应该在本单元测试的初始化阶段完成。
Repeatable 可重复
单元测试无论在任何环境下都应该得到相同的结果,而不应该受到数据库,网络等环境因素的影响。单元测试的结果只跟我们的代码有关,而不应该和环境有关。如果单元测试失败,它应该是我们代码的某个地方有问题,或者是初始值设置的问题。这样在拿到单元测试的结果的时候,我们就可以专注于我们的代码,而不是环境因素。
Self-Validating 自我校验
单元测试只会返回一个布尔结果,要么成功,要么失败,而不是要程序员自己检查运行结果以后才能判断是否成功。在单元测试中,我们应该加上断言,比如 Assert.IsTrue, Assert.AreEqual等,这些断言直接决定测试结果是否成功。
Timely / Thorough 及时/完整
如果我们的开发方式是测试驱动开发 (TDD) ,那么这一条指的是单元测试应该在生产代码 (production code) 完成前完成。如果你不用 TDD,这一条指的是单元测试要达到下面的标准
- 覆盖所有基本路径 (Happy Path)
- 边界条件
- 安全问题
- 覆盖所有可能的功能性用例,而不只是应付 100% 代码覆盖率的要求
结论
单元测试是代码测试中的基础测试,FIRST是写好单元测试的重要原则,它要求我们的单元测试快速,独立,可重复,自我校验,及时/完整。
本文是程序员经常用到的原则之一,下面我们还会介绍 AAA 和 DRY。
下面的链接中,有我写的软件设计之 SOLID 原则,欢迎阅读。
参考链接:
软件设计中的 SOLID 原则
编写代码中的 DRY 原则
单元测试中的 AAA 规则
F.I.R.S.T principles of testing
Writing Your F.I.R.S.T Unit Tests