[Day 17] 新功能的测试,检验不应该存在的资料

自动测试时除了检查加入新资料,有时我们也会希望检查旧资料是否成功地被移除。

今天我们用一个新的功能,来展示如何针对不应该存在的资料进行检查。

新功能测试

我们多加一个新的函数,可以接收 listOf(tag),来更新 user 所有的 tag

重点是,我们这边更新的内容,不仅仅是针对单一个 user

我们还接受传 listOf(user),一次更新所有的 user.tags

fun updateUsersTags(users: List<User>, tags: List<Tag>) {
}

撰写测试案例

像昨天一样,我们在 tests/kotlin/ 资料夹内,建立 UpdateUsersTagsKtTest.kt 档案,并加上几个测试案例

internal class UpdateUsersTagsKtTest {
	fun `测试单一全新用户加上标签`() {
	Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", driver = "org.h2.Driver")  
	    transaction {
			SchemaUtils.create(Users)  
			SchemaUtils.create(Tags)  
			SchemaUtils.create(UsersTags)  
			val testUser = User.new {  
				name = "TestUser"  
			}  
			val testTag = Tag.new {  
				name = "TestTag"  
			}  
			updateUsersTags(listOf(testUser), listOf(testTag))  
			assertEquals(
				listOf(testTag),
				testUser.tags.toList()
			)  
		}  
	}
}

运作测试看看,我们发现测试并没有通过

expected:<[Tag@2380d06e]> but was:<[]>
Expected :[Tag@2380d06e]
Actual   :[]

这是理所当然的,因为我们的 updateUsersTags() 里面还是空的

我们根据昨天的 userAddTag() 稍微调整一下写法,看看能不能通过

fun updateUsersTags(users: List<User>, tags: List<Tag>) {  
    transaction {
        users.forEach {  
            it.tags = SizedCollection(it.tags.toList() + tags)  
        }  
    }
}

重新运作测试,就可以通过了。

我们多加另一个测试的案例 测试多个用户加上标签

@Test  
fun `测试多个用户加上标签`() {  
	Database.connect(
		"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", 
		driver = "org.h2.Driver"
	)  
    transaction {  
		SchemaUtils.create(Users)  
        SchemaUtils.create(Tags)  
        SchemaUtils.create(UsersTags)  
        val testUser = User.new {  
 			name = "TestUser"  
 		}  
 		val testUser2 = User.new {  
 			name = "TestUse2"  
 		}  
 		val testTag = Tag.new {  
 			name = "TestTag"  
 		}  
 		updateUsersTags(listOf(testUser, testUser2), listOf(testTag))  
        assertEquals(listOf(testTag), testUser.tags.toList())  
        assertEquals(listOf(testTag), testUser2.tags.toList())}  
}

这个测试也可以成功通过

为求测试的完整,我们再加上 测试已有标签用户更动新标签

@Test  
fun `测试已有标签用户更动新标签`() {  
    Database.connect(
		"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", 
		driver = "org.h2.Driver"
	)  
    transaction {  
 		SchemaUtils.create(Users)  
        SchemaUtils.create(Tags)  
        SchemaUtils.create(UsersTags)  
        val testUser = User.new {  
 			name = "TestUser"  
 		}  
 		val testTag = Tag.new {  
 			name = "TestTag"  
 		}  
 		val testTag2 = Tag.new {  
 			name = "TestTag2"  
 		}  
 		testUser.tags = SizedCollection(listOf(testTag))  
        updateUsersTags(listOf(testUser), listOf(testTag2))  
        assertEquals(listOf(testTag2), testUser.tags.toList())  
    }  
}

执行之後,我们发现以下错误

expected:<[Tag@641aca5a]> but was:<[Tag@60c77761, Tag@641aca5a]>
Expected :[Tag@641aca5a]
Actual   :[Tag@60c77761, Tag@641aca5a]

这是怎麽回事呢?不是只是拿昨天的程序做了一点调整吗?

和我们的需求逻辑比较之後,我们会发现,这是因为昨天的逻辑是「新增」,但是今天需求的逻辑是「更新」导致的。

我们需要再调整一下我们的程序

fun updateUsersTags(users: List<User>, tags: List<Tag>) {  
    transaction {  
		users.forEach {  
			it.tags = SizedCollection(tags)  
		}  
 	}
 }

这样的话,我们的 测试已有标签用户更动新标签 就会通过了。

我们可以再增加一些测试案例,像是

  • 测试多个已有标签用户更动新标签
  • 测试多个已有标签用户更动多个新标签
@Test  
fun `测试多个已有标签用户更动新标签`() {  
    Database.connect(
		"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", 
		driver = "org.h2.Driver"
	)  
	transaction {  
         SchemaUtils.create(Users)  
        SchemaUtils.create(Tags)  
        SchemaUtils.create(UsersTags)  
        val testUser = User.new {  
             name = "TestUser"
        }
        val testUser2 = User.new {  
            name = "TestUser2"
        }
        val testTag = Tag.new {  
            name = "TestTag"  
        }  
        val testTag2 = Tag.new {  
            name = "TestTag2"  
        }  
        testUser.tags = SizedCollection(listOf(testTag))  
        testUser2.tags = SizedCollection(listOf(testTag))  
        updateUsersTags(listOf(testUser, testUser2), listOf(testTag2))  
        assertEquals(listOf(testTag2), testUser.tags.toList())  
        assertEquals(listOf(testTag2), testUser2.tags.toList())  
    }  
}  
  
@Test  
fun `测试多个已有标签用户更动多个新标签`() {  
    Database.connect(
		"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", 
		driver = "org.h2.Driver"
	)  
    transaction {  
 		SchemaUtils.create(Users)  
        SchemaUtils.create(Tags)  
        SchemaUtils.create(UsersTags)  
        val testUser = User.new {  
 			name = "TestUser"  
 		}  
 		val testUser2 = User.new {  
 			name = "TestUser2"  
 		}  
 		val oldTag = Tag.new {  
 			name = "oldTag"  
 		}  
 		val testTag = Tag.new {  
 			name = "TestTag"  
 		}  
 		val testTag2 = Tag.new {  
 			name = "TestTag2"  
 		}  
 		testUser.tags = SizedCollection(listOf(oldTag))
        testUser2.tags = SizedCollection(listOf(oldTag)) 
		
        updateUsersTags(listOf(testUser, testUser2), listOf(testTag, testTag2))  
        assertEquals(listOf(testTag, testTag2), testUser.tags.toList())  
        assertEquals(listOf(testTag, testTag2), testUser2.tags.toList())  
    }  
}

这些测试都可以顺利通过,代表我们的程序应该是没有问题的。

不过,虽然程序本身没有问题,但是刚刚「更新」和「新增」之间的逻辑区分,确实让我们花了点时间确认,未来的工程师可能在这里也会遇到类似的逻辑问题。

如果我们想要强调这段程序是「更新」而不是「新增」,自动测试也能帮助我们吗?

增加协助我们厘清逻辑的测试案例

上面的问题,答案是:可以的!

我们可以加上一个案例 测试更新已有标签用户时应移除旧标签。这样一来,如果有人不小心将这段程序,改成没有移除旧标签的版本,他就会从错误讯息内,看到这个未通过的测试案例,进而提醒他应该要移除掉旧标签。

测试更新已有标签用户时应移除旧标签 这个测试案例,要强调「应移除旧标签」,所以用之前的 assertEquals() 断言是不够的。

这边我们要利用 org.junit.Assert.* 套件的 assertThat(),搭配上 org.hamcrest.CoreMatchers.* 套件的 not()hasItem(),来协助我们断言旧标签不存在於更新之後的 user.tags

我们来实作一下 测试更新已有标签用户时应移除旧标签

fun `测试更新已有标签用户时应移除旧标签`() {  
	Database.connect(
		"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", 
		driver = "org.h2.Driver"
	)  
    transaction {  
 		SchemaUtils.create(Users)  
        SchemaUtils.create(Tags)  
        SchemaUtils.create(UsersTags)  
        val testUser = User.new {  
 			name = "TestUser"  
 		}  
 		val oldTag = Tag.new {  
 			name = "oldTag"  
 		}  
 		val newTag = Tag.new {  
 			name = "newTag"  
 		}  
 		testUser.tags = SizedCollection(listOf(oldTag))
		updateUsersTags(listOf(testUser), listOf(newTag))  
        assertThat(testUser.tags.toList(), not(hasItem(oldTag)))  
    }

这个测试案例运行通过之後,可以保证之後的修改,如果不小心在执行 updateUsersTags() 时,没有移除旧标签的话,可以在自动测试的阶段就被发现到。


<<:  06 - TPM - Tmux Plugin Manager 与它的插件

>>:  从零开始学3D游戏设计:游戏资料储存基础

[Day18] TS:理解 Omit 的实作

是我们今天要聊的内容,老样的,如果你已经可以轻松看懂,欢迎直接左转去看同事 Ken 精彩的文章 —...

Day12:今天来谈一下Microsoft-Defender-for-Endpoint的设定及管理自动化

在Microsoft Defender for Endpoint中有提供自动化调查和补救功能。 自动...

[Day09 - UI/UX] UI 绘制

在UI出图之前,我们先确认好目前的 wireframe 是完整的。接下来只要依照 wireframe...

[Day19] Vue 3 单元测试 (Unit Testing) - Event Handling

Event Handling 在开发元件时一定少不了会需要触发事件的时候,像是 click 事件、i...

字典与集合

字典特徵 字典和阵列类似,也是可变序列,但它是无序的,保存的内容是以「键:值」的形式存放的。 键是唯...