Robolectric使用文档
[email protected] 2016-01-07
在Android模拟器或者手机上运行单元测试是很漫长的。每次编译、部署、启动应用都需要耗时1分钟以上。有没有更好的办法呢?
Robolectric 是一个针对于Android SDK 的单元测试框架,使用它可以测试驱动你的Android应用程序的开发。测试用例只需要在JVM基础上就能运行起来,这节省了大量的时间。使用Robolectric后,你只需要写出类似这样的测试代码:
@RunWith(RobolectricTestRunner.class) public class MyActivityTest { @Test public void clickingButton_shouldChangeResultsViewTest() throws Excepiton { MyActivity activity = Robolectric.setupActivity(MyActivity.class);
Button button = (Button) activity.findViewById(R.id.button); TextView results = (TextView) activity.findViewById(R.id.results);
button.performClick(); assertThat(results.getText().toString()).isEqualTo("Robolectric Rocks!"); } }
|
Robolectric 通过重写Android SDK 的实现类,然后被加载到测试工程中,然后让上面的测试代码可以在JVM上运行。
Robolectric 模拟了界面生成,资源加载,还有大量的基于Android设备底层C语言提供的原生方法提供的功能。这可以让测试模拟到跟真实设备一样的大部分事情。如果对于一些具体的SDK方法提供自己的实现,也是非常容易的,所以你可以模拟真实机器上发生的一些出错的条件,出错的行为。
Robolectric在Gradle和Maven上测试运行得很好。
在build.gradle里添加下面一行:
testCompile “org.robolectric:robolectric:3.0” |
在测试用例代码中添加Gradle Test的注解
@RunWith(RobolectricGradleTestRunner.class) @Config(constants = BuildConfig.class) public class SandwichTest { } |
注意,一定要配置constants属性指向BuildConfig.class, 编译系统会自动生成这个类,不用手动创建。Robolectric读取constants配置好的输出路径,Gradle生成项目时会使用到。如果没有这些值,Robolectric就找不到应用的manifest, resources还有assets等资源。
Robolectric支持Android Studio 1.1.0及更高的版本。只需要简单的参照上面Gradle的配置。然后在”Build Variants”选项中下拉选择Unit Tests就可以运行了。
在Gradle的Task列表中选择testDebug运行(第一次运行需要的时间很久)。
创建一个Activity,显示一个登陆界面。
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
|
当用户点击登陆按钮就跳转到欢迎界面
public class WelcomeActivity extends Activity {
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.welcome_activity);
final View button = findViewById(R.id.login); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { startActivity(new Intent(WelcomeActivity.this, LoginActivity.class)); } }); } }
|
我们希望测试的是,当用户点击登陆按钮后,我们启动了正常的intent。由于Robolectric只是一个模拟的单元测试框架,LoginActivity并不会真正的启动,但是我们可以检查是否准确的发出了WelcomActivity的intent。
@RunWith(RobolectricTestRunner.class) public class WelcomeActivityTest {
@Test public void clickingLogin_shouldStartLoginActivity() { WelcomeActivity activity = Robolectric.setupActivity(WelcomeActivity.class); activity.findViewById(R.id.login).performClick();
Intent expectedIntent = new Intent(activity, WelcomeActivity.class); assertThat(Shadows.shadowOf(activity).getNextStartedActivity()).isEqualTo(expectedIntent); } }
|
有几种方法来配置Robolectric的运行时行为。
最简单的配置Robolectric的方法就是使用@Config注解。这个注解可以作用于类和方法,如果在类和方法都同时配置了同一个属性,方法的配置会覆盖类的配置。
基类的注解对于所有的子类都是有效的,所以如果你要在很多的类使用同一个配置,可以创建一个基类,然后把@Config注解移到基类中。
下面的例子展示了更多的配置内容。
Robolectric会使用你在manifest中指定的targetSdkVersion版本来运行测试代码。如果你想测试在其它指定版本的表现,可以通过下面的修改这个SDK版本:
@Config(sdk = Build.VERSION_CODES.JELLY_BEAN) public classSandwichTest { @Config(sdk = Build.VERSION_CODES.KITKAT) public void getSandwich_shouldReturnHamSandwich() { } } |
Robolectric会根据manifest的配置自动帮你创建一个Application类,如果你希望提供一个自己实现的类,可以这样设置:
@Config(application = CustomApplication.class) public class SandwichTest {
@Config(application = CustomApplicationOverride.class) public void getSandwich_shouldReturnHamSandwich() { } } |
Robolectric为Gradle和Maven提供了默认的设置,但是也允许你修改这些资源的路径,包括manifest, resource目录,assets目录。如果你有一个自定义的生成脚本这会非常有用。示例:
@Config(manifest = "some/build/path/AndroidManifest.xml") public class SandwichTest {
@Config(manifest = "other/build/path/AndroidManifest.xml") public void getSandwich_shouldReturnHamSandwich() { } } |
默认的,Robolectric会假定你的resouces和assets都是放在目录res和assets中。这些目录都是配置成相对于manifest的相对目录。你也可以在@Config注解中添加resourcesDir和assetsDir选项来修改资源路径。
任何在@Config注解可以配置的属性也可以写成一个全局的properties文件。创建一个文件robolectric.properties,确认在classpath中可以找到。例如:
sdk=18 manifest=some/build/path/AndroidManifest.xml shadows=my.package.ShadowFoo,my.package.ShadowBar |
还有以下的选项可以在properties中配置
robolectric.offline设置true后为离线状态,不会在运行时去下载jar文件
robolectric.dependency.idr在离线状态,提供一个运行时查找依赖文件的目录
robolectric.logging.enable设为true后打开debug日志
在Robolectric 2.2版本以前,创建Activity多数都是直接调用构造函数(new MyActivity())然后手动调用生命周期的函数如onCreate()。还有广泛使用的ShadowActivity(例如ShadowActivity.callOnCreate()),这就是现在ActivityController的设计原型。
为了解决调用的混乱,Robolectric API提供了ActivityController,用它来简化创建Activity还有控制Activity的生命周期。
不需要调用onCreate这些函数,ActivityController确保了Activity生命周期的一致性,这包括把Activity显示到Window,还提供了如LayoutInflater这些系统服务。
你不要直接new一个ActivityController,使用Robolectric.buildActivity()来获得一个实例。对于大多数基本的测试用命 ,你只需要一行代码就能初始化一个Activity:
Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class).create().get(); |
这会创建一个MyAwesomeActivity,并且已经调用了onCreate()。
想去检查一些在onCreate()和onResume()之间发生的事件,这很简单:
ActivityController controller = Robolectric.buildActivity(MyAwesomeActivity.class).create().start(); Activity activity = controller.get(); // assert that something hasn't happened activityController.resume(); // assert it happened! |
还有类似的函数包括start(), pause(), stop()和destroy()。所以如果你想测试一个完整的创建周期:
Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class).create().start().resume().visible().get(); |
模拟使用intent来启动一个Activity:
Intent intent = new Intent(Intent.ACTION_VIEW); Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class).withIntent(intent).create().get(); |
还有保存状态等调用:
Bundle savedInstanceState = new Bundle(); Activity activity = Robolectric.buildActivity(MyAwesomeActivity.class) .create() .restoreInstanceState(savedInstanceState) .get(); |
需要更多关于ActivityController的功能可以查看Java Doc文档。
实际上一个Android app, 一个Activity的视图会在onCreate()被调用后一段时间才添加到Window上的,在此之前,Activity的控件是不可见的,这意味着你没法处理任何的点击交互。Activity的控件树在onPostResume()被调用后才添加到设备或者模拟器的Window上。与其在猜测不确定的时候点Activity会被显示,Robolectric给单元测试的开发者提供了直接控制Activity显示的功能。
然而我们什么时候调用它呢?就是当你在Activity内部需要操作控件的交互时,调用类似于Robolectric.clickOn()需要视图是可见的。注意调用顺序是先onCreate()再调用visible()。
为了减少测试时需要依赖的数量,Robolectric对Android的模拟也拆分为很多附加的子模块,只有在Android SDK的基本包里面的类才包含在Robolectric的主模块中。其它的如appcompat或者support 依赖库是作为附加子模拟提供的。下面的表格中列出了可用的附加包:
SDK包 |
Robolectric附加包 |
com.android.support.support-v4 |
org.robolectric:shadows-support-v4 |
com.android.support.multidex |
org.robolectric:shadows-multidex |
com.google.android.gms:play-services |
org.robolectric:shadows-play-services |
com.google.android.maps:maps |
org.robolectric:shadows-maps |
org.apache.httpcomponents:httpclient |
org.robolectric:shadows-httpclient |
注意附加包的功能需要在build.gradle或者pom.xml里添加后才能使用。
参考:http://robolectric.org/getting-started/