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
...
}
}