Kotlin如何对含嵌套挂起函数的withLock包装方法做单元测试
单元测试修改方案
核心思路是拆分测试边界,不要重复测试相同逻辑,原有V1的业务测试用例完全保留,不需要修改,额外补充两类测试即可:
1. 单独覆盖withLock()的通用包装逻辑
这部分测试完全不耦合业务代码,只验证锁本身的行为正确性,一次测完所有包装场景,后续所有用withLock包装的函数都不需要重复验证锁逻辑:
- 验证业务函数执行前一定会调用
acquireLock() - 验证业务函数执行完成(包括正常返回、抛出异常两种场景)后一定会调用
releaseLock() - 验证业务函数的返回值会被
withLock()正常透传
参考测试代码(基于MockK + Kotlin协程测试库):
@Test fun `withLock executes in order of acquire lock -> run function -> release lock`() = runTest { val testReturn = "test_result" val mockInnerFunc = mockk<suspend () -> String>(relaxed = true) coEvery { mockInnerFunc.invoke() } returns testReturn mockkStatic(::acquireLock, ::releaseLock) every { acquireLock() } just Runs every { releaseLock() } just Runs val result = withLock(mockInnerFunc) coVerifyOrder { acquireLock() mockInnerFunc() releaseLock() } assertEquals(testReturn, result) } @Test fun `withLock releases lock even when inner function throws exception`() = runTest { val testException = RuntimeException("mock business error") val mockInnerFunc = mockk<suspend () -> String>() coEvery { mockInnerFunc.invoke() } throws testException mockkStatic(::acquireLock, ::releaseLock) every { acquireLock() } just Runs every { releaseLock() } just Runs val thrown = assertThrows<RuntimeException> { withLock(mockInnerFunc) } assertEquals(testException, thrown) coVerifyOrder { acquireLock() mockInnerFunc() releaseLock() } }
2. 覆盖V2的组装逻辑
V2本身不需要重复测试StepA/StepB/StepC的业务细节(这部分已经在V1的用例里覆盖),只需要验证V2确实通过withLock()包装了原有业务逻辑即可。
最优实现方式是直接让V2复用V1,避免代码重复,测试也更简单:
suspend fun V2(): RandomType = withLock(::V1)
对应V2的测试只需要验证:调用V2时触发了锁逻辑,且内部实际执行了V1即可,不需要重复写StepA/StepB/StepC的stub和结果校验。
如果暂时不想重构V2的实现,直接把原有V1的测试代码复制一份,把调用目标从V1()改成V2(),额外加两行锁调用的顺序校验即可,原有stub和业务结果校验逻辑完全不需要改动。
避坑提醒:不要在每个被
withLock包装的业务函数测试里都重复写锁获取、释放的校验逻辑,这部分是通用包装器的职责,单独测透之后,业务用例只需要关心自己的逻辑即可。
内容的提问来源于stack exchange,提问作者Haowen Qu
相关产品推荐
相关产品推荐

