翻譯官方文檔-單元測試

官方文檔鏈接: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.frameworkjunit.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按鈕運行測試。
Config.png

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

Results.png

也可以通過點擊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)試的頻率,大大提高了工作效率,希望大家可以重視。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容