Extending Robolectric
Shadow Classes
Robolectric 定义了很多shadow classes用来修改和继承Android Os的类的行为。当一个Android的类被实例化,Robolectric就会去寻找相对应的shadow类,如果它找到了它就会去创建一个shadow对象来关联到android的类。每一次方法被Android的类调用,Robolectric都会确保shadow类的相对应的方法被提前调用。这会被应用到所有的方法,无论是static的还是final的方法。
Adding Functionality
如果Robolectric提供的shadow 类没有做你想要做的,你可能需要为一个单元测试或者一个集成测试改变他们的行为。简单的注册一个类(我们叫他 shadowFoo)然后注解它(@Implements(Foo.class))。你的shadow类可以继承Robolectric的shadows如果你想要的话。让Robolectric知道你的shadow,使用@Config(shadows=ShadowFoo.class)或者创建一个名字是org.robolectric.Config.properties包含这行代码(shadows=my.package.ShadowFoo)来注解你的测试方法或者类。
From Robolectric 2.0 on, the number of shadow classes needed is greatly reduced, because real Android OS code is present. Methods on your shadow class are able to call through to the Android OS code if you like, using Robolectric.directlyOn()
Shadow Classes
Shadow 类通常需要一个无参构造函数,可以让Robolectric框架来实例化它。他们关联到在类声明时使用@Implements注解的类上。通常他们需要一开始就被实现,那些shadow类的实施几乎总是被移除,而且他们的数据成员很难被访问。Shadow类的方法经常既shadow原始类的方法又通过设置返回值促进测试,又提供内部状态的访问,又记录方法调用日志。
Shadow类需要模仿类的继承结构,比如,如果你为ViewGroup实现了shadow,ShadowViewGroup,然后你的shadow类需要继承ViewGroup的超类的shadow , ShadowView。
@Implements(ViewGroup.class)
public class ShadowViewGroup extends ShadowView{
}
Methods
Shadow类实现和Android类具有同样签名的方法,Robolectric将在调用Android类的方法时调用shadow对象的具有同样签名的方法。
假如一个应用指定下面的代码
this.imageView.setImageResource(R.drawable.pivotallabs_logo);
在测试时在Shadow对象中的ShadowImageView#setImageResource(int resId)方法将会被调用。
Shadow方法必须被@Implementation注解标记,Robolectric包含有一个lint测试来帮助确保这个被正确的实施。
@Implements(ImageView.class)
public class ShadowImageView extends ShadowView{
@Implementation
public void setImageResource(int resId){
//implementation here
}
}
在关联的Shdow类中实现原始类中已经定义的方法是很重要的,否则Robolectric的寻找机制将会找不到他们(即使他们已经在Shadow subClass中声明)。比如,方法setEnabled() 已经在View中指定,如果一个setEnabled()方法被在ShadowViewGroup中定义来替代ShadowView,然后在运行时即使当setEnabled()被一个ViewGroup的实例调用,它也将不会被找到。
Shadowing Constructors
一旦一个Shadow对象被实例化,当真实的对象的构造方法被调用的时候Robolectric将会寻找一个叫做constructor并且具有相同参数的方法。
举例,如果应用调用TextView接受Context参数的构造方法:
new TextView(context);
Robolectric将会调用下面的接收Context参数的constrctor方法
@Implements(TextView.class)
public class ShadowTextView{
public void _constructor_(Context context){
this.context =context;
}
}
Getting access to the real instance
有时候,Shadow类可能会需要他们shadowing的对象的引用,用来操作其中的属性,一个Shadow类可以通过声明一个field注解@RealObject来完成。
@Implements(Point.class)
public class ShadowPoint{
@RealObject private Point realPoint;
public void _constructor_(int x,int y){
realPoint.x=x;
realPoint.y=y;
}
}
Robolectric将会在调用Point的真实对象的其他方法之前对realPoing进行设置。
注意真实对象的方法的调用将会被Robolectric拦截并重定向是重要的,在测试代码中并不经常被关注,但是它对Shadow类的实现具有重要的意义。
由于Shadow类的继承结构不能总是反映出和Android类的关联关系,有时候需要通过他们的真实对象的调用,以便Robolectric在运行时将有机会将其转发到正确的基于真实的类对象的shadow类。否则基类的Shadow类的方法将不能访问子类的shadow的方法。
Creating Custom Shadows
自定义shadows是Robolectric的一个特性,允许你在对Android功能进行测试的时候进行有针对性的修改。这可以是捕获的任何事情,简单的方法调用,与测试对象交互插入代码,或者什么都不做。
自定义shadows允许你仅仅在你的一些测试代码中包含shadow功能,而不是在Robolectric源码中添加或者修改一个Shadow。
它也允许你的shadow涉及特定的domain,就像在你的测试类中的domain对象。
Writting a Custom Shadow
自定义Shadows和正常的shadow类是一样的结构。它们在类声明时必须包含@Implements(AndroidClassName.class)注解,你可以使用正常的shadow实现配置,比如shadowing实例方法使用
==@Implementation== 或者shadowing构造方法使用 ==public void constructor(...)==。
@Implements(Bitmap.class){
@RealObject private Bitmap realBitmap;
private int bitmapQuality = -1;
@Implementation
public boolean compress(Bitmap.CompressFormat format,int quality,OutputStream stream){
bitmapQuality=quality;
return realBitmap.compress(format,quality,stream);
}
public int getQuality(){
return bitmapQuality;
}
}
Using a Custom Shadow
自定义Shadows通过在测试类或者测试方法使用@Config注解和使用 ==shadows== 数组属性来和Robolectric获得连接。
使用在上一节提到的MyShadowBitmap类,你需要在测试时注解 ==@Config(shadows=MyShadowBitmap.class)==,
在使用多个自定义shadow时:==@Config(shadows={MyShadowBitmap.class,MyOtherCustomShadow.class})==.
这样使得Robolectric识别和执行代码的时候使用你自定义的shadow。
然而,Robolectric.shadowOf()方法不能工作with自定义shadows,因为它不得不被Robolectric中每个Shadow 类实现。你可以用Robolectric.shadowOf_()代替它并强转返回值到你实现的自定义shadow。
另外,如果你选择对一个已经在Robolectric中shadow过的android类进行shadow,你将会替换这个Robolectric shadow。如果你仍然需要这个ji chu基础shadow的功能,你可以尝试继承这个Robolectric shadow。
Customizing the Test Runner
在有些情况下你想要自定义Robolectric的test runner用来在所有测试运行前或者在每个测试方法运行前执行一些操作。一个很好的例子就是通过一组不同的依赖初始化一个依赖注入框架到你的测试。幸运的是,Robolectric有一个途径来hook测试的生命周期,如果你在AndroidManifest.xml里指定了一个Application类,Robolectric将会自动尝试加载一个你的application类的测试版本。举例:
<application android:name=".FooApplication">
如果你使用了RoboGuice(google的依赖注入框架),你需要初始化这个injector在你的Application类:
public class FooApplication extends Application{
@Override
public void onCreate(){
super.onCreate();
ApplicationModule module = new ApplicationModule();
setBaseApplicationInjector(this,DEFAULT_STAGE,new DefaultRoboModule(this),module);
}
}
你可以指定一个application的测试版本:
public class TestFooApplication extends FooApplication implements TestLifecycleApplication {
@Override
public void onCreate() {
super.onCreate();
TestApplicationModule module = new TestApplicationModule();
setBaseApplicationInjector(this, DEFAULT_STAGE, newDefaultRoboModule(this), module);
}
@Override
public void beforeTest(Method method) {
}
@Override
public void prepareTest(Object test) {
getInjector(this).injectMembers(test);
}
@Override
public void afterTest(Method method) {
}
}
当你测试时常常加载一组不同的bindings时,Robolectric将会加载这个application的测试版本。