在這個單元,我們將使用IntelliJ做為開發工具,讓我們的專案可以簡單一點。
IntelliJ一樣可以用來寫java或kotlin,事實上Android Studio可是基於IntelliJ開發出來的工具。Android 的開發者,IntelliJ 是你的練習單元測試的好工具。
下載IntelliJ
開始寫第一個測試。開啟IntelliJ後,新增Project,選擇Gradle,勾選Kotlin。
輸入GroupId、ArtifaceId
GroipId你可以視為PackageId,ArtifaceId則為專案名稱。
專案建立之後,可以看到如下圖的目錄結構。
main => Production code,撰寫產品程式碼的地方。
test => Testing code,撰寫測試程式碼的地方。
開啟buide.gradle,這裡可以看到使用的測試框架是JUnit,在IntelliJ與Android Studio預設都是用JUnit做為單元測試框架。
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12'
}
第一個測試,我們將寫一個加法的function,作為被測試的方法。
首先在 main/kotlin 新增一個class Math,新增加法的function,傳入number1與number2並回傳這兩個數字的計算結果。
class Math {
fun add(number1: Int, number2: Int): Int {
return number1 + number2
}
}
接著寫一個測試來驗證這個功能是否正確。
在目錄的test/kotlin 新增類別MathTest,新增一個function addTest()
在fun上方加上**@Test**的annotation 。JUnit 用@Test標註這個function是一個測試。
這裡我們會去呼叫math.add (1,2),得到的結果放入actual,而expected變數則是預期的結果。
Assert.assertEquals(expected, actual) 來判斷預期與實際的值。
@Test
fun addTest() {
val expected = 3 //預期結果
val actual = Math().add(1, 2) //呼叫被測試的方法
Assert.assertEquals(expected, actual) //驗證預期與結果
}
點下綠色三角型,執行測試
測試結果如下圖,可以看到Tests passed,代表測試通過了。
我們來試一個會驗證失敗的案例,如果我們把加法寫錯了,寫成了減法
fun add(number1: Int, number2: Int): Int {
return number1 - number2 //應為number1 + number2,寫成了number - number2
}
測試出來的結果則會是紅燈,代表測試失敗。
可以從測試結果看出,預期會是3,但實際得到的是-1。
assertEquals裡的expected與actual不能寫相反。如果寫相反了,把expected放前面
Assert.assertEquals(actual, expected)
則會得到
在看錯誤結果時,這兩種解釋可是完全不一樣的意思。從測試結果你會看到Actual是3,但add(number1, number2)實際計算出來的值是-1。將expected與actual寫相反會讓你誤判程式有錯誤的地方。
一般來說,我們是會先寫一個失敗的測試,這個失敗的測試用來驗證你的測試程式是寫對的。
例如我不小心把assertEquals裡面2個參數都寫成了expected
Assert.assertEquals(expected, expected) //把兩個參數都寫成了expected
那麼這會是一個怎麼測都會是綠燈的測試。不管你的Production Code怎麼寫都會是綠燈。
所以最好的方式是先寫一個會失敗的測試,用來確定你的Testing Code不是因為寫錯而永遠都是綠燈。
關於先寫失敗案例,在之後介紹TDD時,我們會再解釋更多。
剛剛的加法案例,透過驗證function的回傳值
是否符合預期。
另外我們也能使用驗證物件的狀態
改變來進行測試
同樣以加法做為範例,但這次要用驗證物件的狀態來寫測試。
新增Class Math2,這個加法的function 不回傳結果,而是將結果存在result
class Math2 {
var result = 0
fun add(number1: Int, number2: Int){
result = number1 + number2 //將加法結果至至result
}
}
新增測試案例,驗證math.result是否符合預期。
@Test
fun addTest() {
val math = Math2()
val expected = 3 //預期結果
math.add(1, 2) //呼叫被測試方法
val actual = math.result //驗證物件屬性
Assert.assertEquals(expected, actual)
}
執行測試,一樣是綠燈,通過測試。
再來一個範例:這個function的功能是傳入2個數字,回傳最小值。
fun minimum(number1: Int, number2: Int): Int {
if (number1 > number2) {
return number2 //最小值為number2
} else {
return number1 //最小值為number1
}
}
可以看到minimun裡,有著條件式的判斷。需要用兩個測試案例來驗證回傳是否正確。
@Test
fun testNumber1LessNumber2_minimumShouldBeNumber1() {
//number1比number2小的案例
val expected = Math().minimum(1,3)
val actual = 1
Assert.assertEquals(expected, actual)
}
@Test
fun testNumber2LessNumber1_minimumShouldBeNumber2() {
//number2比number1小的案例
val expected = Math().minimum(3,1)
val actual = 1
Assert.assertEquals(expected, actual)
}
在Class 點選Run with Coverage,測試並顯示涵蓋率
涵蓋率結果100%,代表你的Production Code都被測試到。
在取最小值的function,我們用了兩個測試來表達測試的情境。
//第一個測試:number比number2小,結果應回傳number1fun testNumber1LessNumber2_minimumShouldBeNumber1()
//第二個測試:number2比number1,結果應回傳number2testNumber2LessNumber1_minimumShouldBeNumber2
避免將function命名為testNumber2_Is_1_Number_Is_2_minimumShouldBe_3
,原因是將太多的細節寫在上面了,測試的方法名稱應描述測試的情境。也就是number2比number1小,最小值應是number1。寫出number1是3,number是1對測試的情境來說並不是太重要。
驗證物件的行為是否符合預期的方法:
1.驗證目標物件的回傳值
2.驗證目標物件的狀態改變
3.呼叫一個不受控制的第三方元件。 (這個我們會在之後講到Mock的時候講到)
範例下載:
https://github.com/evanchen76/TDD_FirstUnitTestSample
你可以從類別按下 command + N (Alt + Insert) ,快速產生測試類別及方法。
出版書:
Android TDD 測試驅動開發:從 UnitTest、TDD 到 DevOps 實踐