单元测试用例编写避坑指南

 

1.入口方法如何查找mock调用链

写单测用例的时候,需要对入口方法涉及的各种实例进行mock,诸如数据库操作,redis操作,rpc访问等等,至于纯内存计算的实例,只要是条件ok,则可以不必进行mock。

进行mock的时候,有些实例调用有可能隐藏的很深,假设我们没有发现,有可能造成单测用例执行失败,这就需要我们debug待测方法,列出需要进行mock的实例,然后一一操作即可。

单测用例生成框架, tiny-autounit,则是通过递归走查方法体,然后找到相关实例并进行mock,节省大量的查找时间。

 

2. 对ElasticSearch进行mock

系统中,如果用了es,且掺杂有比较复杂的逻辑,则需要对es进行mock,整体mock方式如下:

SearchResponse进行mock: 

SearchResponse searchResponse = mock(SearchResponse.class);
 
 
SearchHits searchHits = mock(SearchHits.class);
when(searchResponse.getHits()).thenReturn(searchHits);
 
 
SearchHit[] searchHits1 = new SearchHit[1];
searchHits1[0] = mock(SearchHit.class);
when(searchResponse.getHits().getHits()).thenReturn(searchHits1);
 
 
when(searchHits1[0].getSourceAsString()).thenReturn("{ " +
        ""updateDate": "2020-03-23 14:47:40", " +
        ""name": "业务建模说明", " +
        ""orderNum": 10000, " +
        ""updateUser": "wangxuanyi5", " +
        ""createUser": "taijian", " +
        ""showStatus": 1, " +
        ""documentContent": { " +
        ""updateDate": "2020-07-08 14:56:18", " +
        ""menuId": 253, " +
        ""updateUser": "chengtingwei", " +
        ""createUser": "taijian", " +
        ""documentStatus": 1, " +
        ""id": 254, " +
        ""content": "在充分了解前台业务现状得更快速高效。", " +
        ""createDate": "2020-03-22 16:34:13" " +
        "}, " +
        ""id": 253, " +
        ""parentId": 251, " +
        ""createDate": "2020-03-22 16:34:13" " +
        "}");
 
TotalHits totalHits = new TotalHits(10, TotalHits.Relation.EQUAL_TO);
when(searchResponse.getHits().getTotalHits()).thenReturn(totalHits);
 
when(elasticsearchNativeOperation.search(Mockito.any(), Mockito.anyString())).thenReturn(searchResponse);

UpdateResponse进行mock:

UpdateResponse updateResponse = mock(UpdateResponse.class);

BulkByScrollResponse进行mock:

BulkByScrollResponse bulkByScrollResponse = mock(BulkByScrollResponse.class);

 

3. 对static类进行mock

注意,@BeforeClass和@AfterClass要成对出现。

@BeforeClass
public static void beforeClass(){
   authorityUtilMockedStatic = Mockito.mockStatic(AuthorityUtil.class);
}
 
@AfterClass
public static void afterClass(){
   authorityUtilMockedStatic.close();
}
 
private static MockedStatic<AuthorityUtil> authorityUtilMockedStatic ;
 
@Test
public void when_list_then_return_success1(){
   authorityUtilMockedStatic.when(()->AuthorityUtil.getUserErp()).thenReturn("test");
   authorityUtilMockedStatic.when(()->AuthorityUtil.isPdAdmin()).thenReturn(false);
   //todo biz
   List returnResult = pdProductInfoServiceImpl.list();
   assert returnResult != null;
}

4. 对入参类型进行mock过程中的注意事项

如果入参是自定义的类对象,则需要利用Mockito.any()来进行,也可以自己new出来一个新类来进行:

when(elasticsearchNativeOperation.search(Mockito.any())).thenReturn(searchResponse);

如果入参既有自定义类对象,也有元数据类型,则可以用如下方式:

when(elasticsearchNativeOperation.search(Mockito.any(), Mockito.anyString())).thenReturn(searchResponse);
或者
when(elasticsearchNativeOperation.search(Mockito.any(), eq("testSring"))).thenReturn(searchResponse);

千万要注意的是,一旦参数中,有一个参数你用了Mockito.***,那么其他参数你要么用Mockito.***来替代, 要么用eq(***具体的参数值***)来替代,不允许直接输入参数值。

如果入参是Integer,但是你用了Mockito.any()来替代,大概率会出现nullpointer错误,这点需要注意,一定要用对替代类型。

 

5. 实例返回结果为null

经常我们在打好mock桩之后,debug代码中后,发现返回的结果为null,怎么设置参数都不行。实际上这种情况,是因为你入参中有参数为null造成的,此时,你需要将为null的参数给处理为非null的数据即可。

如果null参数数据比较难处理,你也可以在打桩的地方,直接给对应的参数设置为 eq(null) 也可以,这样实例返回结果就会返回你的打桩值了。

6. 单测方法一对多

一般一个业务方法是对用多个单测方法的,因为有些分支条件,需要多个单测方法才能覆盖完毕,所以不要吝啬多写单测方法,即便重复了,也没事儿。

7. Exception异常类处理

异常类的话,一般在方法头上打,不必自己进行捕获,利用expected关键字即可。

@Test(expected = ComponentBusinessException.class)
public void when_addAppComponent_then_appname_null() {
    when(lockService.lock(eq(LockEnums.LockTypeEnums.REDIS), Mockito.anyString(), Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt())).thenReturn(false);
    Component4AddAppEntity entity = new Component4AddAppEntity();
    entity.setCurrentLimitLevel("test");
    entity.setDeptName("test");
    ComponentInfoEntity returnResult = componentServiceImpl.addAppComponent(entity);
    assert returnResult != null;
}
原文地址:https://www.cnblogs.com/scy251147/p/15356144.html