android单元测试-Robolectric

背景

今天我们不谈why,以前写过两篇关于测试的文章, 好处就不多说了, 总之就是为了提高产品的质量,以TDD驱动开发。 今天我们讲讲如何用Robolectric进行android单元测试。
Robolectric

Robolectric is a unit test framework that de-fangs the Android SDK jar so you can test-drive the development of your Android app. Tests run inside the JVM on your workstation in seconds.

配置

这里不多说请参考官方配置

Activity

1
2
3
4
5
6
7
@Before
public void setUp(){
//获取activity
mainActivity = Robolectric.setupActivity(MainActivity.class);
turnBtn = (Button) mainActivity.findViewById(R.id.btn_login);
}

带有@Before注解的函数在该类实例化后,会立即执行,通常用于执行一些初始化的操作,比如构造网络请求和构造Activity。带有test注解的是单元测试的case,由Robolectric执行,这些case本身也是函数,可以在其他函数中调用,因此,case也是可以复用的。每个case都是独立的,case不会互相影响,即便是相互调用也不会存在多线程干扰的问题。

创建Activity

1
2
3
4
5
@Test
public void testActivity(){
assertNotNull(mainActivity);
assertEquals(mainActivity.getTitle(), "Robolectric Sample");
}

Robolectric只提供运行环境,逻辑判断还是需要依赖JUnit中的断言。

Activity生命周期

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testLifecycle() {
ActivityController<MainActivity> activityController = Robolectric.buildActivity(MainActivity.class).create().start();
MainActivity mainActivity = activityController.get();
TextView textView = (TextView) mainActivity.findViewById(R.id.text_lifecycle);
assertEquals("onCreate", textView.getText().toString());
activityController.resume();
assertEquals("onResume", textView.getText().toString());
activityController.destroy();
assertEquals("onDestroy", textView.getText().toString());
}

Robolectric.buildActivity()用于构造Activity,create()函数执行后,该Activity会运行到onCreate周期,resume()则对应onResume周期。由于MainActivity的onCreate、onResume中改变了TextView的内容, 可以通过判断textView的值来进行判断。代码如下:

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void onResume() {
super.onResume();
lifecycleTextView.setText("onResume");
}
@Override
protected void onDestroy() {
super.onDestroy();
lifecycleTextView.setText("onDestroy");
}

Activity跳转

1
2
3
4
5
6
7
@Test
public void testStartActivity(){
turnBtn.performClick();
Intent expectedIntent = new Intent(mainActivity, LoginActivity.class);
Intent actualIntent = ShadowApplication.getInstance().getNextStartedActivity();
assertEquals(expectedIntent, actualIntent);
}

测试广播

广播的测试点可以包含两个方面,一是应用程序是否注册了该广播,二是广播接受者的处理逻辑是否正确,关于逻辑是否正确,可以直接人为的触发onReceive()方法,验证执行后所影响到的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Broadcast Receiver
*/
@Test
public void testBroadcast(){
ShadowApplication shadowApplication = ShadowApplication.getInstance();
String action = "com.example.jason.test.login";
Intent intent = new Intent(action);
intent.putExtra("USERNAME", "jasonim");
//是否注册广播接收者
assertTrue(shadowApplication.hasReceiverForIntent(intent));
MyReceiver myReceiver = new MyReceiver();
myReceiver.onReceive(RuntimeEnvironment.application, intent);
SharedPreferences sharedPreferences = shadowApplication.getSharedPreferences("account", Context.MODE_PRIVATE);
assertEquals("jasonim", sharedPreferences.getString("USERNAME", ""));
}

service的测试和广播类似, 具体实现可以看Github的Demo

Shadow

采用Shadow的方式对Android中的组件进行模拟测试,从而实现Android单元测试。Robolectric针对Android SDK中的对象,提供类很多shadow对象, 如: ShadowActivity、ShadowListView等等。对于一些Robolectirc暂不支持的组件,可以采用自定义Shadow的方式扩展Robolectric的功能。

自定义Shadow

创建User的Shadow对象

1
2
3
4
5
6
7
@Implements(User.class)
public class ShadowUser {
@Implementation
public String getName() {
return "jason";
}
}

其中@Implements是声明Shadow的对象,Shadow还可以修改一些函数的功能,只需要在重载该函数的时候添加@Implementation,这种方式可以有效扩展Robolectric的功能。
还需要自定义TestRunner,将User对象添加它上面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CustonShadowTestRunner extends RobolectricGradleTestRunner {
public CustonShadowTestRunner(Class<?> klass) throws InitializationError {
super(klass);
}
@Override
public InstrumentationConfiguration createClassLoaderConfig() {
InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder();
/**
* 添加shadow的对象
*/
builder.addInstrumentedClass(User.class.getName());
return builder.build();
}
}

看看如何进行使用

1
2
3
4
5
6
7
8
9
10
@Test
public void testCustomShadow(){
User user = new User("jasonim");
//由于自定ShadowUser 重重载getName() 实际上调用ShadowUser的方法
Log.d(TAG, "test log");
assertEquals("jason", user.getName());
ShadowUser shadowUser = (ShadowUser) ShadowExtractor.extract(user);
assertEquals("jason", shadowUser.getName());
}

自定义的Shadow需要在config中声明,声明写法是@Config(shadows=ShadowUser.class)

TODO网络测试

TODO数据测试

总结

总之没有测试代码的代码都是在耍流氓, 当然测试代码不用所有的case都覆盖到,主要代码的主要逻辑和核心代码覆盖就行了, 具体怎么取舍看实际决定。项目就那么几个人,老板又再屁股后面催,那就需要你想办法, 但是不能不写测试代码,哪怕以后补上。本文的Demo在Github上。
地址:https://github.com/jasonim/JasonAndroidSimple/tree/master/robolectricsample

参考

http://www.jianshu.com/p/9d988a2f8ff7

JasonThink wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!