Android 單元測試: 首先,從是什么開始

這是一系列安卓單元測試的文章,目測主要會cover以下的主題:

  1. 什么是單元測試

  2. 為什么要做單元測試

  3. JUnit

  4. Mockito

  5. Robolectric

  6. Dagger2

  7. 一個具體的app例子實踐

  8. 神秘的bonus

什么是單元測試

首先需要介紹一下什么是單元測試。很多人像我一樣,本科并不是計算機專業出身的,如果在職的公司不要求做單元測試的話,可能對這個詞并沒有一個確切的概念。而即使是計算機專業出身,如果畢業以后寫的不多的話,可能對這個詞的含義也不是很清楚。從名字上看,單元測試是為了測試某一個代碼單元而寫的測試代碼。但是什么叫“一個代碼單元”呢?是一個模塊、還是一個類、還是一個方法(函數)呢?不同的人、不同的語言,都有不同的理解。一般的定義,尤其是是在OOP領域,是一個類的一個方法。在此,我們也這樣理解:單元測試,是為了測試某一個類的某一個方法能否正常工作,而寫的測試代碼。我們舉一個例子說明一下,假如你有一個類,定義如下:

public class Calculator {
    public int add(int one, int another) {
        //為了簡單起見,暫不考慮溢出等情況。
        return one + another;
    }
}

那么為了測試這個 Calculator 類的 add() 方法,我們可以寫如下的單元測試代碼:

public class CalculatorTest {
    public void testAdd() throws Exception {
        Calculator calculator = new Calculator();
        int sum = calculator.add(1, 2);
        Assert.assertEquals(3, sum);
    }
}

這里的 CalculatorTest 是 Calculator 對應的測試類。而這里的 testAdd() 就是 add() 這個方法對應的測試方法。所以,寫單元測試,就是給你的每個類的每個public方法寫對于的測試方法。非public方法我們一般是不測試的,雖然可以通過反射等手段去做,但是一般看來,非public方法是這個類的實現細節,我們并不關心,我們只關心某一個public方法的輸入、輸出。

一般來說,一個方法對應的測試方法主要分為3部分,以上面的測試方法為例:

Calculator calculator = new Calculator();
int sum = calculator.add(1, 2);
Assert.assertEquals(3, sum);

一般來說,我們寫單元測試,會用到一些單元測試框架。常見的Java單元測試框架有 JUnitTestNG 等等。在這個系列的文章中,我采用JUnit 4,這也是用的最多的一個測試框架。上面的第三部, Assert.assertEquals(3, sum); 用的就是JUnit里面的驗證結果的方法,最常見的就是調用 Assert 類的一些assert方法。除了上面用到的 assertEquals ,還有 assertTrue , assertNotNull 等等。關于JUnit,我會在后面的系列文章中專門介紹。

如何在一個android project里面運行單元測試

我們知道,在一個android gradle project中,源代碼默認是放在src/main/java下面的。而對應的單元測試代碼則是放在src/test/java下面的,如下圖所示:

其中的package name可以隨意定,很多人喜歡跟src package name保持一致,我個人習慣在最后加上.test后綴,因為AndroidStudio太智能了,經常我需要重命名單元測試的package的時候,AndroidStudio會把src的package也給重命名了。

打開 CalculatorTest ,用鼠標右鍵點擊 testAdd() 方法,選擇Run testAdd(), 如下圖所示:

從圖中你可以看出,你可以按快捷鍵 Ctrl+Shift+R 快速運行,當然,這要求你的光標當前焦點是在這個方法內部的。如果你的焦點是在類內部,而不在某一個測試方法內部,那么 Ctrl+Shift+R 將運行這個測試類的所有測試方法。當然,你也可以通過右鍵點擊測試類名來運行這個測試類里面的所有測試方法。

運行結束以后,你會在底部的“Run”這個tab看到運行的結果,如下圖所示:

除了在AndroidStudio里面運行,你還可以在命令行通過 gradle testDebugUnitTest ,或者是 gradle testReleaseUnitTest ,分別運行debug和release版本的unit testing,這種方式可以一次性運行所有測試類的所有測試方法。 這種方式的運行結果如下圖所示:

每個test case的報告可以在project_root/app/build/reports/tests/debug/index.html 這個xml里面看到。大致如下圖:

 

這篇文章的代碼在 github 上,感興趣的可以clone下來自己試試。

單元測試不是集成測試

這里需要強調一個觀念,那就是單元測試只是測試一個方法單元,它不是測試一整個流程。舉個例子來說,一個Login頁面,上面有兩個輸入框和一個button。兩個輸入框分別用于輸入用戶名和密碼。點擊button以后,有一個 UserManager 會去執行 performlogin 操作,然后將結果返回,更新頁面。

那么我們給這個東西做單元測試的時候,不是測這一整個login流程。這種整個流程的測試:給兩個輸入框設置正確的用戶名和密碼,點擊login button, 最后頁面得到更新。叫做集成測試,而不是單元測試。當然,集成測試也是有他的必要性的,然而這不是我們每個程序員應該花多少精力所在的地方。在這方面,有一個理論叫做 Test Pyramid ,如下圖所示:

Test Pyramid理論基本大意是,單元測試是基礎,是我們應該花絕大多數時間去寫的部分,而集成測試等應該是冰山上面能看見的那一小部分。

為什么是這樣呢?因為集成測試設置起來很麻煩,運行起來很慢,發現的bug少,在保證代碼質量、改善代碼設計方面更起不到任何作用,因此它的重要程度并不是那么高,也無法將它納入我們正常的工作流程中。

而單元測試則剛好相反,它運行速度超快,能發現的bug更多,在開發時能引導更好的代碼設計,在重構時能保證重構的正確性,因此它能保證我們的代碼在一個比較高的質量水平上。同時因為運行速度快,我們很容易把它納入到我們正常的開發流程中。

至于為什么集成測試發現的bug少,而單元測試發現的bug多,這里也稍作解釋,因為集成測試不能測試到其中每個環節的每個方面,某一個集成測試運行正確了,不代表另一個集成測試也能運行正確。而單元測試會比較完整的測試每個單元的各種不同的狀況、臨界條件等等。一般來說,如果每一個環節是對的,那么在很大的概率上,整個流程就是對的。雖然不能保證整個流程100%一定是對的。所以,集成測試需要有,但應該是少量,單元測試是我們應該花重點去做的事情。

那對于這個例子,單元測試是怎么樣的呢?這個請看下一小節。

兩種函數(方法),兩種不同的測試方式

一個類的方法可以分為兩種,一種是有返回值的,另一種是沒有返回值的。對于有返回值的方法,我們要測起來比較容易,就跟上面的 Calculator 例子那樣,輸入相應的參數,得到相應的返回值,然后驗證得到的返回值跟我們預期的值一樣,就好了。

但是沒有返回值的方法,要怎么測試呢?比如說剛剛login的例子,點擊那個按鈕,會執行Activity的 login() 方法,它的定義如下:

public void login() {
    String username = ...//get username from username EditText
    String password = ...//get password from password EditText
    //do other operation like validation, etc
    ...

    mUserManager.performlogin(username, password);
}

這個方法是void的,那么怎么驗證這個方法是正確的呢?其實仔細想想,這個方法也是有輸出的,它的輸出就是,調用了 mUserManager 的 performLogin 方法,同時傳給他兩個參數。所以只要驗證 mUserManager 的 performLogin 方法得到了調用,同時傳給他的參數是正確的,就說明這個方法是能正常工作的。

那怎么樣驗證這個Activity的 login() 方法,會調用 mUserManager 的 performLogin 方法呢?這里涉及到 mock 的概念,在后面的文章關于 Mockito 的使用的時候,會介紹到,這里簡單解釋下,那就是在測試環境下,我們會使用一套mock framework(Mockito),生成一個mock的 UserManager 然后賦值給 mUserManager ,因為這個 mUserManager 是通過mock framework生成的,所以這個mock framework可以驗證它的什么方法被調用了,參數是什么,等等。

小結

上面講述了單元測試的定義,以及與集成測試的區別,一般來說,單元測試不會接觸到數據庫,不會接觸到網絡,不會接觸到一些復雜的外部環境,如果有的話,那可能是你測試的方式有誤,測試的粒度不夠“單元”,希望這篇文章能將這兩者的區別解釋清楚。

來自: 小創

 本文由用戶 RonaRTEST 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!