安卓单元测试,只看这一篇就足够啦。真正的完全解析,真正的从0到1,Junit结合Mockito与Robolectric实现从M层到V层,Jacoco扫描函数、逻辑、代码行数单元测试覆盖率100%的全面测试。你是否还在为了验证联网与未联网状态而频繁的开关WiFi开关?或者你是否还在为一个switch判断而频繁的使用debug断点setValue来观测代码的逻辑判断情况?又或者你是否还在为了校验某个UI文案的正确性而反复的比对UI稿?可能你会反问,难道写完代码自测也有错?当然不是,自测是一个良好的习惯,不过作为一名工程师,你要做的不应该只是看看点点的黑盒测试,而是应该设计出一套能够让代码测试代码,一劳永逸的测试工程。
首先我们从Model层开始,通过具体代码来详尽说明一下一个单元测试覆盖率100%的测试工程是如何建立的。严格意义上讲,Model数据层负责数据加载与储存,是游离于安卓环境之外的存在,所以它可以不需要借助安卓SDK的支持。使用Junit结合Mockito即可做到100%条件分支覆盖率的单元测试。如果项目的Model层有安卓依赖,可能就表明此处的代码需要重构了,这也是单元测试其中的一个意义,让代码逻辑更清晰。清除Model层的安卓依赖的另一层面好处是让测试case更高效,我们都知道在不借助UI单元测试框架的情况下在测试case中使用安卓依赖会抛出RuntimeException原因是我们的IDE与SDK只提供了项目的开发和编译环境,并没有提供运行环境,所以直接引用安卓相关代码的时候就会抛出异常。为此,谷歌官方提供了Espresso测试框架,但Espresso的有着运行需要借助安卓设备,运行时间过长,Jenkins自动化集成困难等问题,最终只能放弃了对官方框架的使用。而是采用Robolectric框架作为UI单元测试的核心。即便如此,运行测试case的时间也需要5秒以上,但对于一个没有安卓依赖的Model类,跑完全部case的时间可以降低至毫秒级。所以,去除Model层所不需要的安卓依赖还是很有必要的。
Model层完整测试代码如下:
/**
* Author : YangHaoyi on 2017/6/30.
* Email : yanghaoyi@neusoft.com
* Description :MVP---M test case example.
* Change : YangHaoYi on 2017/6/30.
* Version : V 1.0
*/
@RunWith(MockitoJUnitRunner.class)
public classWeatherModelTest {
privateWeatherModelmodel;
@Mock
ApiServiceapi;
@Mock
WeatherDataConvertconvertData;
@Mock
WeatherRequestListenerlistener;
private static finalStringJSON_ROOT_PATH="/json/";
privateStringjsonFullPath;
privateWeatherDatanetData;
privateMapqueryMap;
@Before
public voidsetUp() {
RxUnitTestTools.openRxTools();
model=newWeatherModel();
}
@Test
@SuppressWarnings("unchecked")
public voidtestParams() {
model.request(listener,"沈阳");
try{
Field fieldParam = WeatherModel.class.getDeclaredField("queryMap");
Field fieldKey = WeatherModel.class.getDeclaredField("CITY");
fieldParam.setAccessible(true);
setFinalStatic(fieldKey, true);
Map queryMaps = (Map) fieldParam.get(model);
String key = (String) fieldKey.get(model);
assertEquals("验证queryMap的Key",key,"city");
String city = queryMaps.get("city");
assertEquals("验证queryMap的value",city,"沈阳");
}catch(Exception e) {
//reflect error
}
}
@Test
@SuppressWarnings("unchecked")
public voidtestRequestSuccess() {
initResponse();
Mockito.when(api.getWeather(queryMap)).thenReturn(Observable.just(netData));
ArgumentCaptor captor = ArgumentCaptor.forClass(WeatherData.class);
model.request(listener,"沈阳");
Mockito.verify(api).getWeather(queryMap);
Mockito.verify(listener).showLoading();
Mockito.verify(listener).hideLoading();
Mockito.verify(convertData).convertData(captor.capture());
WeatherData result = captor.getValue();
intstatus = result.getStatus();
assertEquals("验证code",status,1000);
}
@Test
public voidtestStatusError() {
initResponse();
netData.setStatus(1001);
Mockito.when(api.getWeather(queryMap)).thenReturn(Observable.just(netData));
ArgumentCaptor captor = ArgumentCaptor.forClass(WeatherData.class);
model.request(listener,"沈阳");
Mockito.verify(api).getWeather(queryMap);
Mockito.verify(listener).showLoading()