iT邦幫忙

2021 iThome 鐵人賽

DAY 11
0
DevOps

DevOps 萌新的 TeamCity 極速上手寶典系列 第 11

第十一天:用 TDD 實作購物車類別

有了前面的基礎,今天我們要在專案裡實作一個「購物車(ShoppingCart)」類別。為了確認實作符合預期的規格,我們將會以 TDD(Test-Driven Development)的風格來寫程式。換句話說,我們會用「先寫測試、再寫實際程式」的來回循環來完成這個類別的實作。最終的目標是,當我們把程式碼推到 GitHub 時,TeamCity 會自動拉取最新的程式碼變更並跑測試。若測試失敗的話,我們會收到 TeamCity 的通知並可以在 TeamCity 的 Build Log 裡看到所有歷程的紀錄。

在 2021 年的現代,寫測試的方式有千百種,測試框架也是多如牛毛。以 Kotlin 生態系來說,從老牌 的 JUnit 到後起新秀 Kotest 都有各自的風格與專長。剛開始接觸 Kotlin 的時候,筆者也是先從 JUnit 開始練習,畢竟 xUnit 系列概念大多是共通的,比較好上手。但隨著對 Kotlin 及相關測試工具的認識愈多,發現像 Kotest 這種揉和各家測試風格集大成的框架,其設計更符合 Kotlin 開發者喜歡的簡潔風格。在這個系列裡,筆者將用 Kotest 測試框架做示範。

在專案裡新增 Kotest 測試套件

為了要在練習專案裡使用 Kotest,我們要先在專案裡新增 Kotest 測試框架 ,這意味著我們要在 Gradle 的 Build Script(也就是在專案根目錄底下的 build.gradle.kts)裡新增相依套件。

我們可以直接參考 Kotest 官網說明build.gradle.kts 裡增加 kotestVersiondependencies 設定:

val kotestVersion: String by project

dependencies {
  // ...
  testImplementation("io.kotest:kotest-runner-junit5:$kotestVersion")
  testImplementation("io.kotest:kotest-assertions-core:$kotestVersion")
  testImplementation("io.kotest:kotest-property:$kotestVersion")
}

其中 $kotestVersion 是我們設定的變數,做為我們統一儲存版本號的單一來源。這個變數我們會另外以 Key-Value 的型式存在 gradle.properties 設定檔裡。至於該用哪個 Kotest 版本?可以到 Maven Central 去查詢,以 io.kotest:kotest-runner-junit5 為例,目前最新穩定版是 4.6.2

kotestVersion=4.6.2

設定好 build.gradle.ktsgradle.properties 後,別忘了按一下右上角的 Load Gradle Changes 按鈕,IntelliJ IDEA 會重新整理(包括下載、更新、移除)所有的相依,並載入到專案內。

先寫一個失敗的測試

接著我們來新增我們要建立的類別。首先在 src/main/kotlin 底下新增 Package,對著資料夾按右鍵,然後選 New > Package 選單,以我自己的 Domain 為例,輸入 Package 名稱為 io.kraftsman

接著在我們新建的 Package 裡新增購物車類別。對著 Package 資料夾按右鍵,然後選 New > Kotlin Class/File,輸入類別名稱為 ShoppingCart。IntelliJ IDEA 會在 src/main/kotlin/io.kraftsman 底下建立一個名為 ShoppingCart.kt 的檔案,並預先寫好 Boilerplate Code 如下:

package io.kraftsman

class ShoppingCart {
}

在 TDD 的最佳實踐裡,我們要先寫一個可以執行、但一定失敗的測試,確保我們的測試是真的能被執行,而且如預期的會失敗,這樣才不會因為所有設定都是 Happy Path 而有偏誤。我們可以用 IntelliJ IDEA 來幫我們建立測試類別,把游標放在 ShoppinCart 類別的兩個 { } 之間,然後按 ⌘+N 叫出 Generate 選單,選 Test。在彈出視窗裡把 Testing library 更換成 Kotest,其他保持預設值後按 OK。IntelliJ IDEA 會自動幫我們產生 ShoppingCartTest 類別在 src/test/io.kraftsman/ 路徑底下,並預先寫好 Boilerplate Code 如下:

package io.kraftsman

import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe

class ShoppingCartTest : FunSpec({
    
})

接著我們可以直接點選類別名稱旁的綠色播放鍵,讓 IntelliJ IDEA 幫我們執行 Kotest 的測試。執行結果會出錯是正常的,因為我們本來就打算這樣錯。

用測試描述購物車的行為

接著我們要開始用寫測試的方式來描述我們預期這個 ShoppingCart 類別我們可以怎麼使用?首先我們可以先用一個 context() 函式來描述測試脈絡,接著再用 test() 以「測試 3A(Arrange、Act、Assert)」來描述測試的行為。程式碼如下:

class ShoppingCartTest : FunSpec({

  context("一個購物車") {
    test("當兩個 100 元商品相加時,總價為 200") {
      // Arrange
      val product1 = Product(id = 1, name = "Product 1", price = 100)
      val product2 = Product(id = 1, name = "Product 1", price = 100)
      val shoppingCart = ShoppingCart()

      // Act
      shoppingCart.add(product1)
      shoppingCart.add(product2)

      // Assert
      shoppingCart.totalPrice() shouldBe 200
    }
  }

})

用 IntelliJ IDEA 產生缺漏的程式碼

當這段程式碼寫好時,不用執行就知道編譯會失敗,因為 IntelliJ IDEA 已經用紅字標示出 Product 類別不存在、ShoppingCart 類別上沒有 add()totalPrice() 等問題。這時也不用急著自己去修,我們可以讓 IntelliJ IDEA 快速地幫我們把決漏的程式碼「補」起來。

把游標放在這些被標記成紅字的錯誤程式碼上,然後按快速鍵 Option+Enter(macOS)Alt+Enter(Windows),IntelliJ IDEA 會自動提示該怎麼修正,直接按 Enter 讓它幫我們產生 Product 類別及 ShoppingCart 類別的有 add()totalPrice() 方法。

補上邏輯程式碼

有 IntelliJ IDEA 幫我們寫好基本的程式碼「骨架」後,接下來我們只要「填肉」就好。首先打開 Product 類別,由於它只是一個承裝商品資訊的容器,在 Kotlin 裡可以直接把它宣告成 Data Class。先在 class 前加上 data 關鍵字,把所有建構子的參數加上 val 宣告,因為這個類別不需要邏輯,所以也把多餘的大括號去掉。修改後的程式碼長得會像這樣:

data class Product(val id: Int, val name: String, val price: Int)

ShoppingCart 類別裡,我們在內部參加一個儲存 Product 的 List,當使用者呼叫 add() 方法時就可以把 Product 加進 List 裡。而 totalPrice 就會回傳這個 List 裡所有商品 price 的總和。完成後的程式碼像這樣:

class ShoppingCart {
    private val products = mutableListOf<Product>()

    fun add(product: Product) {
        products.add(product)
    }

    fun totalPrice(): Int {
        return products.sumOf { it.price }
    }
}

這時再回到 ShoppingCartTest 執行測試,假如您看到 Run 面板裡出現的是綠色勾勾的話,就表示剛剛增加的程式碼都是正確的,一個初步的購物車類別就開發完成了!

小結

透過這種 TDD 的開發流程,我們就可以確保程式碼的設計是符合使用者的期待,之後再新增其他程式碼時,只要測試沒有出現紅色錯誤訊息,就表示我們的程式沒有被改壞,是不是更放心了呢?

明天我們就要來看看如何用 TeamCity 來執行 Kotest 的測試案例,以及若測試不通過時會有什麼反應?


上一篇
第十天:在 TeamCity 上完成第一個建置工作
下一篇
第十二天:在 TeamCity 上執行測試
系列文
DevOps 萌新的 TeamCity 極速上手寶典31

尚未有邦友留言

立即登入留言