原则
- [F]AST 快速性
- [I]solate 隔离性
- [R]epeatabel 可重复性
- [S]elf-Validating 自验证性
- [T]imely 及时性
Fast 快速
单元测试应该尽量保持简单和快速,应该做到毫秒级。
需要考虑输入数据的大小、数量和质量对代码性能的影响,并对测试用例的执行时间进行评估。
- 采用内存数据库
- 依赖的外部接口,采用 Mock 的方式进行测试
Isolate 隔离
测试用例应该是独立的,不受其他测试用例的影响,不依赖其他单元测试的执行顺序,不依赖外部资源。
- 对于依赖的外部接口,外部存储都是用 Mock 解决
- 对于 DI 框架中所依赖的类,直接注入到内存
- 一个单元测试应该关注与逻辑的一个方面(存在多个断言应思考是否真正需要,真正需要是否抽离新的测试用例方法)
多关注单测的隔离性,同时也会驱动类、方法实现单一职责(SRP)
Repeatabel 可重复性
每次都是重复的入参和结果,运行都是固定不变的
不受外界的影响,如:
- 时间,经典的例子就是判断时间的业务中,某些时间运行成功,某些时间运行失败
Self-Validating 自验证性
必须使用断言 Assert 方式来进行正确性验证,而不是 var_dump、System.out、Println 等输出进行人肉校验。
单测必须走持续集成&自动化测试,而不是人工上线运行。
Timely 及时性
单元测试应该具有及时性,先于开发而不是后续开发完在补充。
如何限制后续补充单元测试的坏习惯:
- 代码 review 时查看
- git 提交阶段或服务部署阶段使用工具限制
除了 FIRST 原则还有 AIR 原则、ASCII 原则等,具体都没啥大区别,可参考那些年,我们写过的无效单元测试_阿里云云栖号的博客-CSDN 博客
其他准则
BCDE 原则,以保证被测试模块的交付质量。
-
B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
-
C:Correct,正确的输入,并得到预期的结果。
-
D:Design,与设计文档相结合,来编写单元测试。
-
E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。
-
合理的命名测试用例:确保每个方法只测试 "被测类" 的一个明确特性, 并相应的命名测试方法. 典型的命名俗定是
test[what]
, 比如testSaveAs()
,testAddListener()
,testDeleteProperty()
等 -
避免过度断言:永不失败的测试没有价值,一个测试应该只有一个失败的原因。
-
数据操作有完整流程,且数据有标记:不应该手动在数据库插入数据,而是由程序插入或者导入数据的方式来准备数据,并且自动回滚机制,不给数据库造成脏数据,如无法避免产生的数据有明确的前后缀标识。
用例结构
一般都是一个三段式的用例结构,目前较常见的就是 AAA 和 GWT,整体测试结构时相同的。如果说两者有什么区别的话,那区别在于 AAA 的构思更倾向于技术实现,GWT 更倾向于业务流程。
与其说 AAA 与 GWT 区别,不如说 TDD(Test-Driven Development) 和 BDD(Behavior-Driven Development),区别。
AAA
Arrange-Act-Assert 是单元测试时的常见模式。顾名思义,它由三个主要操作组成:
- Arrange:准备测试环境,包括设置对象的状态、参数、依赖项等。
- Act:执行测试代码,即调用要测试的方法或代码块。
- Assert:验证测试结果是否符合预期,即判断输出结果是否正确或符合要求。
例如,对于以下方法的测试:
public int add(int x, int y) {
return x + y;
}
使用 AAA 方法的测试代码如下:
@Test
public void testAdd() {
// Arrange
int x = 3;
int y = 4;
MyClass myClass = new MyClass();
// Act
int result = myClass.add(x, y);
// Assert
assertEquals(7, result);
}
GWT
Given-When-Then
- Given:设置测试环境,包括设置对象的状态、参数、依赖项等。
- When:执行测试代码,即调用要测试的方法或代码块。
- Then:验证测试结果是否符合预期,即判断输出结果是否正确或符合要求。
例如,对于以下方法的测试:
public String greeting(String name) {
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
return "Hello, " + name + "!";
}
使用 GWT 方法的测试代码如下:
@Test
public void testGreeting() {
// Given 准备用例所需的上下文
String name = "Alice";
MyClass myClass = new MyClass();
// When 调用待测的函数
String result = myClass.greeting(name);
// Then 断言
assertEquals("Hello, Alice!", result);
}
@Test(expected = IllegalArgumentException.class)
public void testGreetingWithNullName() {
// Given
String name = null;
MyClass myClass = new MyClass();
// When
myClass.greeting(name);
// Then (expecting exception)
}
参考
二、优秀单元测试的五个特征 FIRST - 纪玉奇 - 博客园
单元测试准则
要对自己写的代码负责 | 蓝士钦
有效的单元测试之一--代码坏味道(读书笔记精华带源码)
unit-test-specification/README.md at master · cyneck/unit-test-specification · GitHub
网友评论