Lincheck 入门
这篇快速入门向导将引导你, 完成 Lincheck 的设置, 编写你的第一个 Lincheck 测试, 并解读测试报告.
你将会:
创建一个新的 IntelliJ IDEA 项目, 并安装 Lincheck.
编写你的第一个并发测试, 并使用 Lincheck 运行它.
创建一个并发数据结构, 并使用两种测试策略, 通过 Lincheck 进行测试.~~
创建项目
在 IntelliJ IDEA 中打开一个既有的 Kotlin 项目, 或者 创建一个新项目.
添加依赖项
要在项目中使用 Lincheck, 请在你的构建配置中添加相应的依赖项:
编写你的第一个测试
对于一个基本的并发测试, 创建一个测试函数, 描述每个线程中应该执行的操作以及预期的断言. Lincheck 使用 模型检查(Model Checking) 探索程序可能的线程交叉执行, 并在出现错误行为时提供错误报告.
在
src/test目录中, 创建CounterTest.kt文件.导入
org.jetbrains.lincheck,kotlinx.concurrent, 和kotlin.test库:import org.jetbrains.lincheck.* import kotlin.concurrent.* import kotlin.test.*编写一个测试, 创建一个变量, 以及操作该变量的两个线程:
class CounterTest { @Test // 声明测试函数 fun test() = Lincheck.runConcurrentTest { var counter = 0 // 并发的递增 counter val t1 = thread { counter++ } val t2 = thread { counter++ } // 等待线程结束 t1.join() t2.join() // 检查两次递增操作是否都已生效 assertEquals(2, counter) } }运行测试. Lincheck 会生成一个报告, 其中包含导致错误行为的线程交叉执行:
| ------------------------------------------------------------------------------- | | Main Thread | Thread 1 | Thread 2 | | ------------------------------------------------------------------------------- | | thread(block = Lambda#2): Thread#1 | | | | thread(block = Lambda#3): Thread#2 | | | | switch (reason: waiting for Thread 1 to finish) | | | | | | run() | | | | counter ➜ 0 | | | | switch | | | run() | | | | counter ➜ 0 | | | | counter = 1 | | | | | counter = 1 | | Thread#1.join() | | | | Thread#2.join() | | | | counter.element ➜ 1 | | | | assertEquals(2, 1): threw AssertionFailedError | | | | ------------------------------------------------------------------------------- |Lincheck 找到了一个线程交叉执行, 其中一个
inc()操作覆盖了counter的值.- 逐步解读报告
在 Thread 2 中, JVM 读取了初始的
counter值.执行从 Thread 2 切换到 Thread 1.
在 Thread 1 中, JVM 递增了计数器.
inc()操作的所有步骤都不中断地执行完毕: 从变量读取值, 递增值, 并将值写回变量.执行切换回 Thread 2.
在 Thread 2 中, JVM 将步骤 1 中获取的值加 1, 并将结果写入
counter变量.
为数据结构编写测试
除了基本的并发测试之外, Lincheck 还支持以声明式方案测试并发数据结构.
要在 Lincheck 中测试数据结构, 你只需要声明数据结构的并发方法, 以及一个测试函数. Lincheck 会生成随机的并发场景, 使用指定的测试策略执行这些场景, 并提供错误报告.
本节中, 你将测试一个简单的计数器:
在
src/test目录中, 创建CounterStructureTest.kt文件.导入
lincheck.datastructures和kotlin.test库:import org.jetbrains.lincheck.datastructures.* import kotlin.test.*创建一个
Counter数据结构:class Counter { @Volatile private var value = 0 fun inc(): Int = ++value fun get() = value }创建
CounterStructureTest类. 设置数据结构的初始状态, 并使用@Operation注解标记数据结构的并发操作:class CounterStructureTest { // 初始状态 private val c = Counter() // 并发操作 @Operation fun inc() = c.inc() @Operation fun get() = c.get() }在
CounterTest类中, 使用ModelCheckingOptions()声明一个测试函数:@Test fun stressTest() = ModelCheckingOptions().check(this::class)运行测试. Lincheck 会生成一个错误报告, 其中包含并发场景, 以及导致错误行为的具体线程交叉执行情况:
| ------------------- | | Thread 1 | Thread 2 | | ------------------- | | inc(): 1 | inc(): 1 | | ------------------- || ------------------------ | | Thread 1 | Thread 2 | | ------------------------ | | | inc(): 1 | | | c.inc(): 1 | | | value ➜ 0 | | | switch | | inc(): 1 | | | | value = 1 | | | value ➜ 1 | | | result: 1 | | ------------------------ |
下一步
阅读 测试策略 章节, 进一步了解数据结构测试的声明式方案, 以及支持的测试策略.