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))));
- 通过上面两个方法的返回对View进行操作。
onView()
方法会返回一个ViewInteraction
对象,而onData()
方法返回一个DataInteraction
对象。两个对象都有一个perform()
方法,该方法接收变长的ViewAction
对象作为参数,可以连续执行一些的操作,诸如点击、滑动、输入等操作。具体可执行的操作由具体的view决定,查看 ViewActions看看可使用什么操作。 - 在
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
...
}
}