天天看點

使用Retrofit和Mockito進行可靠的Android API測試

測試與API互動的HTTP調用是一件令人生厭的複雜事情。測試一個真實的Web伺服器時,一大堆問題随之産生:脆性測試(brittle test,因為網絡或API本身的問題而導緻的測試失敗)、速度減慢測試(slow test,每一次HTTP調用都要花費好幾秒)和不完全測試(“如何觸發一個速率限制越界用例?想一想,我隻希望速率限制會起作用……”)。

像Android這樣的平台HTTP理應是異步調用,問題會變得更加複雜。如果在這些測試組合中添加計時器,那麼你就準備好在測試API調用上認輸吧。

解決這些問題并且練習這些HTTP調用的一個絕妙方法是,使用一個很好的

Mockito (一個Java測試雙庫 double library)通用程式: ArgumentCaptor

ArgumentCaptor與混合測試雙有幾分相似;有點類似存根(stub),也有點類似偵聽程式(spy),但不完全是其中任何一個。可以使用參數捕獲器捕獲并存儲傳給mock/stub的參數。然而這裡真正的亮點是對捕獲的參數進行方法調用,對于像

Retrofit回調

有很大幫助。

譯注:Retrofit是一個Android & Java的類型安全REST用戶端。

有了Retrofit,我們可以發起一個API調用并提供一個回調方法。當伺服器做出響應時,Mockito會使用響應資料執行回調方法。

下面這些代碼使用

Github API 查詢使用者代碼倉庫:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

getApi().repositories(

"swanson"

new

Callback<List<Repository>>() {

@Override

public

void

success(List<Repository> repositories, Response response) {

if

(repositories.isEmpty()) {

displaySadMessage();

}

mAdapter.setRepositories(repositories);

}

@Override

public

void

failure(RetrofitError retrofitError) {

displayErrorMessage();

}

});

這裡有三個我們想要測試的用例:理想路徑(happy path,擷取一些代碼倉庫并把傳遞給擴充卡)、錯誤路徑(error path,向使用者提示伺服器錯誤)、特殊用例(special case, 向使用者提示沒有代碼倉庫錯誤)。

如果你的測試依賴于在真實的API伺服器,那麼第二和第三個用例會很複雜。我了解到最近GitHub有一些DDOS問題,但你肯定不能依賴它們來測試你的錯誤用例!

然而我們可以通過ArgumentCaptor捕獲回調參數,進而完全控制發送的資料。

看一下對理想路徑的測試(我用的是

Robolectri

,建議你也嘗試一下):

Mockito.verify(mockApi).repositories(Mockito.anyString(), cb.capture());

List<Repository> testRepos = 

new

ArrayList<Repository>();

testRepos.add(

new

Repository(

"rails"

"ruby"

new

Owner(

"dhh"

)));

testRepos.add(

new

Repository(

"android"

"java"

new

Owner(

"google"

)));

cb.getValue().success(testRepos, 

null

);

assertThat(activity.getListAdapter()).hasCount(

2

);

captor(cb)捕獲到回調,調用getValue()方法以後,通過success方法向它傳遞一些僞對象(dummy object)。

你可能會感歎“啊哈(原來可以這麼簡單)”。呵呵,如果沒有也沒關系。接下來可以看一下對錯誤路徑的測試:

Mockito.verify(mockApi).repositories(Mockito.anyString(), cb.capture());

cb.getValue().failure(

null

);

assertThat(ShadowToast.getTextOfLatestToast()).contains(

"Failed"

);

像之前一樣,我們捕獲了回調。但是這一次我們調用了failure方法,它模拟了一個API錯誤。如果我們需要更有針對性的錯誤處理(例如:如果傳回狀态是401,就重定向再登陸;如果是500, 彈出一條普通的系統錯誤消息),可以通過建立合适RetrofitError對象作為failure調用的參數。

目前為止,ArgumentCaptor的威力真的很贊。我們完全控制了捕獲到的對象,并且能夠給這些對象設定任意的資料或者觸發任意想要測試的錯誤。

為了讓内容更加豐富,下面是對一個特殊用例的測試:

Mockito.verify(mockApi).repositories(Mockito.anyString(), cb.capture());

List<Repository> noRepos = 

new

ArrayList<Repository>();

cb.getValue().success(noRepos, 

null

);

assertThat(ShadowToast.getTextOfLatestToast()).contains(

"No repos :("

);

assertThat(activity.getListAdapter()).isEmpty();

(你可以在

GitHub

上找到示例的全部源碼和工程檔案)。

有一個特殊細節要注意:如果在聲明捕獲器時使用了Mockito注解,

@Captor

private

ArgumentCaptor<Callback<List<Repository>>> cb;

請確定在設定中的某個地方添加了下面代碼:

MockitoAnnotations.initMocks(

this

);

這種測試方法完全符合書中提到的所有特點:快速、健壯、易于使用。我們還可以通過它很容易地測試項目中很少出現的邊緣情況(會話逾時、伺服器維護、特殊值),確定我們的應用正常運作。

雖然本文示例是專門針對某種棧(Android、Robolectric、Retrofit、Mockito),但是類似的方法幾乎适用于任何應用。