将ut规格化
1. 初始化:给变量赋值,设置mock等 2. 调用:调用你想测的函数 3. Assert:Assert输出是否符合预期 这三部分最好用空行分开
ProductDTO product1 = requestProduct(1);
ProductDTO product2 = new ProductDTO("1", List.of(State.ACTIVE, State.REJECTED))
assertThat(product1).isEqualTo(product2);
变量名要清晰
像下面这段代码的变量名就不好理解 最好是改成这样就可以知道第一个变量是实际得到的变量值,而第二个变量是预期的变量值
ProductDTO actualProduct = requestProduct(1);
ProductDTO expectedProduct = new ProductDTO("1", List.of(State.ACTIVE, State.REJECTED))
assertThat(actualProduct).isEqualTo(expectedProduct);
ut要短
ut太长会让人看不懂。通过大量使用helper function来达到缩短ut的长度,比如:通过将其中的初始化代码包装到helper function中来缩短ut长度
@Test
public void categoryQueryParameter() throws Exception {
List products = List.of(
new ProductEntity().setId("1").setName("Envelope").setCategory("Office").setDescription("An Envelope").setStockAmount(1),
new ProductEntity().setId("2").setName("Pen").setCategory("Office").setDescription("A Pen").setStockAmount(1),
new ProductEntity().setId("3").setName("Notebook").setCategory("Hardware").setDescription("A Notebook").setStockAmount(2)
);
for (ProductEntity product : products) {
template.execute(createSqlInsertStatement(product));
}
String responseJson = client.perform(get("/products?category=Office"))
.andExpect(status().is(200))
.andReturn().getResponse().getContentAsString();
assertThat(toDTOs(responseJson))
.extracting(ProductDTO::getId)
.containsOnly("1", "2");
}
@Test
public void categoryQueryParameter() throws Exception {
insertIntoDatabase(
createProductWithCategory("1", "Office"),
createProductWithCategory("2", "Office"),
createProductWithCategory("3", "Hardware")
);
String responseJson = requestProductsByCategory("Office");
assertThat(toDTOs(responseJson))
.extracting(ProductDTO::getId)
.containsOnly("1", "2");
}
一个ut只测试一个逻辑
ut只能测试一个逻辑,多个逻辑需要添加多个ut来进行测试,比如下面这样就不行分割成多个ut来测试各个corner case
public class ProductControllerTest {
@Test
public void testProduct() {
// a lot of codes here...
}
}
public class ProductControllerTest {
@Test
public void testSingleProduct() {
}
@Test
public void testMultpleProduct() {
}
@Test
public void testProductWithNullParams() {
}
@Test
public void testProductWithEmptyName() {
}
}
ut测试要自包含
不要使用helper function或者production code里面的变量 比如下面这段代码,将变量隐藏在了helper function里面,会使ut逻辑看不懂
insertIntoDatabase(createProduct());
List actualProducts = requestProductsByCategory();
assertThat(actualProducts).containsOnly(new ProductDTO("1", "Office"));
可以将要使用的变量作为函数的parameter传进函数中,比如下面这段代码是好的
insertIntoDatabase(createProduct("1", "Office"));
List actualProducts = requestProductsByCategory("Office");
assertThat(actualProducts).containsOnly(new ProductDTO("1", "Office"));
也不要使用production code里面的变量,比如下面使用到了RowKeyGenerator.LENGTH这个变量,之后RowKeyGenerator.LENGTH这个变量被改了,代码逻辑也跟着被改了,但是UT仍然可以通过,这样是不符合预期的
@Test
public void testGenerate() {
byte[] rk = RowKeyGenerator.generate(project, metric, dimensions);
String rowKey = new String(rk);
byte[] md5 = RowKeyGenerator.md5(project, metric);
byte[] rowKeyPrefix = new byte[RowKeyGenerator.LENGTH];
System.arraycopy(md5, 0, rowKeyPrefix, 0, RowKeyGenerator.LENGTH);
Assert.assertEquals(rowKey, new String(rowKeyPrefix) + project + "$," + metric + "$,userId:12345$,instanceId:i-abc");
}
而是应该使用下面这段代码
@Test
public void testGenerate() {
byte[] rk = RowKeyGenerator.generate(project, metric, dimensions);
String rowKey = new String(rk);
byte[] md5 = RowKeyGenerator.md5(project, metric);
byte[] rowKeyPrefix = new byte[4];
System.arraycopy(md5, 0, rowKeyPrefix, 0, 4);
Assert.assertEquals(rowKey, new String(rowKeyPrefix) + project + "$," + metric + "$,userId:12345$,instanceId:i-abc");
}
这样,如果RowKeyGenerator.LENGTH改成了5,这个UT就不能通过了,可以避免出现逻辑上的bug
不要使用外部数据库的数据
以下代码使用了外部数据库的数据,当外部数据库的数据没了UT就不能通过了
@Test
public void testGetRange() {
SyncClient client = new SyncClient(endpoint, accessId, accessKey,
instanceName);
Assert.assertEquals(client.getRange(), xxx);
}
而是应该使用Mock的client来处理
@Test
public void testGetRange() {
SyncClient client = Mock(SyncClient.class);
Assert.assertEquals(client.getRange(), xxx);
}
可以使用Mockito这个framework来进行mock的测试:https://site.mockito.org/
不要使用随机变量,System.currentSystemMillis(), Instance.now()这样的变量值
使用这样的变量会使ut变得有时通过有时通不过,这样对维护ut变得非常困难
尽量避免使用Assert.assertTrue或者Assert.assertFalse
比如下面这段代码是不好维护的
@Test
public void testGetRange() {
SyncClient client = Mock(SyncClient.class);
Assert.assertEquals(client.getRange(), xxx);
}
这样,当ut不过的时候,输出只会是
expected: but was:
从错误信息完全看不出来为什么错了
所以应该尽量避免这样的代码,而是应该使用
Assert.equals
@Test
public void testGetRange() {
SyncClient client = Mock(SyncClient.class);
Assert.assertEquals(client.getRange(), xxx);
}