Build Instrumented Test

Setups

Instrumented Test是基于JUnit的,使用JUnit4的Test Class风格我们可以可以很快的写出Instrumented Test Class。当然在此之前,我们还需要做一些环境的配置工作。包括:

  • 配置 instrumentation runner。这个runner负责执行Instrumented Test Class!
    android {
          defaultConfig {
              ...
              testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
            }
      }
    
  • 配置需要的dependencies,这里将需要的依赖都列出来了,包括基本依赖、UI测试的依赖和其他库。
    dependencies {
      //这个库是为了避免因为主程序依赖了相同库的不同版本导致冲突加入的
      androidTestCompile 'com.android.support:support-annotations:24.0.0'
      //必需的依赖;
      androidTestCompile 'com.android.support.test:runner:0.5'
      //可选依赖,包含Android提供的几个Rule实现,如ActivityTestRule,ServiceTestRule
      androidTestCompile 'com.android.support.test:rules:0.5'
      // 可选-- Hamcrest library,matcher库,可以很方便的构建matcher
      androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
      // 可选-- UI testing with Espresso UI测试时使用
      androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
      // 可选 -- UI testing with UI Automator 跨App UI测试时使用
      androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
    

}


##Test UI
主要是使用Espresso库来进行UI测试。Espresso能够自动test action和target app的UI状态,它会在UI线程Idle的时候去执行test code,避免开发者自己去做同步的工作,提高了测试的可靠性。在进行UI测试时,可能需要在开发者选项中将窗口动画缩放、过渡动画缩放和动画时长调整选项关闭,否则可能会导致不可预料的结果或者直接导致测试失败。Espresso的使用主要分为几个步骤:
1. 使用`onView()`或者`onData()`方法(针对AdapterView)找到对应的view。`onView()`方法接收一个`Matcher<View>`对象作为参数用来在当前的视图层次结构中找到对应的view,找不到或者有多个View满足条件都会抛出异常。可以查看 [ViewMatchers](https://developer.android.com/reference/android/support/test/espresso/matcher/ViewMatchers.html) 看看Espresso提供的matchers。`onData()`主要适用于AdapterView,比如ListView、GridView、Spinner。这些控件可能包含很多的item view,使用`onView()`方法去找,不一定能找到,因为item view可能不在当前视图层次结构中显示。所以espresso提供`onData()`方法使用。该方法接收一个`Matcher<Object>`类型的参数用来匹配对应的Item。其实就是在对应的Adapter上调用`getItem()`方法,用返回的Object对象去匹配那个Matcher对象。找到这个Item,espresso会自动滚动到该item的位置。
```java
onView(withText("Sign-in"));
onView(withId(R.id.button_signin));
onView(allOf(withId(R.id.button_signin), withText("Sign-in")));

onData(allOf(is(instanceOf(Map.class)),
      hasEntry(equalTo(LongListActivity.ROW_TEXT), is(str))));
  1. 通过上面两个方法的返回对View进行操作。onView()方法会返回一个ViewInteraction对象,而onData()方法返回一个DataInteraction对象。两个对象都有一个perform()方法,该方法接收变长的ViewAction对象作为参数,可以连续执行一些的操作,诸如点击、滑动、输入等操作。具体可执行的操作由具体的view决定,查看 ViewActions看看可使用什么操作。
  2. ViewInteraction或者DataInteraction上调用check()方法验证View的状态;该方法接收一个ViewAssertion作为参数,查看 ViewAssertions提供了哪些方法帮助构建ViewAssertion
onView(withId(R.id.my_view))            // withId(R.id.my_view) is a ViewMatcher
        .perform(click())               // click() is a ViewAction
        .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion

Intents Stub

需要添加espresso-intents依赖库。

@Rule //需要声明使用这个Rule,继承于ActivityTestRule,但会在前后做一些Intents Stub的准备和清理工作
public IntentsTestRule<DialerActivity> mActivityRule = new IntentsTestRule<>(DialerActivity.class);
//如果有匹配matcher的Intent请求,则用result响应
//在启动外部某个app,希望得到响应时很有用
intending(Matcher<Intent> matcher).respondWith(ActivityResult result);

//验证当前的正在测试的application发送了一个matcher中指定的Intent
//重载的方法允许验证匹配的Intent发送了多次
intended(Matcher<Intent> matcher)
intended(Matcher<Intent> matcher, times(2));

Test Service

Service的测试需要用到ServiceTestRule,这个rule帮助我们start或者成功bind到service,在结束测试后还能帮助我们stop或者unbind。这里只给出Test Class的代码,完整的代码在这里

@MediumTest
@RunWith(AndroidJUnit4.class)
public class LocalServiceTest {
    @Rule
    public final ServiceTestRule mServiceRule = new ServiceTestRule();

    @Test
    public void testWithBoundService() throws TimeoutException {
        // Create the service Intent.
        Intent serviceIntent =
                new Intent(InstrumentationRegistry.getTargetContext(), LocalService.class);

        // Data can be passed to the service via the Intent.
        serviceIntent.putExtra(LocalService.SEED_KEY, 42L);

        // Bind the service and grab a reference to the binder.
        IBinder binder = mServiceRule.bindService(serviceIntent);

        // Get the reference to the service, or you can call public methods on the binder directly.
        LocalService service = ((LocalService.LocalBinder) binder).getService();

        // Verify that the service is working correctly.
        assertThat(service.getRandomInt(), is(any(Integer.class)));
    }
}

Test Broadcast

Android没有为Broadcast提供类似ServiceTestRule这样的Rule,因为这根本不需要。一般如果需要测试一个Broadcast,则直接创建这个这个receiver的实例,然后调用onReceive()方法,最后验证一些信息。

public class LocalReceiverTest {
    @Test
    public void testOnReceive() throws Exception {
        LocalReceiver localReceiver = new LocalReceiver();
        Intent intent = new Intent();
        intent.putExtra("key", "I Love You!");//onReceive() just saved the key to somewhere
        localReceiver.onReceive(InstrumentationRegistry.getTargetContext(), intent);
        //do some asserts
        assertEquals(getKeyFromSomeWhere(), "I Love You!");
    }  
}

Test ContentProvider

ContentProvider的测试比较特殊,我们需要在独立的测试环境中测试从而不影响真实的用户数据。具体如何测试,Android官方给了详细说明。想要查看例子可以看这里

Test File or DataBase

文件或者数据库的测试需要注意两个测试之间不能相互影响,且不能影响到正常的数据库。这就要求我们自己建立一个测试环境,好在使用RenamingDelegatingContext能够满足上面的要求。RenamingDelegatingContext会将其他操作委托给一个给定的context对象,但在执行数据库/文件相关的操作时,会用一个prefix重命名给定的数据库名或者文件名。所以使用这个context,我们操作的只是一个测试数据库或者文件。

public class MyDatabase extends SQLiteOpenHelper {
    private static final String DATABASE_NAME = "database.db";
    private static final int DATABASE_VERSION = 1;

    public MyDatabase(Context context){
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db){
        // some code
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // some code
    }
}
public class MyDatabaseTest {
    private MyDatabase db;

    @Before
    public void setUp() throws Exception {
        RenamingDelegatingContext context = new RenamingDelegatingContext(InstrumentationRegistry.getTargetContext(), "test_");
        //每次测试时,删除旧的database文件
        context.deleteDatabase("database.db");
        //使用这个context创建database,文件名对应为'test_database.db'
        db = new MyDatabase(context);
    }

    @After
    public void tearDown() throws Exception {
        db.close(); 
    }

    //@Test
    public void testAddEntry(){
        // Here i have my new database wich is not connected to the standard database of the App
        ...
    }
}

UI Automator Test

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

推荐阅读更多精彩内容