Android单元测试(二) - Roboletric + Powermock

Edit

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方面的测试。

入门

首先推荐的还是官网主页。然后还有几个不错的中文帖子:

对于配置环境和第一个简单的实例,最好是按照官网的步骤去做。因为在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"));
}
}

特别要注意的是:

  1. 因为class开头用了@RunWith(RobolectricTestRunner.class)标注,所以无法用PowermockTestRunner.class。所以必须加上下面两行:
@Rule
public PowerMockRule rule = new PowerMockRule();

否则会出现各种Powermock无法工作的异常,例如class没有prepare啦,不能mock private函数等等。

  1. 因为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问题。

%23%20Android%u5355%u5143%u6D4B%u8BD5%28%u4E8C%29%20-%20Roboletric%20+%20Powermock%0A@%28myblog%29%5BAndroid%2C%20unittest%5D%0A%0A2016%u5E74%u5728%u505A%u5FAE%u4FE1%u6253%u5370Android%20agent%20app%u7684%u65F6%u5019%uFF0C%u7814%u7A76%u8FC7%u5982%u4F55%u5728%u5F00%u53D1Android%20app%u7684%u65F6%u5019TDD%u3002%u8BE6%u7EC6%u53C2%u8003%u6B64%u7BC7%u535A%u6587%5BAndroid%u5355%u5143%u6D4B%u8BD5%5D%28https%3A//zhougy0717.github.io/2016/10/19/Android%25E5%258D%2595%25E5%2585%2583%25E6%25B5%258B%25E8%25AF%2595/%29%u3002%0A%u5F53%u65F6%u7684%u6700%u7EC8%u65B9%u6848%u662F%uFF1APowermock%20+%20Mockito%20+%20JUnit%u505A%5Blocal%20test%5D%28https%3A//developer.android.com/training/testing/unit-testing/local-unit-tests.html%29%u3002%u56E0%u4E3A%u8981Mock%u5404%u79CDAndroid%20SDK%u4E2D%u7684%u7C7B%uFF0C%u6240%u4EE5%u6709%u4E00%u79CD%u88AB%u65E0%u529B%u63A7%u5236%u7684%u6050%u60E7%u3002%u6240%u4EE5%u5728%u505ANiwatori%u7684%u65F6%u5019%uFF0C%u8FDF%u8FDF%u6CA1%u6709%u5F00%u5C55TDD%u3002%u56E0%u4E3A%u6700%u8FD1%u6DFB%u52A0%u7684%u529F%u80FD%u7A0D%u5FAE%u6709%u70B9%u590D%u6742%u4E86%uFF0C%u611F%u89C9%u5404%u79CD%u4E0D%u987A%uFF0C%u6240%u4EE5%u8003%u8651%u91CD%u65B0%u62FE%u8D77TDD%u3002%u4E8E%u662F%u6709%u4E86%u5404%u79CD%u6298%u817E%uFF0C%u5E76%u9A8C%u8BC1%u4E86%u4E4B%u524D%u65E0%u529B%u7684%u6050%u60E7%u540E%u7684%u8FD9%u4E00%u7BC7%u3002%u4E0D%u8FC7%u7ED3%u679C%u597D%u7684%u662F%u770B%u8D77%u6765%u627E%u5230%u4E86%u4E00%u6761Android%20app%20TDD%u5F00%u53D1%u4E0D%u9519%u7684%u8DEF%u5B50%u3002%0A%0A%u8FD9%u4E00%u5207%u90FD%u8981%u5F52%u529F%u4E8E%0A%23%23%20Roboletric%0A%u8D77%u521D%u7684%u76F8%u8BC6%u8FD8%u662F%u5728%5BAndroid%20developer%u5B98%u7F51%u5173%u4E8E%u6D4B%u8BD5%u7684%u9875%u9762%5D%28https%3A//developer.android.com/training/testing/fundamentals.html%29%u3002%u7136%u540E%u5728%u641C%u7D22Powermock%u7528%u6CD5%u7684%u65F6%u5019%uFF0C%u770B%u5230%u5F88%u591A%u5E16%u5B50%u90FD%u63D0%u5230%u4E86roboletric%u3002%u56E0%u4E3A%u8BA4%u8BC6%u5230Mockito%20+%20Powermock%u7684%u5F31%u70B9%28%u5982%u4E0B%29%uFF0C%u6240%u4EE5%u51B3%u5B9A%u8F6C%u5411roboletric%u3002%0A%3E%20Mockito%0A%3E%20-%20%u4E0D%u80FDmock%20final%20class/method%0A%3E%20-%20%u4E0D%u80FDmock%20private/static%0A%0A%3E%20Powermock%0A%3E%20-%20%u9700%u8981%u81EA%u5DF1mock%u5404%u79CDclass%uFF0C%u5F53%u51FD%u6570%u5927%u4E86%u7684%u65F6%u5019%uFF0C%u56E0%u4E3A%u4F1A%u5F15%u7528%u975E%u5E38%u591A%u7684Android%u7C7B%uFF0C%u6240%u4EE5%u4F1A%u8BA9%u4EBA%u611F%u5230%u5F88%u65E0%u529B%0A%0A%u5728%u7F16%u8BD1Android%20app%u7684%u65F6%u5019%uFF0CAndroid%20SDK%u4EE5android.jar%u7684%u5F62%u5F0F%u53C2%u4E0E%u7F16%u8BD1%u3002%u800C%u8FD9%u4E2Aandroid.jar%u4E2D%u7684%u5404%u4E2A%u7C7B%u5E76%u6CA1%u6709%u5177%u4F53%u7684%u5B9E%u73B0%uFF0C%u53EA%u662F%u4E00%u4E2Astub%u3002%u6240%u4EE5%u5982%u679C%u4E0D%u7ECF%u8FC7mock%u5C31%u76F4%u63A5%u5728Unit%20Test%u4E2D%u4F7F%u7528Android%u7C7B%u5C31%u4F1A%u51FA%u73B0%60Error%20java.lang.RuntimeException%3A%20Stub%21%60%u9519%u8BEF%u3002%0ARoboletric%u5C31%u662F%u4E3A%u4E86%u6D88%u9664%u8FD9%u79CD%u9519%u8BEF%u8BDE%u751F%u7684%u3002%u5B83%u4E3A%u6240%u6709%u7684Android%u7C7B%u5B9A%u4E49%u4E86%u53EF%u4EE5%u8DD1%u5728PC%u7AEFJVM%u4E0B%u7684class%u3002%u800C%u4E14%u63D0%u4F9B%u4E86%u5F88%u591A%u989D%u5916%u7684%u529F%u80FD%uFF0C%u65B9%u4FBF%u5F00%u53D1%u8005%u505A%u9694%u79BB%u6D4B%u8BD5%u4EE5%u53CAUI%u65B9%u9762%u7684%u6D4B%u8BD5%u3002%0A%0A%23%23%23%20%u5165%u95E8%0A%u9996%u5148%u63A8%u8350%u7684%u8FD8%u662F%5B%u5B98%u7F51%u4E3B%u9875%5D%28http%3A//robolectric.org/%29%u3002%u7136%u540E%u8FD8%u6709%u51E0%u4E2A%u4E0D%u9519%u7684%u4E2D%u6587%u5E16%u5B50%uFF1A%0A-%20%5BRobolectric%u4F7F%u7528%u6559%u7A0B%5D%28http%3A//www.cnblogs.com/slgkaifa/p/7354609.html%29%3A%20%u53EF%u4EE5%u53C2%u8003Properties%u6587%u4EF6%u4EE5%u53CAActivity%u751F%u547D%u5468%u671F%u90E8%u5206%u3002%0A-%20%5BAndroid%u5355%u5143%u6D4B%u8BD5%u4E4BRobolectric%u6846%u67B6%5D%28https%3A//maxwell-nc.github.io/android/robolectricTest.html%29%3A%20%u53EF%u4EE5%u53C2%u8003Robolectric%u914D%u7F6E%u4EE5%u53CA%u9488%u5BF9%u5404%u79CDAndroid%u7EC4%u4EF6%u7684%u6D4B%u8BD5%u65B9%u6CD5%u90E8%u5206%u3002%0A%0A%u5BF9%u4E8E%u914D%u7F6E%u73AF%u5883%u548C%u7B2C%u4E00%u4E2A%u7B80%u5355%u7684%u5B9E%u4F8B%uFF0C%u6700%u597D%u662F%u6309%u7167%u5B98%u7F51%u7684%u6B65%u9AA4%u53BB%u505A%u3002%u56E0%u4E3A%u5728setup%u7684%u65F6%u5019%uFF0C%u5F80%u5F80%u6709%u5F88%u591A%u7248%u672C%u53F7%u4F9D%u8D56%u8981%u5904%u7406%uFF0C%u5C24%u5176%u662F%u8FD9%u79CD%u51FA%u8EAB%u793E%u533A%u7684%u6846%u67B6%u3002%u5F80%u5F80%u4F9D%u8D56%u5904%u7406%u4E0D%u662F%u7279%u522B%u597D%u3002%0A%0A%23%23%20Powermock%20+%20Roboletric%0A%u7528%u4E0ARoboletric%u4EE5%u540E%uFF0C%u4F60%u4F1A%u53D1%u73B0%u539F%u5148%u9700%u8981%u5404%u79CDmock%u7684%u51FD%u6570%uFF0C%u73B0%u5728%u4E0D%u7528%u600E%u4E48mock%u5C31%u80FD%u7F16%u8FC7%u4E86%uFF0C%u751A%u81F3%u6709%u4E9Blistener%u90FD%u80FD%u8D77%u4F5C%u7528%u3002%u4F46%u662F%u56E0%u4E3ARoboletric%u672C%u8EAB%u53EA%u662F%u63D0%u4F9BAndroid%20SDK%u7684%u526F%u672C%uFF0C%u5BF9%u4E8E%u6D4B%u8BD5%u5E76%u4E0D%u662F%u90A3%u4E48%u5728%u884C%uFF0C%u6216%u8005%u8BF4%u5176%u76EE%u7684%u5E76%u4E0D%u662F%u4E00%u4E2A%u66F4power%u7684mock%u5E93%u3002%u5F53%u6211%u4EEC%u9700%u8981%u4E3A%u67D0%u4E9Bclass%u63D0%u4F9B%u9884%u8BBE%u7684%u529F%u80FD%uFF0C%u5F02%u6216%u9700%u8981%u5BF9%u67D0%u4E9B%u5BF9%u8C61%u8C03%u7528%u6B21%u6570%u8FDB%u884Cverify%u7684%u65F6%u5019%uFF0C%u8FD8%u662F%u9700%u8981powermock%u7684%uFF08%u6216%u8005Mockito%uFF0C%u4F46%u9274%u4E8E%u5176%u529F%u80FD%u5B9E%u5728%u592Alow%uFF0C%u80FD%u7528Powermock%u7684%u5C31%u7528Powermock%u5427%uFF09%u3002%0A%u5165%u95E8%u53C2%u770BRoboletric%20GitHub%20wiki%uFF1A%5BUsing%20PowerMock%5D%28https%3A//github.com/robolectric/robolectric/wiki/Using-PowerMock%29%u3002%u793A%u4F8B%u5982%u4E0B%uFF1A%0A%60%60%60java%0A@RunWith%28RobolectricTestRunner.class%29%0A@Config%28constants%20%3D%20BuildConfig.class%29%0A@PowerMockIgnore%28%7B%20%22org.mockito.*%22%2C%20%22org.robolectric.*%22%2C%20%22android.*%22%20%7D%29%0A@PrepareForTest%28Static.class%29%0Apublic%20class%20DeckardActivityTest%20%7B%0A%0A%20%20%20%20@Rule%0A%20%20%20%20public%20PowerMockRule%20rule%20%3D%20new%20PowerMockRule%28%29%3B%0A%0A%20%20%20%20@Test%0A%20%20%20%20public%20void%20testStaticMocking%28%29%20%7B%0A%20%20%20%20%20%20%20%20PowerMockito.mockStatic%28Static.class%29%3B%0A%20%20%20%20%20%20%20%20Mockito.when%28Static.staticMethod%28%29%29.thenReturn%28%22hello%20mock%22%29%3B%0A%0A%20%20%20%20%20%20%20%20assertTrue%28Static.staticMethod%28%29.equals%28%22hello%20mock%22%29%29%3B%0A%20%20%20%20%7D%0A%7D%0A%60%60%60%0A%u7279%u522B%u8981%u6CE8%u610F%u7684%u662F%uFF1A%0A1.%20%u56E0%u4E3Aclass%u5F00%u5934%u7528%u4E86%60@RunWith%28RobolectricTestRunner.class%29%60%u6807%u6CE8%uFF0C%u6240%u4EE5%u65E0%u6CD5%u7528%60PowermockTestRunner.class%60%u3002%u6240%u4EE5%u5FC5%u987B%u52A0%u4E0A%u4E0B%u9762%u4E24%u884C%uFF1A%0A%60%60%60%0A@Rule%0Apublic%20PowerMockRule%20rule%20%3D%20new%20PowerMockRule%28%29%3B%0A%60%60%60%0A%u5426%u5219%u4F1A%u51FA%u73B0%u5404%u79CDPowermock%u65E0%u6CD5%u5DE5%u4F5C%u7684%u5F02%u5E38%uFF0C%u4F8B%u5982class%u6CA1%u6709prepare%u5566%uFF0C%u4E0D%u80FDmock%20private%u51FD%u6570%u7B49%u7B49%u3002%0A%0A2.%20%u56E0%u4E3ARoboletric%u63D0%u4F9B%u4E86%u5F88%u591AShadow%u7C7B%uFF0C%u800C%u8FD9%u4E9B%u7C7B%u548Cmock%u6709%u51B2%u7A81%u3002%u4F8B%u5982%60Intent%20intent%20%3D%20mock%28Intent.class%29%60%u5C31%u4F1A%u5BFC%u81F4%u8FD9%u4E2A%u83AB%u540D%u5176%u5999%u7684%u9519%u8BEF%u3002%0A%0A%3E%60java.lang.LinkageError%3A%20loader%20constraint%20violation%3A%20loader%20%28instance%20of%20net/bytebuddy/dynamic/loading/MultipleParentClassLoader%29%20previously%20initiated%20loading%20for%20a%20different%20type%20with%20name%20%22org/xmlpull/v1/XmlSerializer%60%0A%0A%u6240%u4EE5%u5C31%u8981%u52A0%u4E0A%60@PowerMockIgnore%60%u3002%u751A%u81F3Ignore%u7684%u5217%u8868%u8981%u66F4%u957F%uFF0C%u4F8B%u5982%uFF1A%0A%60%60%60%0A@PowerMockIgnore%28%7B%20%0A%09%22org.mockito.*%22%2C%20%0A%09%22org.robolectric.*%22%2C%20%0A%09%22android.*%22%2C%0A%09%22javax.xml.*%22%2C%20%0A%09%22org.xml.sax.*%22%2C%20%0A%09%22org.w3c.dom.*%22%2C%20%20%0A%09%22org.springframework.context.*%22%2C%20%0A%09%22org.apache.log4j.*%22%20%0A%7D%29%0A%60%60%60%0A%0A%23%23%20%u8E29%u5751%u5217%u8868%0A%23%23%23%20PowerMockito.whenNew%20is%20not%20working%0A%3EYou%20need%20to%20put%20the%20class%20**where%20the%20constructor%20is%20called**%20into%20the%20@PrepareForTest%20annotation%20instead%20of%20the%20class%20which%20is%20being%20constructed%20-%20see%20Mock%20construction%20of%20new%20objects.%0A%0A%23%23%20%u603B%u7ED3%0A%u7528Roboletric%u6765%u89E3%u51B3Android%20SDK%u95EE%u9898%uFF0C%u7528Powermock+JUnit%u6765%u89E3%u51B3unit%20test%u95EE%u9898%u3002