有了前面的基础,今天我们要在专案里实作一个「购物车(ShoppingCart)」类别。为了确认实作符合预期的规格,我们将会以 TDD(Test-Driven Development)的风格来写程序。换句话说,我们会用「先写测试、再写实际程序」的来回循环来完成这个类别的实作。最终的目标是,当我们把程序码推到 GitHub 时,TeamCity 会自动拉取最新的程序码变更并跑测试。若测试失败的话,我们会收到 TeamCity 的通知并可以在 TeamCity 的 Build Log 里看到所有历程的纪录。
在 2021 年的现代,写测试的方式有千百种,测试框架也是多如牛毛。以 Kotlin 生态系来说,从老牌 的 JUnit 到後起新秀 Kotest 都有各自的风格与专长。刚开始接触 Kotlin 的时候,笔者也是先从 JUnit 开始练习,毕竟 xUnit 系列概念大多是共通的,比较好上手。但随着对 Kotlin 及相关测试工具的认识愈多,发现像 Kotest 这种揉和各家测试风格集大成的框架,其设计更符合 Kotlin 开发者喜欢的简洁风格。在这个系列里,笔者将用 Kotest 测试框架做示范。
为了要在练习专案里使用 Kotest,我们要先在专案里新增 Kotest 测试框架 ,这意味着我们要在 Gradle 的 Build Script(也就是在专案根目录底下的 build.gradle.kts
)里新增相依套件。
我们可以直接参考 Kotest 官网说明 在 build.gradle.kts
里增加 kotestVersion
及 dependencies
设定:
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.kts
及 gradle.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 已经用红字标示出 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 的测试案例,以及若测试不通过时会有什麽反应?
<<: [Day 11] Leetcode 152. Maximum Product Subarray (C++)
>>: [Day1] JavaScript Drum Kit
这个 30 天我们理解了 C 语言与 Objective-C,然而我其实有不少事情是没有看懂的,铁人...
Use Actual Cisco 350-701 Dumps to Learn Faster Pas...
研讨会报名网址:https://supr.link/lmFHX 在这场1小时的线上研讨会中,您可以学...
事前提要: 本 API 系为 永丰金 PYTHON API,尚未申请的朋友们,有两个方法可以申请 洽...
当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将...