随着我们专案功能的增加,虽然目前只有两个函数,但是我们的测试函数已经增加了不少。
为了减少我们未来阅读测试程序的痛苦,也为了提升未来整个专案的可维护度,我们可以开始重构我们的测试程序了。
观察我们的测试程序,我们可以发现前面建立测试资料库的部分,是不断重复的
Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", driver = "org.h2.Driver")
transaction {
SchemaUtils.create(Users)
SchemaUtils.create(Tags)
SchemaUtils.create(UsersTags)
// ...
我们可以将这段逻辑独立成一个函数 initDatabase()
private fun initDatabase() {
Database.connect(
"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;",
driver = "org.h2.Driver"
)
transaction {
SchemaUtils.create(Users)
SchemaUtils.create(Tags)
SchemaUtils.create(UsersTags)
}
}
由於我们有加上 DB_CLOSE_DELAY=-1;
这段参数,我们不用担心资料库建立在切换 transaction()
时被重设,其他 transaction()
都可以使用我们建立的资料表。
然後原本的测试就可以透过 initDatabase()
执行这段逻辑,比方说昨天的 测试单一全新用户加上标签
就可以变成
fun `测试单一全新用户加上标签`() {
initDatabase()
transaction {
val testUser = User.new {
name = "TestUser"
}
val testTag = Tag.new {
name = "TestTag"
}
updateUsersTags(listOf(testUser), listOf(testTag))
assertEquals(
listOf(testTag),
testUser.tags.toList()
)
}
}
是不是比起昨天更简洁了一点点呢?
改好了之後,记得执行一下测试,看看我们有没有哪边改错了。
如果改错的话,由於我们修改的幅度很小,我们只要赶快复原成修改前的样子,再看看改错的地方可能是哪边,这样就可以安心地继续往下重构了。
改好并通过测试之後,我们再往下看看还有哪边可以调整。
再来,我们观察到我们的测试都会尝试建立几个 User
物件以及 Tag
物件,来进行一系列的操作。
当我们需要很多个物件时,就会导致我们要执行很多次的 User.new()
或 Tag.new()
。
有没有办法将这段逻辑抽离出去呢?当然可以!我们来建立一个 makeTestUsers()
函数,接收 num
来设定我们要做出几个 User
物件,并回传 List<USer>
private fun makeTestUsers(num: Int): List<User> {
}
接着就是建立逻辑了,我们观察到 List()
这个函数的签名
public inline fun <T> List(size: Int, init: (index: Int) -> T): List<T>
我们发现到,第一个参数是这个 List
的大小,第二个参数就是每个物件初始化时,所使用的 lambda 函数。这个跟我们需要的逻辑不谋而合!
我们先写 makeTestUsers()
的第一个版本
private fun makeTestUsers(num: Int): List<User> {
return List(size=num){ User.new { name="TestUser" }}
}
再来我们发现,这个函数只有单行!在 Kotlin 语法下可以更简化
private fun makeTestUsers(num: Int): List<User> =
List(size=num){ User.new { name="TestUser" } }
以同样的逻辑,我们可以做出 makeTestTags()
来建立测试用的 List<Tag>
private fun makeTestTags(num: Int): List<Tag> =
List(size=num){ Tag.new { name="TestTag" } }
然後,我们就能在测试程序内快速建立很多 User
和 Tag
物件了
val testUsers = makeTestUsers(2)
不过我们後来发现,这样的写法有个缺点,会导致我们在存取单一物件时,需要用很多的 testUsers[0]
、testUsers[1]
来标记我们的物件,这样看起来有点不美观。
有没有办法让这段逻辑看起来,再更加的直观一点呢?
Kotlin 支援在宣告物件时,就直接将 List
解构的写法。
例如
val (testUser, testUser2) = makeTestUsers(2)
就会让 testUSer
和 testUser2
是透过 makeTestUsers()
建立的 User
物件了。
并且这个写法也支援只有一个元素的 List
解构
val (testUser) = makeTestUsers(1)
利用这个语法,我们可以让我们的测试,再更加的精简
fun `测试单一全新用户加上标签`() {
initDatabase()
transaction {
val (testUser) = makeTestUsers(1)
val (testTag) = makeTestTags(1)
updateUsersTags(listOf(testUser), listOf(testTag))
assertEquals(
listOf(testTag),
testUser.tags.toList()
)
}
}
其他的测试案例,也可以照相同逻辑进行简化
fun `测试多个用户加上标签`() {
initDatabase()
transaction {
val (testUser, testUser2) = makeTestUsers(2)
val (testTag) = makeTestTags(1)
updateUsersTags(listOf(testUser, testUser2), listOf(testTag))
assertEquals(
listOf(testTag),
testUser.tags.toList()
)
assertEquals(
listOf(testTag),
testUser2.tags.toList()
)
}
}
简化过之後,记得要再执行一下测试,以确保没有任何东西被改坏喔!
assertThat()
昨天我们在检查 List 不应存在某个元素时,我们用到了 assertThat()
这个函数
assertThat(testUser.tags.toList(), not(hasItem(oldTag)))
其实之前我们所撰写的 assertEquals()
逻辑,也可以用 assertThat()
实作,并且语意会更加清楚!
我们先来看看 assertThat()
的签名
public static <T> void assertThat(T actual, Matcher<? super T> matcher)
第一个参数是被测试的资料,第二个是我们希望成立的条件。
以前面的 assertEquals(listOf(testTag),testUser2.tags.toList())
举例,就是我们希望 testUser2.tags.toList()
等於 listOf(testTag)
。
用 matcher
的角度来设计,我们可以用 is()
这个函数来实作。不过由於 is
是一个 Kotlin 的关键字,所以我们要用 「`」将 is
包起来。
看起来像是这样
assertThat(testUser.tags.toList(),`is`(listOf(testTag)))
透过这样的调整,整段程序看起来更简洁清楚了。简直就可以直接念出来:「assert that testUser's tags is list of testTag」
我们来看看新的 测试单一全新用户加上标签
fun `测试单一全新用户加上标签`() {
initDatabase()
transaction {
val (testUser) = makeTestUsers(1)
val (testTag) = makeTestTags(1)
updateUsersTags(listOf(testUser), listOf(testTag))
assertThat(
testUser.tags.toList(),
`is`(listOf(testTag))
)
}
}
测试多个用户加上标签
fun `测试多个用户加上标签`() {
initDatabase()
transaction {
val (testUser, testUser2) = makeTestUsers(2)
val (testTag) = makeTestTags(1)
updateUsersTags(listOf(testUser, testUser2), listOf(testTag))
assertThat(
testUser.tags.toList(),
`is`(listOf(testTag))
)
assertThat(
testUser2.tags.toList(),
`is`(listOf(testTag))
)
}
}
写的过程中,我们也有持续的运作测试程序,以确保我们没有改错东西。
这样,我们的测试程序码重构就大功告成罗!
先上html的部分,logout则是把Local Storage清掉 再来是javascript的部...
大家好~~欢迎来到第二十六篇 聊聊 AI 相关论点 本篇呢,来跟大家分享之前本人有做过一个跟车子有关...
即时我们在不同元件分别引入CSS档,但打包後其实每个CSS还是会整个专案共用。 只想对单独元件设立自...
添加 OkHttp 依赖库 要使用 OkHttp,必须在 gradle (Module) 层级的 d...
一. 前言 词性标注 Part Of Speech(後面皆简称POS),简单来说就是将文章、句子中,...