[譯]使用 Espresso 測試一個有序列表

原文: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)在檐薯,你可能會有疑問:TeamTeamAdapter(或我們還沒有集成的 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)系,祝好!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末狱掂,一起剝皮案震驚了整個濱河市演痒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌符欠,老刑警劉巖嫡霞,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異希柿,居然都是意外死亡诊沪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門曾撤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來端姚,“玉大人,你說我怎么就攤上這事挤悉〗ヂ悖” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵装悲,是天一觀的道長昏鹃。 經(jīng)常有香客問我,道長诀诊,這世上最難降的妖魔是什么洞渤? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮属瓣,結果婚禮上载迄,老公的妹妹穿的比我還像新娘。我一直安慰自己抡蛙,他們只是感情好护昧,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著粗截,像睡著了一般惋耙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上熊昌,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天怠晴,我揣著相機與錄音,去河邊找鬼浴捆。 笑死蒜田,一個胖子當著我的面吹牛,可吹牛的內容都是我干的选泻。 我是一名探鬼主播冲粤,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼美莫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了梯捕?” 一聲冷哼從身側響起厢呵,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎傀顾,沒想到半個月后襟铭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡短曾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年寒砖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫉拐。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡哩都,死狀恐怖,靈堂內的尸體忽然破棺而出婉徘,到底是詐尸還是另有隱情漠嵌,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布盖呼,位于F島的核電站儒鹿,受9級特大地震影響,放射性物質發(fā)生泄漏几晤。R本人自食惡果不足惜约炎,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锌仅。 院中可真熱鬧,春花似錦墙贱、人聲如沸热芹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伊脓。三九已至,卻和暖如春魁衙,著一層夾襖步出監(jiān)牢的瞬間报腔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工剖淀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纯蛾,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓纵隔,卻偏偏與公主長得像翻诉,于是被迫代替她去往敵國和親炮姨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,744評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理碰煌,服務發(fā)現(xiàn)舒岸,斷路器,智...
    卡卡羅2017閱讀 134,628評論 18 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,773評論 6 342
  • Instrumentation介紹 Instrumentation是個什么東西芦圾? Instrumentation測...
    打不死的小強qz閱讀 7,765評論 2 39
  • 虞美人·春花秋月何時了 【作者】李煜【朝代】五代 春花秋月何時了蛾派?往事知多少。小樓昨夜又東風个少,故國不堪回首月明中洪乍。...
    丨冰之心丨閱讀 162評論 0 0