最近在研究安卓单元测试,看到官方推荐用Espresso做UI层面的测试,就简单用了下。虽然Espresso很简单,但是适配到真实项目中还是走了不少弯路,踩了不少坑的。这里记录一下:
- 由于项目开发的比较早,是传统的Eclipse工程转AndroidStudio工程的,因此项目结构还是传统的Eclipse工程结构,主代码都在跟目录的src文件夹下。这种情况其实很难加入测试用例进工程,要知道Android测试分为两种,即Test和AndroidTest,一个是用来测Java的,一个是用来测Android依赖的,例如测UI。除了文件目录不同之外在Gradle中的引用配置也不相同,如果没有配置好就会出现明明引入了第三方包,但是怎么也找不到的情况。而且需要在gradle中手动配置test和AndroidTest的路径,例如:
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
androidTest{
java.srcDirs = ['androidTest/src']
}
test{
java.srcDirs = ['test/src']
}
}
这样配置之后工程就知道test的路径和AndroidTest的路径对应在哪里了。但是即便这样配置没问题,代码也能跑通,之后却会遇到各种各样奇怪的问题。这里不详细描述了,我反正在这里踩了两天的坑之后决定放弃了。于是我把工程整体迁移到了新的AndroidStudio结构下,即主代码在app文件夹下,迁移完成之后关于之前遇到的测试的坑全部解决了。因此建议想做单元测试的并且用Eclipse老版结构工程的尽早修改为新AS结构,可以少走不少弯路!
改为AS标准工程结构之后单元测试的坑是被填了,但是莫名其妙的跑出来性能问题。之前旧版工程开启主页Activity的时候很快,几乎是秒开。新工程开启主页Activity竟然会白屏1~2s,严重的时候2~3s。甚是奇怪。抛开首页实现逻辑问题不谈,仅仅修改工程结构竟然会导致加载性能的退后简直让人捉摸不透。最终的解决办法是UI分阶段延迟加载。
一切准备工作完成之后,本以为踩坑三天的我可以愉快的玩耍一下了,却遇到了各种Espresso水土不服,不能正常运行,Loading速度极慢之后还报错了,说AppNotIdleException。再次吐槽一下,估计国内国外做安卓单元测试的人很少,这方面的资料真是少之又少,而且都是你抄我我抄你不说,绝大多数都是讲的介绍和入门,没有什么实战经验。即便是官方文档也只是简单粗略的介绍一番。
首先推荐看Google官方的demo,地址:https://github.com/googlesamples/android-testing
这玩意跑在手机上贼流畅,而且AndroidStudio中直接支持Record Espresso Test。通常情况下大家直接用AS的RET功能不会遇到问题,即便是遇到问题,通过看Google官方的Demo也能解决了。
但是我就是遇到了AppNotIdleException问题。在网上搜了一圈之后也没找到解决的办法,遇到这个问题的人通常都是由于自己写的测试用例有问题,而我排除了这个原因。最后又折腾了2天准备放弃的时候看到了这个:https://groups.google.com/forum/#!topic/android-test-kit-discuss/s9hsoZ9XdFc
后来查资料才知道原来Espresso为了保证线程安全,会等所有AsyncTask任务全部执行完了之后才执行。如果这个等待时间超过了60s就会报AppNotIdleException。这个时候找到没有执行完的AsyncTask任务就好了。
private void dumpThreads() {
int activeCount = Thread.activeCount();
Thread[] threads = new Thread[activeCount];
Thread.enumerate(threads);
for (Thread thread : threads) {
if("RUNNABLE".equals(thread.getState().toString()) && thread.getName().startsWith("AsyncTask")) {
LogHelper.e("H3c", thread.getName() + ": " + thread.getState() + "=" + "RUNNABLE".equals(thread.getState().toString()));
for (StackTraceElement stackTraceElement : thread.getStackTrace()) {
LogHelper.e("H3c", "VVV:" + stackTraceElement);
}
}
}
}
通过这段代码,可以输出所有正在运行的AsyncTask,通过日志找到没有结束的任务。我跟上面哪个链接中的兄弟一样,遇到的是Facebook创建的某个AsyncTask一直在运行导致的,最终我开了翻墙工具之后一切正常了。
总的来说,就是遇到AppNotIdleException问题之后,先排查代码是否写错了,如果不是代码问题,可能就跟我遇到的是一样的问题了,用上面的方法找到正在运行的AsyncTask,然后结束掉就OK了。
此文献给所有用Espresso踩坑的人。