Gradle for Android-運行測試

爲了確保任意app或library的質量,自動化測試是很重要的。長期以來,Android開發工具都不支持自動化測試,但是最近,Google投入巨大精力使得開發者更易啓動測試。一些老舊的框架已經被更新,而且一些新的框架也已被添加,確保我們徹底地測試app和library。我們不僅可從AS中運行它們,也可以使用gradle,直接從命令行接口中運行。

本章節中,我們將會探索不同的測試Android app和library的方式。我們也會了解gradle是如何幫助自動化測試的。

本章中,我們會涉及以下主題

  • 單元測試
  • 功能測試
  • 測試覆蓋

單元測試

在項目中有寫的非常好的單元測試不僅僅可以保障質量,也使得檢測新的代碼是否影響到其他功能更加容易。AS和Gradle Android插件已經支持了單元測試,但是使用之前需要配置一些東西。

JUnit

JUnit是一個已經存在超過10年非常流行的單元測試library。它使得書寫測試更加容易而且確保它們都更易被閱讀。但須記住這些特殊的單元測試僅適用於業務邏輯而非與Android SDK關聯的的代碼。

在爲程序寫JUnit測試之前,需要創建一個用於測試的目錄。按照慣例,這個目錄被稱爲test而且應該和主目錄位於同一目錄層次。目錄結構如下所觀:

app
└─── src
        ├─── main
        │      ├─── java
        │      │      └─── com.example.app
        │      └───res
        └─── test
                └─── java
                       └─── com.example.app

然後可以在src/test/java/com.example.app下創建測試類。

爲了利用JUnit的最新特性,使用JUnit版本4。可通過在test的build中添加一個依賴保證這點:

dependencies {
    testCompile 'junit:junit:4.12'
}

注意,此處我們使用testCompile代替compile。使用該配置確保該依賴僅當我們運行測試時纔會構建,而非打包分發app時。使用testCompile添加的依賴將從不會被assemble tasks泰諾健到發佈版本的APK中。

如果在build type或product flavors中有特殊的條件,可能會在特定的build中單獨添加一個測試所用的依賴。例如,如果僅想在付費flavor中添加JUnit測試,可做如下所觀:

dependencies {
    testPaidCompile 'junit:junit:4.12'
}

萬事俱備,是時候開始寫一些測試了。這是一個簡單的關於一個類測試一個添加兩個數字的方法的例子:

import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class LogicTest {
    @Test
    public void addingNegativeNumberShouldSubtract() {
        Logic logic = new Logic();
        assertEquals("6 + -2 must be 4", 4, logic.add(6, -2));
        assertEquals("2 + -5 must be -3", -3, logic.add(2, -5));
    }
}

爲了使用gradle運行所有測試,僅執行gradlew測試即可。如果想在一個確定的build變體中運行測試,簡單地添加變體名稱即可。如果想在debug變體中運行測試,執行gradlewtestDebug。如果測試失敗,gradle會在命令行接口打印錯誤信息。如果所有測試順利運行,gradle會顯示常規的BUILD SUCCESSFUL信息。

一個單獨的失敗測試會導致test task失敗,整個進程都會立刻中止。這意味着萬一出現了失敗,不是所有的task都會被執行。如果想要確保對所有的build變體,整個測試套件被執行,使用continue標籤:

$ gradlew test --continue

也可以通過在相應目錄存儲測試類專門針對某一確定的build類型寫測試。例如,如果想在app的付費版本中測試特定的行爲,把類放到src/testPaid/java/com.example.app.

如果不想運行整個的測試套件,而是針對某個特殊的類的測試,可使用test標籤,如下所觀:

$ gradlew testDebug --tests="*.LogicTest"

執行test task不僅會運行所有的測試,而且也會創建一個test上報。可在app/build/reports/tests/debug/index.xml中發現。該上報使得如果出現了失敗,發現問題更容易,而且在自動執行測試情況下格外有用。gradle將會爲你運行測試的每一個build變體創建一個上報。

如果所有測試都運行成功,單元測試報告如下:

這裏寫圖片描述

也可以在AS中運行測試,這麼做的時候,會在IDE中獲得及時反饋,而且可以點擊失敗的測試導向到相應的代碼。如果測試全部通過,Run工具窗口將會如下:

這裏寫圖片描述

如果想運行包含了Android特定類或資源引用的部分代碼,常規的單元測試還不夠完善。你可能已經嘗試並遭遇到java.lang.RuntimeException: Stub!錯誤。爲了修復這個問題,需要實現你自己的Android SDK中的每一個方法,或使用一個模擬框架。幸運的是,存在幾個library已經照顧到Android SDK。最流行的就是Robolectric,它提供一個容易的方式測試Android功能,而不需要設備或模擬器。

Robolectric

使用Robolectric,可以書寫使用了Android SDK或資源的測試,當還在Java虛擬機內部運行測試的時候。這意味着你不需要一個運行的設備或模擬器利用在測試中的Android資源。因此使得測試app或library中的UI組件的行爲更快。

爲了開始Robolectric,需要添加幾個測試依賴。除了Robolectric自身之外,還需要添加JUnit。如果你想利用支持包,也需要Robolectric影子類使用它:

apply plugin: 'org.robolectric'
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    testCompile 'junit:junit:4.12'
    testCompile'org.robolectric:robolectric:3.0'
    testCompile'org.robolectric:shadows-support:3.0'
}

Robolectric測試類應在src/test/java/com.example.app目錄下創建,就像普通的單元測試一樣。差異在於你現在可以書寫調用了Android類和資源的測試。例如,這個測試驗證了一個確定的TextView的文本變化當點擊一個特地的Button之後。

@RunWith(RobolectricTestRunner.class)
@Config(manifest = "app/src/main/AndroidManifest.xml", sdk = 18)
public class MainActivityTest {
    @Test
    public void clickingButtonShouldChangeText() {
        AppCompatActivity activity = Robolectric.buildActivity
    (MainActivity.class).create().get();
        Button button = (Button)
        activity.findViewById(R.id.button);
        TextView textView = (TextView)
        activity.findViewById(R.id.label);
        button.performClick();
        assertThat(textView.getText().toString(), equalTo
(activity.getString(R.string.hello_robolectric)));
}
}

Robolectric有一些已知的問題在Android Lollipop和兼容包。如果運行出錯提示缺失與兼容包相關的資源,可以修復它。你需要添加添加一個文件到一個稱之爲project.properties的模塊中,並添加如下代碼:

android.library.reference.1=../../build/intermediates/
exploded-aar/com.android.support/appcompat-v7/22.2.0
android.library.reference.2=../../build/intermediates/
exploded-aar/com.android.support/support-v4/22.2.0

可以幫助Robolectric發現兼容包資源

功能測試

功能測試是用來測試app的幾個組件是否按照預期一起工作。例如,可以創建一個功能測試確保點擊一個按鈕打開一個新的Activity。對於Android來說有好幾個功能測試框架,但是最容易開始功能測試的方式就是使用Espresso框架。

Espresso

Google創建Espresso使得開發者書寫功能測試更容易。這個library通過Android支持倉庫被提供,所以你可以使用SDK Manager安裝它。

爲了在設備上運行測試,需要定義一個測試runner。通過測試支持庫,Google提供了AndroidJUnitRunner測試runner,它將幫助你在Android設備上運行JUnit測試類。測試runner將會加載app APK和測試APK到設備上,運行所有的測試,然後構建測試結果報告。

加入你已經下載了測試支持庫,這就是你應該如果建立測試runner:

defaultConfig {
    testInstrumentationRunner
"android.support.test.runner.AndroidJUnitRunner"
}

你也需要建立幾個新的依賴在使用Espresso之前:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    androidTestCompile 'com.android.support.test:runner:0.3'
    androidTestCompile 'com.android.support.test:rules:0.3'
    androidTestCompile
'com.android.support.test.espresso:espresso-core:2.2'
    androidTestCompile
'com.android.support.test.espresso:espresso-contrib:2.2'
}

需要引用測試支持庫和espresso-core啓動Espresso。最後的依賴espresso-contrib是一個具有增補Espresso特性的庫,但不是core庫的一部分。

注意這些依賴使用androidTestCompile配置,代替我們之前使用的testCompile配置。這在單元測試和功能測試之間做了區分。

此時如果你要嘗試運行測試build,可能會報錯:

Error: duplicate files during packaging of APK app-androidTest.apk
    Path in archive: LICENSE.txt
    Origin 1: ...\hamcrest-library-1.1.jar
    Origin 2: ...\junit-dep-4.10.jar

錯誤自身非常具有描述性。gradle因爲一個重複的文件無法完成構建。幸運的是,僅是一個許可描述,所以我們可以從build中把它刪除。錯誤自身也包含了如何去做的信息:

You can ignore those files in your build.gradle:
android {
packagingOptions {
    exclude 'LICENSE.txt'
    }
}

一旦build文件建立,可以開始添加測試。功能測試較之常規單元測試位於一個不同的目錄下。就像依賴配置一樣,需要使用androidTest代替僅使用test,所以對於功能測試來說正確的目錄是src/androidTest/java/com.example.app。這是一個檢查在MainActivity中的TextView的文本是否正確的測試類的的例子:

@RunWith(AndroidJUnit4.class)
@SmallTest
public class TestingEspressoMainActivityTest {
    @Rule
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
    @Test
    public void testHelloWorldIsShown() {
        onView(withText("Hello world!")).check(matches(isDisplayed()));
    }
}

在能使用Espresso測試之前,需要確保你有個設備或模擬器。如果忘記連接設備,嘗試執行測試task將會拋出異常:

Execution failed for task ':app:connectedAndroidTest'.
>com.android.builder.testing.api.DeviceException:
java.lang.RuntimeException: No connected devices!

一旦已經連接上一個設備或啓動了一個模擬器,可以使用gradlewconnectedCheck運行Espresso測試。這個task將會執行connectedAndroidTest在所有已連接的設備上的debug build上運行所有的測試,執行createDebugCoverageReport創建一個測試報告。

你會在app目錄下的build/outputs/reports/androidTests/connected目錄下發現生成的報告。打開index.xml查看報告,如下所觀:

這裏寫圖片描述

功能測試報告展示了測試運行的設備和Android版本。可以同時在多個設備上運行這些測試,所以設備信息使得發現設備或版本特定的bug更容易。

如果想在AS內部獲得測試反饋,建立一個run/debug配置直接從IDE運行測試。一個run/debug配置代表了一系列的run/debug安裝屬性。AS工具欄有個配置選擇器,你可以在那裏選擇你想使用的run/debug配置。

這裏寫圖片描述

爲了建立一個新的配置,通過點擊Edit Configurations打開配置編輯器…然後創建一個新的Android測試配置。選擇模塊和指定instrumentation runner爲AndroidJUnitRunner,如下截圖所示:

這裏寫圖片描述

一旦保存該配置,可以在配置選擇器中選擇,並點擊Run按鈕運行所有測試。

從AS中運行Espresso測試有個警告:測試報告沒有生成。原因是AS執行connectedAndroidTest task代替connectedCheck。而connectedCheck就是生成測試報告的task。

測試範圍

一旦開始爲你的Android項目寫測試,瞭解測試覆蓋率是很好的。有很多Java的測試覆蓋率工具,但Jacoco是最流行的一個。它也是默認添加的,這使得它很容易開始。

Jacoco

使得覆蓋率上報時很容易的事。只需要在當前測試的build type中設置testCoverageEnabled = true。使得debug build type測試覆蓋率如下:

buildTypes {
    debug {
        testCoverageEnabled = true
    }
}

當使能測試覆蓋時,執行gradlew connectedCheck時會創建覆蓋率報告。創建報告自身的是createDebugCoverageReport。雖然沒有記錄,而且當你運行gradlew tasks也不會出現在task列表中,但可能直接運行。然而,因爲createCoverateReport依賴connectedCheck,不能分開執行它們。依賴於connectedCheck也意味着你需要一個已連接的設備或模擬器生成測試覆蓋率報告。

task執行之後,可在app/build/outputs/reports/coverage/debug/index.xml目錄下發現測試報告。每個build變體都有自己的report目錄,因爲每個變體都可以有不同的測試。測試覆蓋率報告如下所觀:

這裏寫圖片描述

報告在類層次展示了覆蓋率的一個漂亮概覽,而且可以點擊獲取更多信息。在最詳細的視圖,可以看到哪一行被測試和哪一行沒被測試,使用一個有用的顏色編碼文件視圖。

如果想指定一個特定的Jacoco版本,簡單地在build type中添加一個Jacoco配置塊,定義版本:

jacoco {
    toolVersion = "0.7.1.201405082137"
}

然而,明確的定義版本不是必須的;不論如何Jacoco將會工作。

總結

本章節中,我們瞭解了測試Android app和library的幾種選項。我們開始着手簡單地單元測試,然後瞭解了更多使用Robolectric的Android特定的測試。然後我們涉及了功能測試並從Espresso開始。最後,我們瞭解了使能測試覆蓋率報告去查看測試套件在哪續被提升。既然知道了如何使用gradle和AS運行整個測試套件,可以生成覆蓋率報告,沒有理由不寫測試的。在第八章我們將會了解更多使用持續集成工具的自動化測試的方式。

下一章節包含了自定義build進程最重要的一個方面:創建自定義task和插件。這一章節也包含了對Groovy的簡單介紹。這不僅當創建task和插件時有幫助,也使得理解gradle如何工作更容易。

發佈了39 篇原創文章 · 獲贊 77 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章