iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 2
1

開發工具

在這個單元,我們將使用IntelliJ做為開發工具,讓我們的專案可以簡單一點。
IntelliJ一樣可以用來寫java或kotlin,事實上Android Studio可是基於IntelliJ開發出來的工具。Android 的開發者,IntelliJ 是你的練習單元測試的好工具。
下載IntelliJ

建立IntelliJ 專案

開始寫第一個測試。開啟IntelliJ後,新增Project,選擇Gradle,勾選Kotlin。

New Project

輸入GroupId、ArtifaceId
GroipId你可以視為PackageId,ArtifaceId則為專案名稱。

GroupId

專案建立之後,可以看到如下圖的目錄結構。

main => Production code,撰寫產品程式碼的地方。
test => Testing code,撰寫測試程式碼的地方。

https://ithelp.ithome.com.tw/upload/images/20190916/201118968HCg8V9tOC.png

測試框架 JUnit

開啟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) //驗證預期與結果
}

點下綠色三角型,執行測試
Run Test

測試結果如下圖,可以看到Tests passed,代表測試通過了。

Test Pass

我們來試一個會驗證失敗的案例,如果我們把加法寫錯了,寫成了減法

fun add(number1: Int, number2: Int): Int {
    return number1 - number2 //應為number1 + number2,寫成了number - number2
}

測試出來的結果則會是紅燈,代表測試失敗
可以從測試結果看出,預期會是3,但實際得到的是-1。

Fail

測試結果應能解釋失敗原因

assertEquals裡的expected與actual不能寫相反。如果寫相反了,把expected放前面

Assert.assertEquals(actual, expected)

則會得到

Fail

在看錯誤結果時,這兩種解釋可是完全不一樣的意思。從測試結果你會看到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,測試並顯示涵蓋率

Code Coverage

涵蓋率結果100%,代表你的Production Code都被測試到。

Full Code Coverage

測試命名應具備可讀性、可維護性

在取最小值的function,我們用了兩個測試來表達測試的情境。

//第一個測試:number比number2小,結果應回傳number1
fun testNumber1LessNumber2_minimumShouldBeNumber1()

//第二個測試:number2比number1,結果應回傳number2
testNumber2LessNumber1_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) ,快速產生測試類別及方法。
Tip


上一篇
Day01 - Android TDD 前言
下一篇
Day03 - JUnit 測試框架
系列文
Android TDD 測試驅動開發30

尚未有邦友留言

立即登入留言