将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长度
@Testpublic 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");}
@Testpublic 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 {@Testpublic void testProduct() {// a lot of codes here...}}
public class ProductControllerTest {@Testpublic void testSingleProduct() {}@Testpublic void testMultpleProduct() {}@Testpublic void testProductWithNullParams() {}@Testpublic 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仍然可以通过,这样是不符合预期的
@Testpublic 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");}
而是应该使用下面这段代码
@Testpublic 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就不能通过了
@Testpublic void testGetRange() {SyncClient client = new SyncClient(endpoint, accessId, accessKey,instanceName);Assert.assertEquals(client.getRange(), xxx);}
而是应该使用Mock的client来处理
@Testpublic 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
比如下面这段代码是不好维护的
@Testpublic void testGetRange() {SyncClient client = Mock(SyncClient.class);Assert.assertEquals(client.getRange(), xxx);}
这样,当ut不过的时候,输出只会是
expected: but was:
从错误信息完全看不出来为什么错了
所以应该尽量避免这样的代码,而是应该使用
Assert.equals
@Testpublic void testGetRange() {SyncClient client = Mock(SyncClient.class);Assert.assertEquals(client.getRange(), xxx);}
