模式: Arrange/Act/Assert 布局-操作-断言
标签: 测试
通常被认为: Given/When/Then
意图
Arrange/Act/Assert (AAA) 是一种组织单元测试的模式
它将测试分为三个明确的步骤:
- 安排: 执行测试所需要的设置和初始化
- 行动: 采取测试所需的行动
- 断言: 验证测试结果
解释
这种模式有几个显著的好处。它在测试设置、操作和结果中创建了一个很清晰的界限。 这种结构使得代码更容易阅读和理解 如果你将步骤按顺序排列并格式化代码, 将它们分开,你可以扫描测试并可以很快理解它的功能
在编写测试时, 它还强制执行一定的程度的规程。你必须思考清楚你的测试需要执行的三个步骤。 同时它使测试更自然地编写的,因为你已经有了大纲。
ps: 有点像现在流行的TDD测试驱动开发,但是实际开发的过程中很少写单元测试,以后工作中可以试着写下,好处还是很多的😊
现实生活中的例子
我们需要为一个类编写全面而清晰的单元测试
简单地说
Arrange/Act/Assert 是一种测试模式, 它将测试组织为三个清晰的步骤, 以便于维护
维基百科说
Arrange/Act/Assert是用于在单元测试中排列和格式化代码的模式
编程示例
让我们首先介绍一下要进行单元测试的'Cash'类
public class Cash {
private int amount;
Cash(int amount) {
this.amount = amount;
}
void plus(int addend) {
amount += addend;
}
boolean minus(int subtrahend) {
if (amount >= subtrahend) {
amount -= subtrahend;
return true;
} else {
return false;
}
}
int count() {
return amount;
}
}
然后我们根据 Arrange/Act/Assert模式编写单元测试。注意每个单元测试的单独步骤
public class CashAAATest {
@Test
void testPlus() {
// Arrange 初始化
Cash cash = new Cash(3);
//Act 操作
cash.plus(4);
//Assert 断言, 判断处理是否正确
assertEquals(7, cash.count());
}
@Test
void testMinus() {
//Arrange 初始化
Cash cash = new Cash(8);
//Act 操作
Boolean result = cash.minus(5);
//Assert 断言, 判断处理是否正确
assertTrue(result);
assertEquals(3, cash.count());
}
@Test
void testInsufficientMinus() {
//Arrange 初始化
Cash cash = new Cash(1);
//Act 操作
boolean result = cash.minus(6);
//Assert 断言, 判断处理是否正确
assertFalse(result);
assertEquals(1, cash.count());
}
/*
* 结合多个操作测试
*/
@Test
void testUpdate() {
//Arrange 初始化
Cash cash = new Cash(5);
//Act 操作
cash.plus(6);
boolean result = cash.minus(3);
//Assert 断言, 判断处理是否正确
assertTrue(result);
assertEquals(8, cash.count());
}
}
适用性
在以下情况下使用Arrange/Act/Assert
- 你需要构建单元测试, 以便它们更易于阅读、维护和迭代
总结
Arrange/Act/Assert模式 可以让我们更容易编写单元测试。
笔者在实际项目开发中,很少很少写单元测试,大多都是通过postman发请求进行测试, 可能会导致改了一些东西而影响到其他部分, 出现bug. 后续要加强这方面的实践,变得规范起来
如果单元测试完备的话, 能够有效较少bug的数量, 减轻开发人员单元测试的时间, 对后续维护迭升级帮助都很大
题外话
我们开发的系统一般都是和数据库和其他系统进行交互,而单元测试执行中可能没有这些依赖的条件.
那这样如何编写单元测试呢?
可以通过借助工具如mockito来mock(模拟)这些交互,其实步骤也是类似的.
如下所示:
public class ApiGatewayTest {
/**
* 被测试的类
*/
@InjectMocks
private ApiGateway apiGateway;
/**
* mock的接口,构造输入值和返回值
*/
@Mock
private PriceClient priceClient;
/**
* 设置要进行mock的类, 一般都是当前的类即this
*/
@BeforeEach
public void setup(){
MockitoAnnotations.initMocks(this);
}
@Test
void testGetProductMobile() {
String price = "20";
// 设置mock对象的方法的输出结果
when(priceClient.getPrice()).thenReturn(price);
// 在apiGateway调用到 priceClient里的getPrice方法时,就会直接返回 事先预定好的值
MobileProduct mobileProduct = apiGateway.getProductMobile();
// 执行方法并判断结果是否正确
assertEquals(price, mobileProduct.getPrice());
}
}