官方文檔鏈接:https://developer.android.google.cn/training/testing/unit-testing/index.html
1.前言
單元測試是應(yīng)用程序測試策略中的基本測試。通過對代碼創(chuàng)建和運行單元測試,可以輕松驗證獨立邏輯單元是否正確。每次構(gòu)建后運行單元測試,有助于快速捕獲和修復代碼改變引入的軟件問題。通常以可重復的方式執(zhí)行盡可能小的代碼單元的功能(可能是方法、類或組件)。當需要驗證應(yīng)用程序中指定代碼的邏輯,應(yīng)該構(gòu)建單元測試。例如,正在單元測試一個類,可能會檢查類的狀態(tài)是否正確。代碼單元是獨立測試的,只影響和監(jiān)聽指定單元的改變,模擬框架可被用來隔離單元和它的依賴。
注意:單元測試不適合測試復雜的UI交互事件。應(yīng)該使用UI測試框架,如自動化UI測試中描述的。
為了測試安卓應(yīng)用程序,通常會創(chuàng)建這些類型的自動化單元測試:
- 本地測試:只運行于本地機器的單元測試。這些測試編譯運行于Java虛擬機(JVM)來減少執(zhí)行時間,不依賴安卓框架或者可以使用模擬對象替代依賴項。
- 設(shè)備測試:運行在安卓設(shè)備或模擬器上的單元測試。這些測試可以訪問儀器的信息,例如被測試應(yīng)用程序的上下文,這些不易被模擬對象替代的安卓依賴項。
下面將介紹如何構(gòu)建這些類型的自動化單元測試。
2.本地單元測試
如果單元測試沒有依賴或僅簡單依賴安卓,應(yīng)該在本地開發(fā)機器上運行測試。這種方式有助于避免每次運行測試,都加載目標應(yīng)用程序和單元測試代碼到物理設(shè)備或模擬器,大大減少單元測試的執(zhí)行時間。為配合這種方式,通常使用類似Mockito的模擬框架來滿足所有依賴關(guān)系。
2.1.設(shè)置測試環(huán)境
在Android Studio項目中,必須存儲本地單元測試的源文件到模塊名/src/test/java/目錄(創(chuàng)建新項目時已存在)下。還需要使用JUnit 4框架提供的標準APIs,來配置項目的測試依賴。如果測試需要安卓的依賴配合,類似Mockito庫可以簡化本地單元測試,要了解關(guān)于使用模擬對象的更多信息,請參閱模擬Android依賴項。
在應(yīng)用程序頂層build.gradle文件中(即工程目錄,若僅哪個模塊需要,在該模塊下配置),需要指定這些庫作為依賴:
dependencies {
// Required -- JUnit 4 framework
testCompile 'junit:junit:4.12'
// Optional -- Mockito framework
testCompile 'org.mockito:mockito-core:1.10.19'
}
2.2.創(chuàng)建本地單元測試類
本地單元測試類應(yīng)該寫成JUnit 4測試類。JUnit是最流行和廣泛使用的Java單元測試框架,它最新的版本相比之前,允許以更簡潔和靈活的方式編寫測試。不同于基于JUnit 3的Android單元測試的做法,JUnit 4不需要擴展junit.framework.TestCase類,也不需要為測試方法名稱加test關(guān)鍵字作為前綴,同時不需要使用junit.framework或junit.extensions包中的任何類。
創(chuàng)建基本的JUnit 4測試類(包含一個或多個測試方法的Java類)。每個測試方法以@Test注解開始,且包含用于執(zhí)行和驗證想要測試的組件中單一功能的代碼。下面的例子展示如何實現(xiàn)本地單元測試類,測試方法emailValidator_CorrectEmailSimple_ReturnsTrue驗證被測試的應(yīng)用程序中isValidEmail()方法返回結(jié)果的正確性。
import org.junit.Test;
import java.util.regex.Pattern;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class EmailValidatorTest {
@Test
public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
assertThat(EmailValidator.isValidEmail("name@email.com"), is(true));
}
...
}
為測試應(yīng)用程序中的組件是否返回期望的結(jié)果,使用junit.Assert方法執(zhí)行驗證檢查(或斷言),來比較被測試組件的狀態(tài)與一些期望的值。為了讓測試更具可讀性,可以使用Hamcrest匹配器(例如is()和equalTo()方法)來比較返回的結(jié)果和預期的結(jié)果。
2.3.模擬安卓依賴項
默認情況下,Gradle的安卓插件基于修改過的android.jar庫(不包含任何實際代碼),執(zhí)行本地單元測試。在測試方法中調(diào)用安卓類時會引發(fā)異常,來確保只測試編寫的代碼,不依賴于Android平臺的任何特有行為(當沒有顯式地模擬時)。
可以使用模擬框架模擬代碼中的外部依賴項,從而很容易地按期望的方式測試需要與依賴交互的組件。不僅將單元測試與Android系統(tǒng)的其余部分隔離,同時驗證那些依賴項中的正確方法是否被調(diào)用。支持Java的Mockito模擬框架(1.9.5版本及以上)提供對安卓單元測試的兼容,可以配置模擬對象被調(diào)用時返回一些特定的值。若要使用此框架向本地單元測試添加模擬對象,請遵循以下開發(fā)步驟:
- 按照設(shè)置測試環(huán)境那節(jié)中描述的,在
build.gradle文件中添加對Mockito庫的依賴。 - 在單元測試類的定義之前,加上
@RunWith(MockitoJUnitRunner.class)注解。這個注解告訴Mockito測試運行器去驗證框架的使用是否正確,并且簡化模擬對象的初始化。 - 為安卓依賴項創(chuàng)建模擬對象時,在成員變量聲明前添加
@Mock注解。 - 重寫依賴項的行為,可以通過使用
when()和thenReturn()方法,指定一個條件和當條件滿足時返回的值。
下面的例子展示如何使用模擬的上下文對象創(chuàng)建單元測試。
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.*;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import android.content.SharedPreferences;
@RunWith(MockitoJUnitRunner.class)
public class UnitTestSample {
private static final String FAKE_STRING = "HELLO WORLD";
@Mock
Context mMockContext;
@Test
public void readStringFromContext_LocalizedString() {
// Given a mocked Context injected into the object under test...
when(mMockContext.getString(R.string.hello_word))
.thenReturn(FAKE_STRING);
ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext);
// ...when the string is returned from the object under test...
String result = myObjectUnderTest.getHelloWorldString();
// ...then the result should be the expected one.
assertThat(result, is(FAKE_STRING));
}
}
要了解更多關(guān)于使用Mockito框架,請參閱Mockito API參考和樣例代碼中的SharedPreferencesHelperTest類。
2.4.Error:"Method ... not mocked"
如果運行測試時,調(diào)用了安卓SDK中沒有模擬的API,將會接收到一個此方法沒有被模擬的錯誤,這是因為運行單元測試使用的android.jar文件不包含任何實際代碼(這些APIs僅由設(shè)備上安卓系統(tǒng)鏡像提供)。通過默認引發(fā)異常,來確保只測試編寫的代碼,不依賴于Android平臺的任何特有行為(當沒有顯式地模擬時,例如使用Mockito)。當不希望測試中拋出異常時,可以通過給項目頂層的build.gradle文件(若僅針對模塊,就用模塊下的)中添加如下的配置來改變行為,讓方法能夠返回null或0:
android {
...
testOptions {
unitTests.returnDefaultValues = true
}
}
注意:設(shè)置
returnDefaultValues屬性為true時,應(yīng)當謹慎。以null/0作為返回值會在測試中傳遞,這很難調(diào)試,而且可能會導致失敗的測試通過,所以把它當作最后的手段。
2.5.運行本地單元測試
要運行本地單元測試,請執(zhí)行以下步驟:
- 通過點擊工具欄中同步工程按鈕,確保項目被Gradle同步。
- 用下面的方式之一運行測試:
- 運行單一測試方法,打開
Project窗口,然后右擊一個測試方法并點擊Run選項。 - 運行類中所有測試方法,右擊這個類或測試文件中的方法并點擊
Run選項。 - 運行目錄下所有測試方法,右擊這個目錄并點擊
Run tests選項。
- 運行單一測試方法,打開
Gradle的安卓插件會編譯位于默認目錄(src/test/java/)下的本地單元測試代碼,構(gòu)建一個測試應(yīng)用程序,并且使用默認的測試運行器類來本地執(zhí)行它,然后Android Studio在Run窗口中展示結(jié)果。
3.設(shè)備單元測試
設(shè)備單元測試是運行在物理設(shè)備和模擬器的測試,可以使用安卓框架APIs和支持的APIs,例如安卓測試支持庫。當測試需要訪問設(shè)備信息(例如目標應(yīng)用程序的上下文)或需要安卓框架組件的真正實現(xiàn)(例如Parcelable或SharedPreferences對象)時,才創(chuàng)建設(shè)備單元測試。使用設(shè)備單元測試也有助于減少需要編寫和維護模擬代碼的工作量,同時可以使用模擬框架來模擬任何依賴關(guān)系。
3.1.設(shè)置測試環(huán)境
在Android Studio項目中,必須存儲設(shè)備測試的源文件到模塊名/src/androidTest/java/目錄下,此目錄創(chuàng)建新項目時已存在并包含設(shè)備測試樣例。
首先應(yīng)該下載安卓測試支持庫,它提供為應(yīng)用程序快速構(gòu)建和運行設(shè)備測試代碼的APIs,同時包含JUnit 4測試運行器(AndroidJUnitRunner)和UI功能測試(Espresso和UI Automator)所需的APIs。接著,需要配置工程的安卓測試依賴項,來使用測試支持庫提供的測試運行器和規(guī)定的APIs。為了簡化測試開發(fā),也應(yīng)該包含Hamcrest庫,從而使用它的匹配APIs來創(chuàng)建更靈活的斷言。
在應(yīng)用程序頂層build.gradle文件中(即工程目錄,若僅哪個模塊需要,在該模塊下配置),需要指定這些庫作為依賴:
dependencies {
androidTestCompile 'com.android.support:support-annotations:24.0.0'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'
// Optional -- Hamcrest library
androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
// Optional -- UI testing with Espresso
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
// Optional -- UI testing with UI Automator
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
}
注意:如果構(gòu)建配置的依賴包括
compile注解支持庫和androidTestCompileEspresso核心庫,那么依賴沖突可能會導致構(gòu)建失敗。要解決此問題,按下面的方式更新對Espresso核心庫的依賴:androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' })
為了使用JUnit 4測試類,通過在app模塊下的build.gradle文件中添加以下設(shè)置,確保指定AndroidJUnitRunner作為工程的默認設(shè)備測試運行器:
android {
defaultConfig {
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
3.2.創(chuàng)建設(shè)備單元測試類
設(shè)備單元測試類應(yīng)該寫成JUnit 4測試類。要了解更多關(guān)于創(chuàng)建JUnit 4測試類和使用JUnit 4斷言及注解,請參閱創(chuàng)建本地單元測試類。為創(chuàng)建設(shè)備的JUnit 4測試類,在定義此類之前添加@RunWith(AndroidJUnit4.class)注解,也需要指定安卓測試支持庫提供的AndroidJUnitRunner類為默認測試運行器。下面例子展示如何編寫設(shè)備單元測試,來驗證LogHistory類是否正確實現(xiàn)了Parcelable接口:
import android.os.Parcel;
import android.support.test.runner.AndroidJUnit4;
import android.util.Pair;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class LogHistoryAndroidUnitTest {
public static final String TEST_STRING = "This is a string";
public static final long TEST_LONG = 12345678L;
private LogHistory mLogHistory;
@Before
public void createLogHistory() {
mLogHistory = new LogHistory();
}
@Test
public void logHistory_ParcelableWriteRead() {
// Set up the Parcelable object to send and receive.
mLogHistory.addEntry(TEST_STRING, TEST_LONG);
// Write the data.
Parcel parcel = Parcel.obtain();
mLogHistory.writeToParcel(parcel, mLogHistory.describeContents());
// After you're done with writing, you need to reset the parcel for reading.
parcel.setDataPosition(0);
// Read the data.
LogHistory createdFromParcel = LogHistory.CREATOR.createFromParcel(parcel);
List<Pair<String, Long>> createdFromParcelData = createdFromParcel.getData();
// Verify that the received data is correct.
assertThat(createdFromParcelData.size(), is(1));
assertThat(createdFromParcelData.get(0).first, is(TEST_STRING));
assertThat(createdFromParcelData.get(0).second, is(TEST_LONG));
}
}
3.3.創(chuàng)建測試套件
為組織設(shè)備單元測試的執(zhí)行,可以收集一系列測試類到一個測試套件類中,然后一起運行這些測試。測試套件可以被嵌套,即收集其它測試套件到自己測試套件中,然后一起運行所有的測試類。測試套件包含在測試包中,類似于主應(yīng)用程序包,命名通常以.suite結(jié)尾作為后綴(例如,com.example.android.testing.mysample.suite)。
為單元測試創(chuàng)建測試套件,需導入JUnit中RunWith和Suite類。在套件中添加@RunWith(Suite.class)和@Suite.SuitClasses()注解,并在@Suite.SuitClasses()注解中分別列出測試類或測試套件作為參數(shù)。下面的例子展示,如何實現(xiàn)名為UnitTestSuite的測試套件,它收集并一起運行 CalculatorInstrumentationTest和CalculatorAddParameterizedTest測試類。
import com.example.android.testing.mysample.CalculatorAddParameterizedTest;
import com.example.android.testing.mysample.CalculatorInstrumentationTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
// Runs all unit tests.
@RunWith(Suite.class)
@Suite.SuiteClasses({CalculatorInstrumentationTest.class,
CalculatorAddParameterizedTest.class})
public class UnitTestSuite {}
3.4.運行設(shè)備單元測試
按照下面這些步驟運行設(shè)備測試:
- 通過點擊工具欄中同步工程按鈕,確保項目被Gradle同步。
- 用下面的方式之一運行測試:
- 運行單一測試方法,打開
Project窗口,然后右擊一個測試方法并點擊Run選項。 - 運行類中所有測試方法,右擊這個類或測試文件中的方法并點擊
Run選項。 - 運行目錄下所有測試方法,右擊這個目錄并點擊
Run tests選項。
- 運行單一測試方法,打開
Gradle的安卓插件會編譯位于默認目錄(src/androidTest/java/)下的設(shè)備測試代碼,構(gòu)建一個測試應(yīng)用包和產(chǎn)品應(yīng)用包,安裝到連接的設(shè)備或模擬器上,并且運行測試,隨后Android Studio在Run窗口中展示結(jié)果。
注意:當運行或調(diào)試設(shè)備測試時,Android Studio不注入
Instant Run所需的額外方法,并將功能關(guān)閉。
3.5.在Firebase上運行測試
使用Firebase測試實驗室,可以在谷歌數(shù)據(jù)中心的物理和虛擬設(shè)備中,選擇多款流行安卓設(shè)備和不同配置(地區(qū)、橫豎屏、屏幕尺寸和平臺版本),同時測試應(yīng)用程序。可以從Android Studio或命令行,直接部署應(yīng)用程序到測試實驗室。測試結(jié)果提供日志,并包括應(yīng)用程序失敗的所有詳細信息。
在開始使用Firebase測試實驗室之前,需要做到以下幾點,除非已經(jīng)擁有谷歌賬號和Firebase工程:
- 如果還沒有,創(chuàng)建谷歌賬號。
- 在Firebase控制臺中,點擊
Create New Project選項。
在Spark計劃的每日免費額度內(nèi),使用測試實驗室測試應(yīng)用程序不收取費用。Android Studio提供集成工具,用來配置希望如何部署測試到Firebase測試實驗室。當按照規(guī)定的步驟創(chuàng)建完Firebase工程,就可以創(chuàng)建測試配置和運行測試:
- 在主菜單點擊
Run > Edit Configurations選項。 - 點擊
Add New Configuration選項并選擇Android Tests。 - 在安卓測試配置對話框內(nèi):
- 輸入或選擇測試的詳細信息,例如測試名稱、模塊類型、測試類型和測試類。
- 從
Deployment Target Options功能區(qū)的Target下拉菜單中,選擇Firebase Test Lab Device Matrix選項。 - 如果還沒有登錄,點擊
Connect to Google Cloud Platform,并允許Android Studio訪問自己的賬戶。 - 接著是
Cloud Project,點擊Settings按鈕并從列表中選擇自己的Firebase工程。
- 創(chuàng)建和配置測試矩陣:
- 接著是
Matrix Configuration下拉列表,點擊Open Dialog按鈕。 - 點擊
Add New Configuration (+)。 - 在
Name字段處,輸入新配置的名字。 - 選擇想要測試應(yīng)用程序的設(shè)備、安卓版本、區(qū)域和橫豎屏。Firebase測試實驗室將在選擇的每種組合下測試應(yīng)用程序,并生成測試結(jié)果。
- 點擊
OK保存配置。
- 接著是
- 點擊
Run/Debug Configurations對話框中的OK按鈕退出。 - 通過點擊
Run按鈕運行測試。

當Firebase測試實驗室完整運行了測試,Run窗口將打開并顯示結(jié)果,如下圖所示??赡苄枰c擊Show Passed按鈕來查看所有執(zhí)行過的測試。

也可以通過點擊Run窗口中,顯示在測試執(zhí)行日志開頭的鏈接,到網(wǎng)頁上分析測試。要了解更多關(guān)于網(wǎng)頁展示結(jié)果的分析,請參閱分析Firebase安卓測試實驗室的結(jié)果。
3.6.附加示例代碼
要下載關(guān)于設(shè)備單元測試的示例應(yīng)用程序,請參閱Android ActivityInstrumentation Sample。
4.總結(jié)
單元測試可以說是程序員在開發(fā)時,最常用的自檢技術(shù)。通過它確定業(yè)務(wù)邏輯當中,輸入和輸出的對應(yīng)關(guān)系(與函數(shù)式編程觀點類似,有興趣可以研究),方便開發(fā)人員確定自己編碼的正確性,減少安裝到設(shè)備上調(diào)試的頻率,大大提高了工作效率,希望大家可以重視。