单元测试的三部曲
打桩,即为非测试目标方法设置返回值,这些返回值在测试目标方法中被使用。执行测试,调用测试目标方法。验证测试结果,如测试方法是否被执行,测试结果是否正确等。
上报测试
/** 被测代码 */
fun reportLikedVideoRemoveExposure(num: String?, feed: stMetaFeed?, rank: String?, userId: String?) {
val map = JsonObject()
map.addProperty("num", num ?: "")
map.addProperty("rank", rank ?: "")
map.addProperty("user_id", userId ?: "")
Router.getService(BeaconReportService::class.java).reportUserExposure("liked.remove", "1", map.toString(), feed?.id ?: "", feed?.poster_id ?: "")
}
这里就使用PowerMock来写单测,mockk的步骤一样
// 真实对象方法执行时对方法体中的对象方法进行mock和验证
@Test
public void testReportLikedVideoRemoveExposure() {
BeaconReportService service = mock(BeaconReportService.class); // 使用mock后该对象的属性是null,方法没有实际执行能力
JsonObject jsonObject = getJsonObject("1", "1", "aswq");
mockStatic(Router.class); // reportLikedVideoRemoveExposure()方法调用了静态方法需要提前mock其行为
when(Router.getService(BeaconReportService.class)).thenReturn(service); // 指定mock对象的方法行为
PersonalCenterReport.INSTANCE.reportLikedVideoRemoveExposure("1", any(), "1" ,"aswq");
verify(service)
.reportUserExposure(Mockito.eq("liked.remove"), Mockito.eq("1"),
eq(jsonObject.toString()), eq(""), eq(
"")); // 验证结果
}
@NotNull
private JsonObject getJsonObject(String num, String rank, String userId) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("num", num);
jsonObject.addProperty("rank", rank);
jsonObject.addProperty("user_id", userId);
return jsonObject;
}
@Test
public void testReportLikedVideoRemoveExposure_null() {
BeaconReportService service = mock(BeaconReportService.class);
JsonObject jsonObject = getJsonObject("1", "1", "aswq");
mockStatic(Router.class);
when(Router.getService(BeaconReportService.class)).thenReturn(service);
PersonalCenterReport.INSTANCE.reportLikedVideoRemoveExposure("1", any(), "1" ,"aswq");
verify(service)
.reportUserExposure(Mockito.eq("liked.remove"), Mockito.eq("1"),
eq(jsonObject.toString()), eq(""), eq(
""));
}
使用PowerMock时需要注意:
@PrepareForTest({Router.class, BeaconReportService.class})
@Rule
public PowerMockRule rule = new PowerMockRule();
被测方法使用kotlin编写,需要把?.的两种情况都覆盖到才会有单测覆盖率
mockk时
[0]: argument: richlike.remove, matcher: eq(richlike.video), result: -
[1]: argument: 1, matcher: eq(1), result: +
[2]: argument: {"num":"1","rank":"","user_id":""}, matcher: eq({"video_cover":"big","num":"1","label":"0","search_id":"","search_word":""}), result: -
[3]: argument: , matcher: eq(), result: +
[4]: argument: , matcher: eq(), result: +
根据Matchers文档如下,在打桩阶段有一个原则,一个mock对象的方法,如果其若干个参数中,有一个是通过Matchers提供的,则该方法的所有参数都必须通过Matchers提供。而不能是有的参数通过Matchers提供,有的参数直接给出真实的具体值。
If you are using argument matchers, all arguments have to be provided by matchers. E.g: (example shows verification but the same applies to stubbing):
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
//above is correct - eq() is also an argument matcher
verify(mock).someMethod(anyInt(), anyString(), "third argument");
//above is incorrect - exception will be thrown because third argument is given without argument matcher.
Matcher methods like anyObject(), eq() do not return matchers. Internally, they record a matcher on a stack and return a dummy value (usually null).
上述文档中给出的示例,可以使用eq()方法将真实的具体值转换为Matchers提供的值或者在mock行为时参数使用具体的值。