【玩轉(zhuǎn)Test】開篇-Android test 介紹

不會測試的開發(fā)不是好開發(fā)——魯迅

image

一直以來,關(guān)于如何寫測試代碼的相關(guān)內(nèi)容資源都比較少,之前在優(yōu)達學城看到了這部分的視頻,但由于沒有中文字幕,對有些小伙伴可能不太友好。因此我決定將其整理成系列文章,那么就從認識 test 開始吧

本文內(nèi)容來自 Udacity Advanced Android with Kotlin-Lesson 10-5.1 Testing:Basics

結(jié)構(gòu)

作為 Android 開發(fā)者我們知道在 Android Studio 的 Android 視圖中有三部分代碼

  • app 的邏輯代碼(main source set)
  • androidTest 代碼
  • local test 代碼
image

test 代碼知道所有的 main source set 中的代碼,因此可以測試這些類。但是 app 代碼不知道 test 中的代碼,并且 androidTest 和 test 都不知道對方的存在。事實上,當你構(gòu)建出 apk 并提交應用市場時,測試代碼并沒有包含在內(nèi)

依賴引用

下面標記的依賴 使用了 test 的引用方式 testImplementationandroidTestImplementation

image

注意:這些 test 代碼不會打包到最終的 apk 文件中

testImplementation 引用的 JUnit 依賴只能在 test source set 中使用,這種依賴范圍的限制是 Gradle 實現(xiàn)的

簡單總結(jié)下:

  • 三種 source setsmain,test,androidTest
  • 測試代碼能訪問 app 代碼
  • app 代碼 不能 訪問測試代碼
  • 測試不會被打入到 Apk 中
  • 依賴范圍包括:testImplementationandroidTestImplementation

運行第一個 test

我們打開 test source set ,看到其中有一個 ExampleUnitTest

ExampleUnitTest

可以看到其內(nèi)部只有一個 addition_isCorrect() 方法

有兩個要素使它成為一個 test:

  • 使用了 @Test 注解
  • 它存在于兩個 test source set 之一

有了這兩個要素,這個方法就可以獨立的作為 test 運行

本示例 test 測試的內(nèi)容在第 15 行,它被稱之為斷言(assertion

斷言是 test 的核心內(nèi)容,它檢查你的代碼或者 app 行為是否符合你的預期

本示例中,斷言檢查 4 是否等于 2 + 2

按照規(guī)定,您需要將你的 預期結(jié)果 傳入到 expected 參數(shù)中,將 實際結(jié)果 傳入到 actual 參數(shù)中

@Test 注解和斷言語句都是 JUnit 下的

image

關(guān)于 JUnit 的更詳細的的信息,請移步 官方文檔

讓我們開始運行一下這個 test,右擊該方法,點擊 Run

image

緊接著,Run 窗口會出現(xiàn)

image

可以看到該窗口顯示了 test 的信息,顯示出 test 是否通過以及有多少 test 通過

下面我們嘗試一個 test 不通過的情況,我們加入一個斷言,如下所示

image

這次我們點擊 Run 窗口的綠色按鈕來運行 test

image

我們可以看到,即使只有一個斷言失敗,整個 test 失敗了

窗口指出了預期的結(jié)果為 5,而實際的結(jié)果為 4,并且下邊標記處錯誤發(fā)生在第 15 行,可以看到這的確是個 bug

解決好 bug ,我們再次運行 test 。這次我們使用一個不一樣的方式

image

下面介紹一些其他運行 test 的方式

可以右擊類名選擇 Run 選項

image

也可以在左側(cè)視圖中右擊 test source set 選擇 Run 按鈕,該方法會運行所有 test。在頂部可以切換要運行的 test ,點擊綠色按鈕可以運行。也可切換回 app

androidTest VS test

下面我們來對比一下 androidTesttest

test androidTest
Local Tests Instrumented Tests
Local machine JVM Real or emulated devices
Faster Slower

我們來運行一個 androidTest,可以看到啟動了模擬器

寫一個 test

首先我們針對一個功能來創(chuàng)建 test,如圖所示,存在一個 getForkAndOriginRepoStats() 方法用于獲取 fork 倉庫和原始倉庫的數(shù)據(jù)并返回 StatsResult,其中 StatsResult 第一個參數(shù)為 fork 項目的百分比,第二個參數(shù)為原始項目的百分比。我們調(diào)用 Generate ,選中 test 選項,在彈出框中選擇 JUnit4,點擊 OK 并選擇存放在 local test 中。這樣我們就創(chuàng)建了一個 test

171aa39da9ee94ed.gif

接下來我們編寫 test ??梢钥吹阶詣觿?chuàng)建出的 test 路徑與 app code 中的代碼路徑的包名是對應的。我們先測試項目列表只有一個 item,并且沒有 fork ,然后計算 fork 項目的百分比和原始項目的百分比。理論上講,fork 項目的百分比為 0 ,而原始項目的百分比為 100%,代碼如下圖所示,我們編寫完畢后點擊運行

image

可以看到測試通過

這是一個正常流程,我們還需要測試異常的流程,比如 repos 為 empty list 或者 repos 變量本身為 null

可以看到我們的代碼中沒有針對 list 為 empty 或 null 做判斷,所以導致了空指針,之后我們修改代碼后即可通過測試

事實上,我們上面的編碼流程叫做 Test Driven Development(TDD) 有關(guān) TDD 的更多信息,可以移步 Test-Driven Development on Android with the Android Testing Support Library (Google I/O '17)

讓你的 test 更具可讀性

與寫普通代碼一樣,您需要讓您的 test 代碼更具可讀性,可以從三個方向入手

  • 優(yōu)秀的命名
  • Given/When/Then
  • 借助斷言庫

優(yōu)秀的命名

首先我們來談談命名,我們知道 test 方法使用 @Test 注解標記,理論上方法名可以隨意命名,但隨意的命名會導致可讀性的降低,因此需要一些特定的命名規(guī)范

測試模塊_ 動作或輸入_ 結(jié)果狀態(tài)

例如上面的例子我們的命名為:getForkAndOriginRepoStats_noForked_returnHundredZero

第一部分顯示我們要測試的是 getForkAndOriginRepoStats() 方法,第二部分代表我們需要的是沒有 fork 倉庫的數(shù)據(jù)源,第三部分是結(jié)果的狀態(tài),0%

Given/When/Then

說完了命名我們來談談 Given/When/Then

測試的基本結(jié)構(gòu)是 Given X,When Y,Then Z

還是上面的例子

  • Given 為你的測試邏輯提供數(shù)據(jù)源
  • When 是你的實際操作
  • Then 檢查 test 是否通過
image

借助斷言庫

上面示例最后的斷言代碼讓人看著很別扭,我們可以借助斷言庫來提高這部分的可讀性

// 之前
assertEquals(result.forkPercent, 0f)

// 之后
assertThat(result.forkPercent, `is`(0f))

下面的語句就像人類的一句話,翻譯下來就是 斷言 forkPercent 是 0f

這樣的寫法需要引入一個庫 Hamcrest

testImplementation "org.hamcrest:hamcrest-all:1.3"
image

注意:由于 is 是 kotlin 中的關(guān)鍵字,因此使用 `is` 來轉(zhuǎn)義

常用的斷言庫

測試范圍

測試范圍指一個 test 測試多少代碼

例如自動化測試根據(jù)測試范圍可以分為

  • Unit Tests(單元測試)
  • Integration Tests(組裝測試)
  • End to end Tests(端到端測試)

您的測試策略需要覆蓋到所有的類型

Unit Tests

上面的示例我們已經(jīng)寫過了 Unit Tests

  • 范圍是單個方法或類
  • 幫助查明失敗原因
  • 應該運行的很快,通常是本地測試
  • 低保真度

他們的范圍是單個的方法或類

如果 Unit Tests 失敗了,您知道您的代碼在哪里出了問題。因為它聚焦于很小一段代碼

Unit Tests 也意味著可以快速運行,由于您頻繁地修改代碼會使得它會頻繁的運行,因此需要速度。Unit Tests 通常是本地測試

它們有較低的保真度,因為現(xiàn)實世界您的 app 要執(zhí)行很多代碼而不僅僅是一個方法或者類

Unit Tests 就像檢查一個鏈條的每個環(huán)節(jié)是否能夠正常運行

image

但它不檢查這些環(huán)節(jié)組合在一起是否能夠運行,為此您需要 Integration Tests

Integration Tests

image

Integration Tests 擁有更大的范圍

  • 范圍是幾個類或單個功能
  • 確保幾個類共同運行
  • 可以使用本地測試或機器測試

就像 Integration 這個詞一樣,Integration Tests 整合一些類確保他們組合起來的表現(xiàn)符合預期

構(gòu)建 Integration Tests 的方式是讓他們測試單個功能,就像獲取指定用戶的 Github 倉庫

Unit Tests 相比,Integration Tests 有著更大的范圍,但他們?nèi)赃\行的很快并且有著很好的保真度

根據(jù)具體情況來判斷使用本地測試還是機器測試,例如如果您寫的 Integration Tests 涉及到了 UI 組件,那么您需要使用真機來測試了

End to end Tests

第三種類型是 End to end Tests,該測試將一些列功能組合起來一起運行

image
  • 范圍是 app 的大部分
  • 高保真度
  • 將 app 作為整體來測試
  • 接近真實地使用,應該使用設(shè)備測試

End to end Tests 測試 app 的大部分,它十分接近真實地使用,因此速度上會比較慢

它有著最高的保真度并確保您的應用作為一個整體運行

這些測試應該使用設(shè)備測試

測試比重

推薦的測試比例是 70% 的單元測試,20% 的組裝測試,以及10% 的端到端測試

image

您能否輕松地在各個部分測試您的 app 取決于您的 app 使用的結(jié)構(gòu)

例如,您的應用將所有邏輯都放置在一個 activity 的大的方法中,您可能可以寫出端到端測試,但單元測試和組裝測試則寫不出來

image

一個更好的架構(gòu)應該將應用的邏輯拆分為多個方法和類,這允許每部分可以獨立的測試

image

對于單元測試,您可以測試 ViewModelRepository 以及 DAO

image

對于組裝測試,您可以組合測試 fragmentViewModel ,或者您可以測試整個數(shù)據(jù)庫代碼

image

端到端測試會測試整個應用

image

關(guān)于測試的原理,可移步 官方文檔

test 的 codelab

關(guān)于我

我是 Fly_with24

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

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