Android单元测试---常见问题和套路

image.png

前言

最近开始给公司的项目写单元测试,先从已经抽离成库的类开始写,因为不会涉及到界面,所以目前写起来较为容易。
单元测试框架采用的是 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);

}

然后美滋滋的运行一下,发现

Android单元测试---常见问题和套路_第1张图片
image.png

这就是我们在 JVM 的环境里找不到 TextUtils.isEmpty() 这个方法,这时候我们就可以手动创建这个方法,注意包名要正确:

Android单元测试---常见问题和套路_第2张图片
image.png

再次运行测试用例,即可得到通过的结果。

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");
}

再一次美滋滋的点下运行,然后发现:


image.png

这个错误日志是说验证的代码中,其实没有交互,但是我们被测试明明有交互,那么到底问题出在哪里呢?其实问题就出在了下面这行代码中:

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:

image.png

这个问题笔者在 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 中的单元测试感兴趣的可以关注本人以前和以后分享的文章。

你可能感兴趣的:(Android单元测试---常见问题和套路)