Android单元测试(二) - Roboletric + Powermock
2016年在做微信打印Android agent app的时候,研究过如何在开发Android app的时候TDD。详细参考此篇博文Android单元测试。
当时的最终方案是:Powermock + Mockito + JUnit做local test。因为要Mock各种Android SDK中的类,所以有一种被无力控制的恐惧。所以在做Niwatori的时候,迟迟没有开展TDD。因为最近添加的功能稍微有点复杂了,感觉各种不顺,所以考虑重新拾起TDD。于是有了各种折腾,并验证了之前无力的恐惧后的这一篇。不过结果好的是看起来找到了一条Android app TDD开发不错的路子。
这一切都要归功于
Roboletric
起初的相识还是在Android developer官网关于测试的页面。然后在搜索Powermock用法的时候,看到很多帖子都提到了roboletric。因为认识到Mockito + Powermock的弱点(如下),所以决定转向roboletric。
Mockito
- 不能mock final class/method
- 不能mock private/static
Powermock
- 需要自己mock各种class,当函数大了的时候,因为会引用非常多的Android类,所以会让人感到很无力
在编译Android app的时候,Android SDK以android.jar的形式参与编译。而这个android.jar中的各个类并没有具体的实现,只是一个stub。所以如果不经过mock就直接在Unit Test中使用Android类就会出现Error java.lang.RuntimeException: Stub!
错误。
Roboletric就是为了消除这种错误诞生的。它为所有的Android类定义了可以跑在PC端JVM下的class。而且提供了很多额外的功能,方便开发者做隔离测试以及UI方面的测试。
入门
首先推荐的还是官网主页。然后还有几个不错的中文帖子:
- Robolectric使用教程: 可以参考Properties文件以及Activity生命周期部分。
- Android单元测试之Robolectric框架: 可以参考Robolectric配置以及针对各种Android组件的测试方法部分。
对于配置环境和第一个简单的实例,最好是按照官网的步骤去做。因为在setup的时候,往往有很多版本号依赖要处理,尤其是这种出身社区的框架。往往依赖处理不是特别好。
Powermock + Roboletric
用上Roboletric以后,你会发现原先需要各种mock的函数,现在不用怎么mock就能编过了,甚至有些listener都能起作用。但是因为Roboletric本身只是提供Android SDK的副本,对于测试并不是那么在行,或者说其目的并不是一个更power的mock库。当我们需要为某些class提供预设的功能,异或需要对某些对象调用次数进行verify的时候,还是需要powermock的(或者Mockito,但鉴于其功能实在太low,能用Powermock的就用Powermock吧)。
入门参看Roboletric GitHub wiki:Using PowerMock。示例如下:
@RunWith(RobolectricTestRunner.class)@Config(constants = BuildConfig.class)@PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*" })@PrepareForTest(Static.class)public class DeckardActivityTest { @Rule public PowerMockRule rule = new PowerMockRule(); @Test public void testStaticMocking() { PowerMockito.mockStatic(Static.class); Mockito.when(Static.staticMethod()).thenReturn("hello mock"); assertTrue(Static.staticMethod().equals("hello mock")); }}
特别要注意的是:
- 因为class开头用了
@RunWith(RobolectricTestRunner.class)
标注,所以无法用PowermockTestRunner.class
。所以必须加上下面两行:
@Rulepublic PowerMockRule rule = new PowerMockRule();
否则会出现各种Powermock无法工作的异常,例如class没有prepare啦,不能mock private函数等等。
- 因为Roboletric提供了很多Shadow类,而这些类和mock有冲突。例如
Intent intent = mock(Intent.class)
就会导致这个莫名其妙的错误。
java.lang.LinkageError: loader constraint violation: loader (instance of net/bytebuddy/dynamic/loading/MultipleParentClassLoader) previously initiated loading for a different type with name "org/xmlpull/v1/XmlSerializer
所以就要加上@PowerMockIgnore
。甚至Ignore的列表要更长,例如:
@PowerMockIgnore({ "org.mockito.*", "org.robolectric.*", "android.*", "javax.xml.*", "org.xml.sax.*", "org.w3c.dom.*", "org.springframework.context.*", "org.apache.log4j.*"})
踩坑列表
PowerMockito.whenNew is not working
You need to put the class where the constructor is called into the @PrepareForTest annotation instead of the class which is being constructed - see Mock construction of new objects.
总结
用Roboletric来解决Android SDK问题,用Powermock+JUnit来解决unit test问题。