首先谈谈,为什么要进行单元测试呢?理论上说,多了单元测试这一环节意味着要付出额外时间,如果开发周期不变,那么编写代码的时间必须相应缩短。这岂不是作为程序猿的我们自己给自己找麻烦,吃力不讨好吗?讲真的,我一开始也有这种想法,怀着半信半疑的态度来做单元测试。后来慢慢地发现,单元测试过程帮助自己找出考虑不周或者意料之外的bug,即时修复。这样一来,去到测试人员手中的app瞬间变得高大上,bug出现率有所降低,减少了二次测试验证所浪费的时间。那么,做单元测试的必要性还是存在的,它可以帮助我们快速定位bug,从而修复bug,提高代码质量。曾经有一次,我无意间看到Jake Wharton这位Android界大神都做单元测试,而且是几乎每个类都进行细节测试。我们有什么理由不进行单元测试呢?目前有AndroidJUnit4、Dagger、Robolectric、Mockito这四个测试框架,现在选择Robolectric来介绍下。
1、在Android Studio的Gradle环境测试
Robolectric相关依赖:
testCompile 'org.robolectric:robolectric:3.0'
testCompile 'org.robolectric:robolectric-annotations:3.0'
testCompile 'org.robolectric:shadows-multidex:3.0'
testCompile 'org.robolectric:shadows-support-v4:3.0'
另外是测试常用的Juint依赖,现在是出到4.12版本:
testCompile 'junit:junit:4.12'
2、在Maven环境中测试
需要在pom.xml文件中添加以下依赖:
org.robolectric
robolectric
3.1.4
test
3、在Eclipse环境测试
需要安装m2e-android插件,点击project -> Run as -> JUnit Test, 然后选择Eclipse JUnit,就可以运行起来。
4、Linux和Mac平台注意要点
打开Edit Configuration,选择Defaults->JUint,修改Working directory为自己指定的工作目录,如下图:
开始工作,需要在src目录下创建test文件夹,与main同级,如下图所示:
接下来看下Robolectric基本测试类:
@RunWith(RobolectricGradleTestRunner.class)
@Config(cOnstants= BuildConfig.class)
public class SampleTest {
@Before
public void setUp(){
}
@Test
public void testSomeThing(){
}
@After
public void tearDown(){
}
}
其中,@RunWith指定在RobolectricGradleTestRunner类运行,@Config指定BuildConfig类作为constants常数,@Before方法里执行初始化操作,@Test执行真正测试,@After则是测试完毕的后续工作。@Before和@After不是非必须的。但是,如果没有添加@Config指定BuildConfig类作为constants常数,会抛出如下错误提示:
值得注意的是,目前Robolectric只支持API21以下版本。如果你在gradle配置targetSdkVersion大于21,会抛出如下错误:
初始化setUp方法里,可以是获取application实例,启动activity,根据id绑定控件等操作。
//获得application实例
application = ShadowApplication.getInstance();
//启动activity
activity = Robolectric.setupActivity(ToastActivity.class);
//断言application与activity不为null
assertNotNull(application);
assertNotNull(activity);
//通过activity找到对应id的控件
btn_toast = (Button) activity.findViewById(R.id.btn_toast);
et_username = (EditText) activity.findViewById(R.id.et_username);
这里的ShadowApplication是shadow for android.os.application,也就是说真实对象是application。而Robolectric.setupActivity方法其实是通过反射得到真正类,并完成初始化操作:
public static T setupActivity(Class activityClass) {
return ActivityController.of(shadowsAdapter, activityClass).setup().get();
}
在setup方法里,完成了onCreate、onStart、onResume等activity生命周期的调用。
public ActivityController setup() {
return create().start().postCreate(null).resume().visible();
}
下面模拟登陆成功的测试, 其中指定正确userName为hello,使用assertThat进行断言下一个activity为非null。
@Test
public void success(){
et_username.setText("hello");
btn_toast.performClick();
assertThat("The Next Activity Should Start", application.getNextStartedActivity(), is(notNullValue()));
Log.e("123456", "Hello, Robolectric!");
}
测试是pass了,但是并没有log打印出来。为什么呢?难道是Robolectric偷工减料,跳过log打印的步骤了?不是这样的,真正原因是Robolectric跑单元测试是在jvm环境运行,而log来自android.util。其实Robolectric也考虑到这边,只需要添加一行代码便可实现转换,让你的log正常输出。
//打印log日志
ShadowLog.stream = System.out;
果然,Robolectric并没有欺骗我们感情,期待的log终于出现了。
好了,Robolectric做单元测试的基本用法已经介绍完毕。希望本文可以帮助大家开启单元测试之旅,尽情享受find bug & fix bug的乐趣。如果有什么问题,可以相互交流,为尽可能扼杀bug于摇篮,防bug于未然,提高代码质量,争取做一名更加出色的码农而奋斗。