Android单元测试技巧

之前做单测是为了完成任务,现在实在地为了测试代码的正确性,就容易遇到坑,刚好之前也没写总结,现在记一些好的单测方法,还有踩过的坑。

一.日志输出

我觉得这个是最重要的,测试代码时肯定要根据输出的日志来判断代码走到哪,之前又太注重行覆盖率,对这块不重视,这块还花了挺多时间处理的。

Log.xxx是不能输出日志到控制台的,只能是System.out.print才能看到,所以我们需要mock Log的静态方法,把它的输出转变为System.out.print:

PowerMockito.mockStatic(Log.class);
Mockito.when(Log.e(anyString(), anyString())).then(new Answer<Void>() {
    @Override
    public Void answer(InvocationOnMock invocation) throws Throwable {
        String TAG = (String) invocation.getArguments()[0];
        String msg = (String) invocation.getArguments()[1];
        System.err.println(String.format("Error: /%s: %s", TAG, msg));
        return null;
    }
});

其它日志等级类似了,可以把主要的一些方法都写在一个类里,后面方便我们调用。

这里要注意是,PowerMockito是不能mock返回值为空的(可能也是我没找到?)。

二.测试模板

一般用Robolectric和PowerMock搭配,但是搭配的时候也要设置一些参数,不然两者容易冲突,比如这两个在一块的时候,Robolectric下的RuntimeEnvironment.application就会变成null,所以准备了一个模板,相当于集成测试环境:

@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class)
// 这个就是为了避免PowerMock把Robolectric的一些东西mock掉
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
@PrepareForTest({Log.class})

public class TestTemplate {
    // 这个相当于setUp()函数,不加是mock不了的.
    @Rule
    public PowerMockRule rule = new PowerMockRule();

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        MockLogUtils.mockLog();
    }
}

依赖:

testImplementation "org.powermock:powermock-module-junit4:1.6.6"
testImplementation "org.powermock:powermock-module-junit4-rule:1.6.6"
testImplementation "org.powermock:powermock-api-mockito:1.6.6"
testImplementation "org.powermock:powermock-classloading-xstream:1.6.6"
testImplementation 'org.robolectric:robolectric:3.8'
// 以下非必须,robolectric还有很多这样的影子库
testImplementation 'org.robolectric:shadows-httpclient:3.3.2'

PS:这个模板有个缺陷,如果你要Mock一个Context,就会和robo有冲突,这种情况就再建一个测试类,用PowerMock.runner来测试吧。

三.测试技巧:

(1)除了覆盖分支,还需要考虑边界:
参数传空,函数调用多次,留意执行时间,等等(一起补充吧)

(2)Whitebox是个好东西:
它完成了很多反射的功能,setInternalState和getInternalState,可以直接帮我们设置和获取被测试对象里面的属性变化,一些函数函数执行完毕后,就可以用verify或者assert去验证。

(3)测试异常抛出:
在Test(expect = Exception.class) 里面,expect加上你要的异常。

(4)when…thenReturn和doReturn…when的区别:
(a)when(…) thenReturn(…)会调用真实的方法,如果你不想调用真实的方法而是想要mock的话,就不要使用这个方法;
(b)doReturn(…) when(…) 不会调用真实方法

(5)PrepareForTest的问题。
最近发现一个问题,在类级别加了注解PrepareForTest({Log.class}),测试一个方法的时候,在方法级别也加了个PrepareForTest({XXX.class}),结果运行这个方法的时候说Log.class方法没有prepare,还不知道为什么,发现一旦分开prepare就不行,如果在类或者方法上面同时prepare这两个类,就好了。

(6)测试异步方法
如果要被测试的方法会异步执行,比如里面new Thread去执行,或者用线程池去执行,正常来说是看不到结果的,因为Test方法执行完就会System.exit()掉,这个时候子线程可能都还没创建。可以在Test方法最后Thread.sleep()住,等待子线程去执行。

(7)测试Handler
Handler是Android特有的,所以本地单测的时候是直接跳过的,如果要执行Handler的回调是否正确,可以mock它,拿到传给它的参数后手动去执行:

 @Mock
Handler handler;

@Before
@PrepareForTest({ Handler.class})
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);

    PowerMockito.mock(Handler.class);
    // mock它的post方法,其它类似
    PowerMockito.doAnswer(new Answer<Object>() {
        @Override
        public Object answer(InvocationOnMock invocation) throws Throwable {
            Runnable runnable = (Runnable) invocation.getArguments()[0];
            runnable.run();
            return null;
        }
    }).when(handler).post((Runnable) any());
}

(8)spy了一个对象,返回的对象就是可以Mock的了。这里说一下spy和mock的区别:
①mock出来的对象完全是假的,调用它的任何方法都不会真正调用,一般用在被测对象的依赖对象。
②spy,如果需要调用真实的方法,就要真正地创建这个类对象,但这个时候如果你要去mock它一些方法,当调用when(obj)的时候就会报错,因为这个时候obj是不可mock的,如果要mock,就spy(obj),它会返回一个新的obj,就可以mock了,和mock出来的对象相比,它是『部分模拟』,你没有去mock它的一些方法,它还是会去调用真正的方法。

猜你喜欢

转载自blog.csdn.net/aa642531/article/details/109256999