本篇詳細(xì)介紹了Espresso的使用方式.
Espresso 測試代碼位置和靜態(tài)導(dǎo)入
Espresso 測試代碼必須放在 app/src/androidTest 目錄下.
為了簡化 Espresso API 的使用, 強(qiáng)烈建議使用以下靜態(tài)導(dǎo)入. 可以允許在沒有類前綴的前提下訪問這些靜態(tài)方法.
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
ViewMatcher 的使用
查找一個 View, 需要使用 onView() 方法和視圖匹配器(view matcher)來選擇正確的 View. 如果你使用的是 AdapterView, 需要使用 onData() 方法而不是 onView()方法. onView() 返回的是一個 ViewInteraction 類型的對象, onData() 返回的是一個 DataInteraction 類型的對象.
表1 中介紹了可用的匹配器.
表 1 Espresso 匹配器
執(zhí)行操作(perform action)
ViewInteraction 和 DataInteraction 可以通過 perform 方法和 ViewActions 類型的對象指定某種操作. ViewActions 類為最常見的操作了提供了相應(yīng)的輔助方法. 像:
- ViewActions.click()
- ViewActions.typeText()
- ViewActions.pressKey()
- ViewActions.clearText()
- ViewActions.replaceText()
perform() 方法還會再次返回一個 ViewInteraction 的對象, 也就是說你可以執(zhí)行更多的操作或者驗證結(jié)果. 而且你也可以通過 perform() 方法的可變參數(shù)來指定多個操作.
驗證測試結(jié)果
調(diào)用 ViewInteraction.check() 方法可以斷言一個 View 的狀態(tài). 這個方法需要一個 ViewAssertion 類型的對象作為參數(shù). ViewAssertion 類提供了用于創(chuàng)建這些對象的輔助方法.
- maches() - Hamcrest 匹配器
- doesNotExist() - 斷言選中的 View 不存在
你也可以使用強(qiáng)大的 Hamcrest 匹配器. 下面給出了部分示例:
onView(withText(startsWith("ABC"))).perform(click());// 1.
onView(withText(endsWith("YYZZ"))).perform(click()); // 2.
onView(withId(R.id.viewId)).
check(matches(withContentDescription(containsString("YYZZ")))); // 3.
onView(withText(equalToIgnoringCase("xxYY"))).perform(click()); // 4.
onView(withText(equalToIgnoringWhiteSpace("XX YY ZZ"))).perform(click()); // 5.
onView(withId(R.id.viewId)).check(matches(withText(not(containsString("YYZZ"))))); // 6.
① 匹配以文本 "ABC" 開頭的 View
② 匹配以文本 "YYZZ" 結(jié)尾的 View
③ 匹配指定 id View 的且檢查文本內(nèi)容包含 "YYZZ"
④ 匹配一個含有指定字符串的 View, 并且該字符串忽略大小寫
⑤ 匹配一個含有指定字符串的 View, 并且該字符串忽略空白字符
⑥ 匹配指定 id View 的且檢查文本內(nèi)容不包含 "YYZZ"
訪問 instrumentation API
通過 InstrumentationRegistry.getTargetContext() 可以訪問到測試應(yīng)用的上下文, 例如你想使用字符串 id 而不是 R.id, 下面的方法可以幫助你實(shí)現(xiàn):
/**
* Created by zhanglulu on 2018/3/6.
*/
@RunWith(AndroidJUnit4.class)
public class EspressoTest2ActivityTest {
@Rule
public ActivityTestRule<EspressoTest2Activity> mRule =
new ActivityTestRule<EspressoTest2Activity>(
EspressoTest2Activity.class);
@Test
public void buttonShouldUpdateText(){
onView(withId(R.id.resultView)).perform(click());
onView(withId(getResourceId("Click"))).check(matches(withText("Done")));
}
/**
* 根據(jù) id 字符串獲取真實(shí) id
* @param idStr
* @return
*/
private static int getResourceId(String idStr) {
Context targetContext = InstrumentationRegistry.getTargetContext();
String packageName = targetContext.getPackageName();
return targetContext.getResources().getIdentifier(idStr, "id", packageName);
}
}
配置Activity的啟動 Intent
如果你給 ActivityTestRule 構(gòu)造方法的第三個參數(shù)指定為 false, 則可以配置啟動 Intent, 并手動啟動測試 Activity.
/**
* Created by zhanglulu on 2018/3/6.
*/
@RunWith(AndroidJUnit4.class)
public class EspressoTest2ActivityTest {
@Rule
public ActivityTestRule<EspressoTest2Activity> mRule =
new ActivityTestRule<EspressoTest2Activity>(
EspressoTest2Activity.class, true, false);
@Test
public void demonstrateIntentPrep() {
Intent intent = new Intent();
intent.putExtra("input", "Test");
mRule.launchActivity(intent);
onView(withId(R.id.resultView)).check(matches(withText("Test")));
}
}
Adapter Views
AdapterView 是一種特殊的組件, 它可以動態(tài)的從 Adapter 中加載數(shù)據(jù). 只有一部分?jǐn)?shù)據(jù)在 View 層次結(jié)構(gòu)中有真實(shí)的 View. onView() 不會為這些數(shù)據(jù)找到相應(yīng)的 View. 而 onData() 可以與 Adapter 進(jìn)行交互, 找到對應(yīng)的 View, 例如 ListView. 下面給出部分示例:
@Test
public void testList() {
// 點(diǎn)擊 ListView 中文本為 "測試 15" 的 item
onData(allOf(is(instanceOf(String.class)), is("測試 30")))
.perform(click());
onData(allOf(is(instanceOf(String.class)), is("測試 1")))
.perform(click());
onData(allOf(is(instanceOf(String.class)), is("測試 49")))
.perform(click());
}
Espresso 測試添加權(quán)限
可以通過 instrumentation API 給你的測試執(zhí)行授予權(quán)限的操作.如下面示例:
@Before
public void grantPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().getUiAutomation().executeShellCommand(
"pm grant " + getInstrumentation().getTargetContext().getPackageName()
+ " android.permission.CALL_PHONE");
}
}
Espresso UI Recorder
Android Studio 在測試菜單欄上提供了一個 Run ? Record Espresso Test 的功能, 它可以記錄下你與應(yīng)用程序的交互并從中創(chuàng)建 Espresso 測試.
點(diǎn)擊運(yùn)行,之后 Android Studio 將記錄下你與應(yīng)用的交互行為,并生成測試代碼.
訪問當(dāng)前的 Activity
你還可以訪問正在進(jìn)行測試的 Activity 對象,并調(diào)用它的方法. 例如,你有下面的 TestConfigureActivity, 并存在一個方法 configureActivity().
public class TestConfigureActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_configure);
}
// 外部調(diào)用
public void configureActivity(String Uri) {
// do something with this
}
public void onClick(View view) {
startActivity(new Intent(this, SecondActivity.class));
}
}
如果你想在測試方法中得到這個 Activity 并且執(zhí)行里面的方法, 你可以通過 ActivityTestRule 來獲取, 示例如下:
@Test
public void useAppContext() throws Exception{
TestConfigureActivity activity = mRule.getActivity();
activity.configureActivity("https://testurl");
//do more
}
并且你也可以重寫 ActivityTestRule 類來配置你的測試 Activity.例如:
@Rule
public ActivityTestRule<TestConfigureActivity> mRule =
new ActivityTestRule<TestConfigureActivity>(TestConfigureActivity.class){
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
//Activity 啟動前做些事情
}
@Override
protected void afterActivityLaunched() {
super.afterActivityLaunched();
//Activity 啟動后做些事情
}
};
你甚至可以拿到前臺的 Activity 的實(shí)例.這里著重說明一下, 你所測試的 Activity 不一定都在前臺顯示, 例如,你從當(dāng)前測試的 Activity (也就是你在 ActivityTestRule 傳入的 Activity) 跳轉(zhuǎn)到另外的一個 Activity, 此時在前臺的 Activity 就是另外的一個 Activity. 這個時候就可以通過 ActivityLifecycleMonitorRegistry 來獲取前臺的 Activity, 示例如下:
@Test
public void navigate() {
onView(withText("Next")).perform(click());
Activity activity = getActivityInstance();
boolean b = (activity instanceof SecondActivity);
assertTrue(b);
// do more
}
public Activity getActivityInstance() {
final Activity[] activity = new Activity[1];
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
Activity currentActivity = null;
Collection resumedActivities =
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);
if (resumedActivities.iterator().hasNext()){
currentActivity = (Activity) resumedActivities.iterator ().next();
activity[0] = currentActivity;
}
});
return activity[0];
}
需要注意的是 ActivityLifecycleMonitorRegistry 并不是標(biāo)準(zhǔn)的 API, 所以可能在后期名稱會發(fā)生變化.
運(yùn)行 Espresso 測試輸出測試報告
- 使用 Android Studio
右擊你的測試類, 并點(diǎn)擊運(yùn)行就 OK 了.
- 使用 Gradle
通過 Gradle 執(zhí)行 connectedCheck Task 可以執(zhí)行測試目錄下所有的測試用例.
執(zhí)行結(jié)束后將會在 app/build/reports 目錄下生成測試報告, 點(diǎn)擊打開 index.html 即可查看.
檢查 Taost
下面是一個點(diǎn)擊 Item ,并檢查 Toast 是否彈出的測試.
@Test
public void testToast() {
onData(allOf(instanceOf(String.class), is("測試 20"))).perform(click());
onView(withText(startsWith("點(diǎn)擊")))
.inRoot(withDecorView(not(is(mRule.getActivity().getWindow().getDecorView()))))
.check(matches(isDisplayed()));
}
通過Espresso Intents模擬Intent
Espresso 也提供了模擬 Intent 的功能. 它可以檢查一個 Activity 是否正常發(fā)出一個正確的 Intent.如果它接收了正確 Intent 則測試通過.
Espresso Intents 通過 com.android.support.test.espresso:espresso-intents 依賴提供.
使用 Espresso Intents 必須使用 IntentTestRule 而不是 ActivityTestRule.下面是一個簡單的示例:
/**
* Created by zhanglulu on 2018/3/7.
*/
@RunWith(AndroidJUnit4.class)
public class TestIntent {
@Rule
public IntentsTestRule<EspressoTest1Activity> mIntentTestRule =
new IntentsTestRule<EspressoTest1Activity>(EspressoTest1Activity.class);
@Test
public void triggerIntentTest() {
onView(withId(R.id.switchActivity)).perform(click());
intended(allOf(
hasAction(Intent.ACTION_CALL),
hasData("123456789"),
toPackage(InstrumentationRegistry.getTargetContext().getPackageName()),
hasExtra("input", "Test")
));
}
}
更多詳細(xì)內(nèi)容請移步: https://developer.android.com/training/testing/espresso/intents.html#matching