概覽
Lincheck 是一個用於在 JVM 上測試並行程式碼的架構。執行測試時,Lincheck 會探索程式中潛在的執行緒交錯 (thread interleavings),並回報導致錯誤行為的交錯。
在 Kotlin Multiplatform 專案中,您只能使用 Lincheck 在 JVM 上測試程式碼。
在 Lincheck 中,並行測試只需要您列出每個執行緒的操作以及預期的斷言 (assertions)。Lincheck 會處理其餘部分:
kotlin
class CounterTest {
@Test // 測試函式宣告
fun test() = Lincheck.runConcurrentTest {
var counter = 0
// 並行遞增計數器
val t1 = thread { counter++ }
val t2 = thread { counter++ }
// 等待執行緒結束
t1.join()
t2.join()
// 檢查兩個遞增是否都已套用
assertEquals(2, counter)
}
}如果測試失敗,Lincheck 會提供導致錯誤的執行緒交錯和執行緒切換點:
text
| ------------------------------------------------------------------------------- |
| 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 的運作方式
每當 JVM 執行並行程式碼時,跨執行緒的操作執行順序可能會發生變化。例如,一個操作可能會被另一個執行緒中的操作中斷。這本身不是錯誤,但如果程式碼中存在並行錯誤 (concurrency bugs),則可能導致錯誤。

執行案例 (execution scenario) 定義了操作如何在執行緒之間分佈,以及每個執行緒內的執行順序。
執行排程 (execution schedule,也稱為 執行緒交錯) 定義了所有執行緒中所有操作的執行順序。
Lincheck 實作了兩種測試策略,以尋找導致錯誤行為的執行排程:
- 模型檢查 (Model checking)。Lincheck 透過在程式中插入明確的執行緒切換指令來控制排程。這些指令被放置在同步點或共用記憶體存取處。模型檢查使 Lincheck 能夠產生導致錯誤的精確執行追蹤 (trace)。
- 壓力測試 (Stress testing)。由作業系統控制排程。Lincheck 會多次執行每個案例,以增加發現錯誤的機會。
探索 Lincheck
- 在 Lincheck 快速入門 中逐步學習 Lincheck 特性。
- 在 測試策略 文章中了解測試並行資料結構的宣告式方法。
