美文网首页Android开发经验谈Android开发
Android单元测试---常见问题和套路

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

作者: 老王头碎碎念 | 来源:发表于2018-06-05 23:16 被阅读1次
    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);
    
    }
    

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

    image.png

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

    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单元测试---常见问题和套路

        本文链接:https://www.haomeiwen.com/subject/phkvsftx.html