Robolectric使用(四)自定义

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的测试版本。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容