對于測試粪狼,大家都不陌生纺念,但是我相信還是有部分開發(fā)覺得測試工作和自己沒有直接關系拷泽、測試工作是測試工程師的事。慚愧的說杉武,本人也是很長一段時間內(nèi)沒真正理解“測試”這件事兒,之前呆過的幾家公司都沒有真正的“測試工程師”辙售,確切的說轻抱,是沒有會寫代碼的測試工程師,基本上都是手動測試旦部,然后輸出報告祈搜,測試無需懂技術,我相信國內(nèi)很多公司都是這樣志鹃,特別是 App 端的測試夭问,很少有白盒測試的。這篇要說的東西不多曹铃,主要來說說單元測試缰趋,由于本人也是最近才開始實踐,文章拋磚引玉陕见,如果有說得不到位的地方秘血,希望讀者給予指正。
0. 提綱
- 0.1 什么是單元測試
- 0.2 單元測試的意義
- 0.3 Android 開發(fā)中進行單元測試
- 0.3.1 AndroidStudio 中配置
- 0.4 開源框架
- 0.5 MVP 架構中如何寫單元測試
- 0.6 例子
1. 什么是單元測試
我們先來做這么一個體驗评甜。假設現(xiàn)在我們有個開發(fā)者要完成某個功能的開發(fā)灰粮,現(xiàn)在我們從電腦屏幕的視角來觀察他接下來的一序列行為:
他創(chuàng)建了一個類:A。
光標在同一個地方閃爍了好久忍坷,他應該在思考 ....
他在 A 寫了一個方法 a粘舟。
接著寫了方法b、c佩研、d柑肴。
寫的有點多了,滾動條來回滑動旬薯,他在閱讀代碼梳理邏輯晰骑。
他打開了啟動器,運行了A绊序。
運行報錯了硕舆。
他繼續(xù)修改方法。
再次打開啟動器骤公,運行了A抚官。
運行成功了,他收獲了滿足感淋样。
......
在這個觀察中耗式,我們看到他每次啟動運行 A 所做的事其實就是“測試”,所以單元測試這件事其實是每個開發(fā)都做得最多的一件事,只是怎么讓這件事變得更高效刊咳、更有意義是接下來要講的彪见。最后給出一個官方性的定義:單元測試又稱為模塊測試, 是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工作。 程序單元是應用的最小可測試部件娱挨。
2. 單元測試的意義
我們先來看看一張圖:
這張圖叫做測試金字塔余指,單元測試處于開發(fā)環(huán)節(jié)比較早期,粒度也比較小跷坝,也是最重要的一個環(huán)節(jié)酵镜,對整體測試的效率至關重要,這部分做好了柴钻,后期的測試才會更有效淮韭,試想如果整棟樓建好了才發(fā)現(xiàn)磚頭質(zhì)量有問題,那是多么糟糕的事贴届;單元測試就像對每個螺絲釘測試一樣靠粪,生產(chǎn)螺絲釘?shù)臅r候發(fā)現(xiàn)螺絲釘有問題很容易解決,如果在機器運轉(zhuǎn)故障的時候去定位是不是螺絲釘毫蚓、具體是那顆螺絲釘就難了占键。單元測試的意義可以總結如下幾點:
- 代碼質(zhì)量的基礎保障。
- 單元測試元潘,模塊區(qū)間比較小畔乙,更容易發(fā)現(xiàn)問題,發(fā)現(xiàn)問題更容易定位翩概。
- 為后期集成測試節(jié)省了很多時間牲距。
- 單元測試是對程序結構的一個觀察者視角審視,有利于我們提高代碼的整體設計能力钥庇。
3. Android 開發(fā)中進行單元測試
3.1 AndroidStudio 中配置
Android 為我們提供了兩種跑測試的方式:
- Unit Tests
- Android Instarumentaion Tests
3.1.1 Unit Tests
Unit Tests 這種方式嗅虏,跑的測試代碼運行在本機 JVM 上,不需要編譯Apk 上沐,不需要 Android 設備的支持,速度相對快楞艾,當然参咙,測試的對象不能含有 Android 的 API,否則運行時會報錯硫眯。這個模式下蕴侧,測試代碼的默認路徑是 test/java。單元測試的基礎框架是 Junit两入,需要配置依賴:
testCompile "junit:junit:4.12"
然后我們來寫一個被測試類:Calculator.java
public class Calculator {
public int add(int a, int b) {
return a - b;
}
}
把Test Artifact 切換到 Unit Tests 模式净宵,然后我們創(chuàng)建一個針對這個類的測試:CalculatorTest.java
選擇 create new test
public class CalculatorTest {
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void testAdd() throws Exception {
}
}
自動生成了選擇的測試方法,@Before、 @After择葡、@Test 這幾個注解來自 Junit 的 API紧武,@Before 標記的方法,在測試用例啟動時最先執(zhí)行敏储,然后執(zhí)行 @Test 標記的方法阻星,最后執(zhí)行 @After 標記的方法。一般會在 @Before 的方法中做初始化工作已添,例如:創(chuàng)建被測試的對象妥箕, @Test 標記的就是我們要測試的方法,在其中執(zhí)行驗證方法更舞。完善我們的測試代碼:
public class CalculatorTest {
private Calculator calculator;
@Before
public void setUp() throws Exception {
calculator = new Calculator();
}
@After
public void tearDown() throws Exception {
}
@Test
public void testAdd() throws Exception {
// 驗證方法執(zhí)行結果畦幢,1+1 是否等于 2
assertEquals(2, calculator.add(1 , 1));
}
}
然后在這個類上右鍵,我們就可以運行這個測試類了缆蝉。
3.1.1 Android Instarumentaion Tests
Android Instarumentaion Tests 包的測試代碼運行在 Android 設備上宇葱,速度相對慢一些。測試代碼默認路徑是 androidTest/java返奉。為了讓測試能跑在設備上贝搁,需要 AndroidJUnitRunner 的支持:
defaultConfig {
applicationId "com.dalimao.demoforunittest"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
// support Instarumentaion Tests
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
}
配置依賴:
androidTestCompile "com.android.support.test:runner:0.4.1"
同時也離不開 Junit 的支持,配置依賴:
androidTestCompile "junit:junit:4.12"
創(chuàng)建測試和 Unit Tests 的一樣芽偏,運行的時候需要連接模擬器或手機雷逆。
4 開源框架
測試框架很多,這里只簡單說說比較流行的組合以及本例子中要使用的框架:
- Junit 是最簡單最基本的 Java 單元測試框架污尉。
- Mockito 強大的 Java 測試框架膀哲,強大之處在于能很方便的模擬對象,改變對象行為被碗,同時提供了很多驗證API某宪。
- <font color = gray>espresso</font>:google 提供的 U I測試框架。
5. MVP 架構中如何寫單元測試
理解了 AndroidStudio 中如何寫最基本的單元測試锐朴,再結合幾個開源框架兴喂,我們可以來寫一些更為貼近實際開發(fā)的測試用例了。 目前 MVP 是 Android 項目的比較流行的架構模式焚志,它的優(yōu)點在代碼的易測性
上也得到了極好的體現(xiàn)衣迷,對 MVP 不熟悉的考驗參考我之前的幾篇博客:
對于 MVP 個層次我們怎么來寫測試代碼? 先來看看框架的選型:
View層
View的測試自然依賴于Android環(huán)境酱酬,同時需要模擬U交互的能力壶谒,使用 Espresso + JUnit,Espresso用于模擬和驗證各種各樣的UI操作膳沽,使用 Android Instarumentaion Tests 運行汗菜,代碼存放于AndroidTest中让禀。
Presenter層
Presenter 上對UI,下對 Model 都是接口抽象陨界,控制流程邏輯巡揍,不涉及 Android API,用 Junit + Mockito 測試即可普碎,使用 Unit Tests 運行吼肥,代碼存放于test中。
Model層
負責數(shù)據(jù)的存取麻车,數(shù)據(jù)可能來自于網(wǎng)絡缀皱、數(shù)據(jù)庫和內(nèi)存,如果涉及設備相關的API动猬、依賴Android系統(tǒng)環(huán)境啤斗,則使用 Android Instarumentaion Tests 運行,代碼存放于androidTest中赁咙;反之可以Junit+Mockito钮莲,使用 Unit Tests 運行,代碼存放于test中彼水。
6. 例子
6.1 場景介紹
這是一個簡單的場景崔拥,一個游戲列表頁面。對于列表頁面凤覆,通常都會有緩存邏輯链瓦,加載數(shù)據(jù)時會優(yōu)先去加載本地數(shù)據(jù),如果沒有盯桦,則加載遠端數(shù)據(jù)慈俯。本例使用 MVP 來架構這個簡單的場景(引入了 EventBus 來代替常規(guī)的異步回調(diào),EventBus 極其簡單拥峦,不過不理解也不影響對本章主題的理解)贴膘,然后真對 M 和 P 分別寫了對于的單元測試,本例沒有寫 View 層的單元測試(個人覺得對 View 寫單元測試的必要性不大略号,如果你的感受相反刑峡,非常感謝你給我一個說服我的理由),我們先來流程圖:
至此玄柠,建議先你下載 demo 源代碼氛琢,再一起完成下面的理解。認真的人不會找不到源碼地地址
6.2 目錄結構
test/java :該目錄下寫了針對 GamePresenter 的單元測試 GamePresenterTest 和針對 GameManager 的測試類 GameManagerTest (這個跑不過随闪,因為涉及類 Android 的 API)
androidTest / java: 該目錄下寫 針對 GameManager 的測試類 GameManagerAndroidTest (邏輯和 GameManagerTest 一樣)
6.3 例子中涉及 Mockito 的注解和說明
@Mock :對成員變量的注解,被這個注解的變量不用手動實例化骚勘,Mockito 會幫你注入一個模擬的對象(需要在 @Before 標記的方法中先執(zhí)行 MockitoAnnotations.initMocks(this)铐伴。例如:
@Mock
private IGameManager gameManager;
初始化 MockitoAnnotations
public void setUp() throws Exception {
//Log 是 Android 的 API,Unit test 模式下關閉使用
LogUtil.setDebug(false);
// 初始化 @Mock 注解功能,自動注入 @Mock 標記的對象
MockitoAnnotations.initMocks(this);
//gameManager 已經(jīng)實例化
}
verify(T):這是 Mockito API 提供的一個靜態(tài)方法撮奏,用來驗證某個 mock 對象(@Mock 標記的對象)是否執(zhí)行了某個方法,執(zhí)行了多少次当宴。verify(T)畜吊,返回是一個泛型,你傳入什么類型他就返回什么類型的子類給你户矢,重寫的方法中驗證傳入對象對應的方法是否執(zhí)行過玲献。例如:
//驗證 gameListView.showLoading() 是否執(zhí)行
verify(gameListView).showLoading();
when(Object.method()).thenReturn(ReturnObject): 這是 Mockito API 提供的一個靜態(tài)方法,用來改變方法的對象的行為(方法)梯浪,當 Object.method() 調(diào)用的時候返回 ReturnObject 捌年,用來模擬方法的返回值。例如本例中模擬獲取本地數(shù)據(jù)為空:
//模擬本地數(shù)據(jù)為空
when(localGameDataSource.getData()).thenReturn(null);
認真的人不會找不到源碼地地址挂洛,歡迎留言礼预。