前言
最近开始给公司的项目写单元测试,先从已经抽离成库的类开始写,因为不会涉及到界面,所以目前写起来较为容易。
单元测试框架采用的是 powerMock 框架,不过在写的过程中,发现了一些问题,今天我们就说一下一些常见的问题,和对某些类型代码常见的套路(针对这种类型应该如何编写单元测试用例)。
常见问题
1、android 类方法调用为空,Method isEmpty in android...
Android 相关类依赖的环境是 Android 虚拟机,而我们不在真机上做单元测试的时候,依赖的是 JVM 环境,这时候我们可以主动创建需要类的方法。
举个例子:
public HttpRequest getProductSearch(String url){
if(TextUtils.isEmpty(url)){
return null;
}
return new HttpRequest(url);
}
假设这是我们要测的类,正常来说我们我们可以这样做单元测试
@Test
public void TestGetProductSearch(){
HttpSetting httpSetting = new HttpSetting();
HttpRequest request = httpSetting.getProductSearch("url");
Assert.assertNotNull(request);
}
然后美滋滋的运行一下,发现
这就是我们在 JVM 的环境里找不到 TextUtils.isEmpty() 这个方法,这时候我们就可以手动创建这个方法,注意包名要正确:
再次运行测试用例,即可得到通过的结果。
2、Wanted but not invoked:
先看一下要被测试的代码,在 HttpSetting 类里面:
public void getCart(User user){
user.setName("hello");
user.setPin("world");
}
然后我们想要验证这个方法内 User 的行为,有时候会这样写:
@Test
public void TestGetCart(){
User user = mock(User.class);
HttpSetting httpSetting = mock(HttpSetting.class);
httpSetting.getCart(user);
Mockito.verify(user).setName("hello");
Mockito.verify(user).setPin("world");
}
再一次美滋滋的点下运行,然后发现:
这个错误日志是说验证的代码中,其实没有交互,但是我们被测试明明有交互,那么到底问题出在哪里呢?其实问题就出在了下面这行代码中:
HttpSetting httpSetting = mock(HttpSetting.class);
mock(HttpSetting.class) 在我看了其实就是模拟这个类,并且记录行为,但是并不会真正执行一些逻辑,所以你直接调用 mock 出来类的方法,只会产生一个记录,并不会真正执行里面发生的逻辑,导致了出现的错误提示,所以我们把那行代码改成下面这样,即可解决问题。
HttpSetting httpSetting = new HttpSetting();
还有一种情况,就是你用到了 PowerMock 特性,但是并没有加上下面两行注解内容,这样加上对应的注解就行
@RunWith(PowerMockRunner.class)
@PrepareForTest( {用到的类名.class })
public class XXX
3、org.powermock.reflect.exceptions.FieldNotFoundException:
这个问题笔者在 stackoverflow 上看到的答案基本都是 PowerMock 库版本号低,相对于 Mockito 库的版本。
发生错误时,笔者导入的相关库的版本:
testCompile 'org.mockito:mockito-core:1.9.5'
// required if you want to use Powermock for unit tests
testCompile 'org.powermock:powermock-module-junit4:1.5.6'
testCompile 'org.powermock:powermock-module-junit4-rule:1.5.6'
testCompile 'org.powermock:powermock-api-mockito:1.5.6'
当我把 powermock 三个相关库的版本改成 1.6.5 后再运行一下测试用例,这个错误果然消失了。
针对套路
1、方法内部 new 对象如何 Mock,验证其行为
还是先看下要被测试的代码
public void initMall(){
Mall mall = new Mall();
mall.setCode("123456");
mall.setLogo("tree");
mall.setName("I am Groot");
}
起先我看到类似的代码我是懵逼的,完全不知道怎样去测,幸亏在偶然间看到了 PowerMock 强大特性介绍,发现其中竟然可以模拟构造函数,使构造函数返回我们 mock 出来的对象,从而验证其发生的行为。
下面我们看测试用例代码
@Test
public void TestInitMall() throws Exception{
Mall mall = mock(Mall.class);
whenNew(Mall.class).withAnyArguments().thenReturn(mall);
HttpSetting httpSetting = new HttpSetting();
httpSetting.initMall();
Mockito.verify(mall).setCode("123456");
Mockito.verify(mall).setLogo("tree");
Mockito.verify(mall).setName("I am Groot");
}
关键代码是
whenNew(Mall.class).withAnyArguments().thenReturn(mall);
这行代码含义是当你以任何参数创建出来的这个类,都会返回我指定的类的实例,当然也可以指定参数,或者没有参数,返回指定的类实例。
2、需要验证的方法内部有类私有变量怎么办
这种情况也很常见,随便写一个被测试的代码来看看
private TimeLimit timeLimit = new TimeLimit();
public String getLimitTime(){
//这里我们假设 timeLimit.getCurrentTime() 肯定会返回一个不为空的值
return timeLimit.getCurrentTime();
}
随意点的单元测试用例,可能会写出下面的测试代码
@Test
public void TestGetLimitTime(){
HttpSetting httpSetting = new HttpSetting();
String limitTime = httpSetting.getLimitTime();
Assert.assertNotNull(limitTime);
}
随便验证一下返回结果不为空就可以了,但是如果我们想更加详细的测一下,验证是否是通过 TimeLimit 拿到的结果,这时我们就可以这样做
@Test
public void TestGetLimitTime(){
TimeLimit timeLimit = mock(TimeLimit.class);
when(timeLimit.getCurrentTime()).thenReturn("1");
HttpSetting httpSetting = new HttpSetting();
Whitebox.setInternalState(httpSetting,"timeLimit", timeLimit);
String limitTime = httpSetting.getLimitTime();
Assert.assertNotNull(limitTime);
Mockito.verify(timeLimit).getCurrentTime();
}
通过下面这行代码可以指定类实例的私有变量
Whitebox.setInternalState(httpSetting,"timeLimit", timeLimit);
这里 Junit 验证结果和 Mockito 验证行为有些冲突,因为是 mock 的对象,所以如果不指定方法行为,将会返回空,就不会通过 Assert.assertNotNull(limitTime)
3、如何 mock 类的部分方法
当你在测试一个类的一个方法时,这个类又调用了该类的另一个方法时,如果这个方法很复杂,不想执行内部逻辑,只需要返回一个什么结果的时候就可以模拟这个方法。
我们简单看下示例代码:
要被测试的类方法:
public void login(String pin){
if(checkUser(pin)) {
User user = new User();
user.setPin(pin);
user.doLogin();
}
}
public boolean checkUser(String pin){
//假设执行了各种复杂逻辑
return true;
}
然后是单元测试代码
@Test
public void TestLogin() throws Exception{
HttpSetting httpSetting = new HttpSetting();
HttpSetting spy = PowerMockito.spy(httpSetting);
User user = mock(User.class);
whenNew(User.class).withNoArguments().thenReturn(user);
when(spy.checkUser("hello world")).thenReturn(true);
spy.login("hello world");
Mockito.verify(user).setPin("hello world");
Mockito.verify(user).doLogin();
}
单元测试的常见问题和套路今天就先分享到这,对 Android 中的单元测试感兴趣的可以关注本人以前和以后分享的文章。