原文:Testing a sorted list with Espresso
作者:Egor Andreevici
譯者:lovexiaov
Espresso 是一個十分強大的工具殊者,可以用它為 Android 編寫驗收測試狂秘。所謂驗收測試是指正確實現(xiàn)了所有特性(或某些方面的特性)向臀。自動化驗收測試的優(yōu)勢在于簡單的捕捉回歸愿险,這在積極開發(fā)階段和 bug 修復階段很常見碧聪。在你寫完自動化測試之后,查看新的修改是否引入了問題將變得簡單幸撕。太棒啦笛粘!
本文將向你展示如何在你的 Android 工程中設置 Espresso,并會寫一個簡單的驗收測試來檢驗一組英超聯(lián)賽團隊是否按字母順序排序队腐。給自己沖杯咖啡(譯者注:Espresso 是一種咖啡)并系好你的安全帶喲蚕捉!
Espresso 設置
如果你使用 Android Studio 和 Gradle,那么配置 Espresso 對你來說將非常簡單柴淘。你只需要打開 app
模塊下的 build.gradle
文件迫淹,并添加以下依賴:
def APPCOMPAT_VERSION = "23.1.1"
def ESPRESSO_RUNNER_VERSION = "0.4.1"
dependencies {
// dependencies with "compile" scope go here
androidTestCompile "com.android.support:support-annotations:${APPCOMPAT_VERSION}"
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
androidTestCompile "com.android.support.test:runner:${ESPRESSO_RUNNER_VERSION}"
androidTestCompile "com.android.support.test:rules:${ESPRESSO_RUNNER_VERSION}"
}
順便說一下,此工程的完整代碼可托管在GitHub上为严,你可以免費獲取敛熬。
使用 Gradle 同步你的工程,在 Gradle 構建時你可以抿一口咖啡第股。完成配置還有最后一步应民,在 build.gradle
文件的 defaultConfig
語句塊中添加如下一行:
defaultConfig {
// default setup here
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
這樣我們就完成了所有配置,讓我們開始使用 Espresso 編寫驗收測試吧夕吻。
編寫驗收測試
想必你應該注意到了 androidTest
文件夾在 src
目錄下:Espresso 測試通常就寫在這里诲锹。創(chuàng)建一個名為 TeamsActivityTest
的類,然后添加一對注解如下所示:
@RunWitch(AndroidJunit4.class)
@LargeTest
public class TeamsActivityTest {
}
我們在代碼中聲明了將會使用 JUnit 4 來編寫測試涉馅。@LargeTest注解向測試執(zhí)行器指示了該類包含什么類型的測試归园,它經(jīng)常在 Espresso 測試中出現(xiàn)。下面我們將會在測試類中添加以下字段:
@Rule public ActivityTestRule<TeamsActivity> activityTestRule = new ActivityTestRule<>(TeamsActivity.class);
在 JUnit 4 引入之前 Android 測試類通常繼承自 ActivityInstrumentationTestCase2稚矿。使用 JUnit 4 并在測試類中添加被 @Rule
注解的 ActivityTestRule類型的字段就足以描述待測 Activity
如何被啟動了庸诱。查看 ActivityTestRule
的幾個構造方法找到合適測試啟動的那個。
讓我們繼續(xù)在我們的測試類中實現(xiàn)測試用例:
@Test
public void teamsListIsSortedAlphabetically() {
onView(withId(android.R.id.list)).check(matches(isSortedAlphabetically()));
}
onView()
晤揣,withId()
和 matches()
都是框架中的靜態(tài)方法偶翅,所以我建議使用靜態(tài)導入來時測試定義看起來簡潔明了。在 GitHub 中的 示例代碼中查看中學的導入碉渡。
isSortedAlphabetically()
是一個自定義的 Hamcrest 匹配器聚谁,描述了我們想檢查我們的 View
,換句話說滞诺,檢查 android.R.id.list
中的內容是否按字母順序排序形导。下面是匹配器的定義:
private static Matcher<View> isSortedAlphabetically() {
return new TypeSafeMatcher<View>() {
private final List<String> teamNames = new ArrayList<>();
@Override
protected boolean matchesSafely(View item) {
RecyclerView recyclerView = (RecyclerView) item;
TeamsAdapter teamsAdapter = (TeamsAdapter) recyclerView.getAdapter();
teamNames.clear();
teamNames.addAll(extractTeamNames(teamsAdapter.getTeams()));
return Ordering.natural().isOrdered(teamNames);
}
private List<String> extractTeamNames(List<Team> teams) {
List<String> teamNames = new ArrayList<>();
for (Team team : teams) {
teamNames.add(team.name);
}
return teamNames;
}
@Override
public void describeTo(Description description) {
description.appendText("has items sorted alphabetically: " + teamNames);
}
};
}
由于我們知道使用的是 RecyclerView
,所以我們可以安全的轉換 matchesSafely()
的參數(shù)习霹,并取出 TeamsAdapter
以得到其中的數(shù)據(jù)朵耕。我們使用 extractNames()
方法從列表中取出 Team
對象的名稱,然后使用 Guava 的 Ordering 類檢查列表是否正確的排序淋叶。編寫 Hamcrest 匹配器時阎曹,不要忽視 describeTo()
方法,它在測試失敗時非常有用。在我們的 describeTo()
中处嫌,我們簡短的描述了匹配器做了什么并會打印我們保存的數(shù)據(jù):現(xiàn)在栅贴,當測試失敗時,我們將會明確知道數(shù)據(jù)集合是什么樣的并得出測試失敗的原因熏迹。
現(xiàn)在檐薯,你可能會有疑問:Team
和 TeamAdapter
(或我們還沒有集成的 RecyclerView
)來自哪里呢?編寫測試注暗,甚至不編譯是非常好的測試驅動開發(fā)(TDD)方式坛缕。該方式引入了“紅-綠-重構”循環(huán):編寫測試,使它們編譯通過捆昏,重構以提出重復代碼赚楚。我們現(xiàn)在在“紅”階段,接下來讓我們編寫一些代碼進入“綠”階段骗卜。
首先直晨,通過在 app/build.gradle
中添加以下依賴集成 RecyclerView
:
dependencies {
// other "compile" dependencies go here
compile "com.android.support:recyclerview-v7:${APPCOMPAT_VERSION}"
// "androidTest" dependencies are here
}
如果在你的工程中已經(jīng)有了一個 MainActivity
,請將它重命名為 TeamsActivity
,或者直接創(chuàng)建膨俐。TeamsActivity
將使用這個布局。Team
是我們的實體類(POJO)罩句,代碼如下所示:
public class Team {
public final String name;
public final @DrawableRes int logoRes;
public Team(@NonNull String name, @DrawableRes int logoRes) {
this.name = name;
this.logoRes = logoRes;
}
public static final Comparator<Team> BY_NAME_ALPHABETICAL = new Comparator<Team>() {
@Override public int compare(Team lhs, Team rhs) {
return lhs.name.compareTo(rhs.name);
}
};
}
請注意 BY_NAME_ALPHABETICAL
比較器——我們將使用它來按需排序 Team
對象焚刺。
下面是 TeamAdapter
類,簡潔易懂:
public class TeamsAdapter extends RecyclerView.Adapter<TeamsAdapter.ViewHolder> {
private final LayoutInflater layoutInflater;
private final List<Team> teams;
public TeamsAdapter(LayoutInflater layoutInflater) {
this.layoutInflater = layoutInflater;
this.teams = new ArrayList<>();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new ViewHolder(layoutInflater.inflate(R.layout.row_team, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Team team = teams.get(position);
holder.teamLogo.setImageResource(team.logoRes);
holder.teamName.setText(team.name);
}
@Override public int getItemCount() {
return teams.size();
}
public void setTeams(List<Team> teams) {
this.teams.clear();
this.teams.addAll(teams);
notifyItemRangeInserted(0, teams.size());
}
public List<Team> getTeams() {
return Collections.unmodifiableList(teams);
}
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView teamLogo;
TextView teamName;
public ViewHolder(View itemView) {
super(itemView);
teamLogo = (ImageView) itemView.findViewById(R.id.team_logo);
teamName = (TextView) itemView.findViewById(R.id.team_name);
}
}
}
row_team
的布局在這里∶爬茫現(xiàn)在乳愉,讓我們添加代碼來為 TeamActivity
創(chuàng)建 Team
對象并初始化 TeamAdapter
:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView teamsRecyclerView = (RecyclerView) findViewById(android.R.id.list);
teamsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
TeamsAdapter teamsAdapter = new TeamsAdapter(LayoutInflater.from(this));
teamsAdapter.setTeams(createTeams());
teamsRecyclerView.setAdapter(teamsAdapter);
}
private List<Team> createTeams() {
List<Team> teams = new ArrayList<>();
String[] teamNames = getResources().getStringArray(R.array.team_names);
TypedArray teamLogos = getResources().obtainTypedArray(R.array.team_logos);
for (int i = 0; i < teamNames.length; i++) {
Team team = new Team(teamNames[i], teamLogos.getResourceId(i, -1));
teams.add(team);
}
teamLogos.recycle();
Collections.sort(teams, Team.BY_NAME_ALPHABETICAL);
return teams;
}
我們在把列表傳入適配器之前使用 Team.BY_NAME_ALPHABETICAL
適當?shù)倪M行排序。
請通過 GitHub上的示例將代碼補全屯远。
代碼編寫完了蔓姚!現(xiàn)在你可以通過右擊 TeamsActivityTest
類選擇“Run”命令來執(zhí)行測試,也可以在命令行中執(zhí)行如下命令:
./gradlew connectedAndroidTest
測試會執(zhí)行通過慨丐,就算測試失敗坡脐,通常我們也會得到 Espresso 非常有用的輸出信息來幫助我們調試問題。
現(xiàn)在房揭,我們使用 Espresso 編寫了回歸測試备闲,該測試會自動檢查我們已經(jīng)實現(xiàn)的功能是否正常。如上文所說捅暴,GitHub上有完整的示例代碼恬砂。
你有使用 Espresso 編寫測試驗證你的功能嗎?希望能與你交流蓬痒。如果你有任何反饋或發(fā)現(xiàn)文中錯誤泻骤,歡迎留言或直接與我聯(lián)系,祝好!