tests/:測試策略與 fixtures 設計

140+ 測試檔、30+ 語言的 sample fixture——怎麼測「多語言 AST 解析」
檔案:tests/(約 140 檔)· tests/fixtures/

大方向

ARCHITECTURE.md 講得很清楚:一個模組一個測試檔,全部是純單元測試——不碰網路、不寫 tmp_path 以外的檔案系統。跑法:pytest tests/ -q

測試量很大(test_extract.py 136KB、test_detect.py 117KB、test_watch.py 135KB、test_languages.py 122KB…),所以這頁是「策略導覽」而不是逐檔翻譯。

1. fixtures 設計:一語料庫,多語言

tests/fixtures/sample.* tests/fixtures/每個支援語言都有一份「同一支範例程式」,測法統一。

fixtures 目錄的心臟是 30+ 個 sample.<ext> 檔案——sample.pysample.tssample.rssample.gosample.javasample.sqlsample.passample.sv(SystemVerilog)…每種語言一份小樣本。

這讓 test_languages.py 可以用同一套斷言邏輯測所有語言:每個 fixture 都有個別或跨語言的名稱解析測例(test_cross_language_call_resolutiontest_csharp_member_callstest_swift_cross_file_callstest_java_type_resolution…)。

特殊語料按場景放:cpp_logger/a vs cpp_logger/b(同名檔衝突)、swift_cross_file/(extension)、xaml_viewmodel/(XAML+ViewModels)、pascal_cross_file/objc_mixed/crate_a/crate_b(Cargo workspace)。

graphify-out/fixtures 快取 tests/fixtures/graphify-out/cache/預先算好的抽取快取,讓測試不必真的跑 tree-sitter。

段落式說明:fixtures 裡有幾個 graphify-out/cache/<sha>.json——預先抽取好的結果。測試直接載入快取,驗證「快取載入 → 建圖」這條路徑,不必每次真的解析。這也測試了快取格式的相容性(改快取格式時這裡會炸)。

2. 代表測試檔

test_cluster.py(3KB,全文可讀) tests/test_cluster.py小而完整的測試示範。

這個檔是「測一個純函數模組」的範本,值得整檔讀:

  • make_graph()build_from_json 從 fixtures 的 extraction.json 建圖——所有測試共用。
  • 行為測試:cluster 回傳 dict、覆蓋所有節點、cohesion_score 的極端值(complete=1.0、單節點=1.0、disconnected=0.0)。
  • capsys 測試(很有特色):test_cluster_does_not_write_to_stdout 用 pytest 的 capsys fixture 驗證 cluster 不會寫 stdout、stderr 不含 ANSI 碼——直接對應 cluster._partition 的「擋 ANSI」行為(issue #19)。
  • 確定性測試:remap_communities_to_previous 重用舊 ID、新社群給確定性新 ID。
教學重點:這個測試檔把「純函數 + 固定 fixture + 行為/邊界/副作用/確定性」四種測試一次示範完。3KB 而已。
test_validate.py · test_languages.py · test_build.py 等 tests/schema 驗證、語言矩陣、建圖整合。

段落式說明:

  • test_validate.py — 對 validate_extraction 的 schema 測試:缺 nodes/edges key、非 dict、非法 file_typevideo 不是允許值)、非法 confidence…
  • test_languages.py(122KB,最大的語言矩陣)— 每個語言的抽取正確性。
  • test_build.py(67KB)— 建圖整合,含 hyperedges、ghost 合併、檔案消歧。
  • test_dedup.py(40KB)— 去重管線逐步測試(對應 dedup 的設計)。
  • test_incremental.py / test_cache.py — 增量與快取(對應 增量設計)。

3. 一個值得注意的 macOS 註記

sample.f90 vs sample.F90 README 開發章節大小寫不敏感的檔案系統會撞檔。

README 的 contributing 章節提醒:測試套件同時有 sample.f90sample.F90(Fortran 兩種大小寫副檔名)。在 macOS 的 HFS+/APFS(大小寫不敏感)上這兩個檔會撞在一起——要同時測兩種 Fortran 變體得在 Linux 或 Docker 跑。

教學價值:一個跨平台專案連「測試檔命名」都要考慮檔案系統大小寫。實務上的 workaround 之一是把區分移到目錄層級。

看完這頁你應該能說出:fixtures 怎麼支援 30+ 語言、capsys 測了什麼、為什麼要預先算好快取 fixture。測試策略與「新增一個語言」的貢獻流程直接呼應——加語言就要加 fixture + test_languages.py 測例。

① 進階真實情境 Worked Example:用 fixture 驅動的方式為新語言寫測試

前面是「看懂測試策略」;這次是動手補測試:你剛在 extractors 頁加完 Kotlin 支援,現在要用「同一個 fixture 通吃所有語言」的紀律把它測好。

  1. 寫 sample.kt:仿照 sample.py 的結構——一個 class、幾個函數、跨檔 import、型別註解,讓斷言可以「同構」對應其他語言。
  2. 加跨檔 fixture:若是跨檔解析測例,建 kotlin_cross_file/ 目錄(仿 swift_cross_file/),驗證 Phase 2 的類別級 INFERRED 邊。
  3. 加 test_languages.py 測例:斷言「sample.kt 抽出 N 個 class、M 個 function、import 邊存在」;若該語言支援跨檔解析,加 test_kotlin_cross_file_calls
  4. 驗證快取路徑:若測「快取載入 → 建圖」,把預先抽取結果放 graphify-out/cache/ fixture——測試不必每次真跑 tree-sitter,也順便鎖住快取格式。
  5. 全量回歸pytest tests/ -q——確認 140+ 檔沒被新 fixture 弄壞(尤其同名檔衝突、大小寫敏感檔案系統)。
為什麼選這條路徑:fixtures 的策略讓「新增語言的成本 = 新增 fixture + 新增測例」,而不是重寫一整套測試。共用樣本也讓「語言間的一致性」可比較——Kotlin 抽得比 Python 差時,同樣的斷言會立刻暴露差異。測試不只是驗證,還是「語言的深度契約」。

② 深入原理擴充:capsys、確定性測試、與「測試即契約」

tests 頁最容易被跳過的是三個「非典型」測試技巧:

大家以為建圖正確、但其實有誤的案例:以為「測試全綠 = 抽取正確」。140+ 個測試檔覆蓋的是「已知的」語言與「已寫測例的」結構——一個你沒測的語法(Kotlin 的 inline function、Swift 的 @available 條件)抽錯時,測試照樣全綠。sample.* fixture 只有一份「樣本」,不代表整個語言。最經典的誤判:新增語言後 pytest tests/ -q 全過,就宣稱「Kotlin 支援完成」——實際上只完成「sample.kt 支援完成」。真實語料的語法變體,要靠「把真實 repo 當 fixture」的 integration 測試才抓得到。

③ 診斷式疑難排解表

症狀可能原因解決方案
測試在 macOS 上「多出」一堆失敗大小寫不敏感檔案系統讓 sample.f90 / sample.F90 撞檔在 Linux/Docker 跑該測例;或把區分移到目錄層級
新 fixture 造成別語言測試爆炸fixture 檔名撞名(同名 .py/.ts 樣本被誤抓)或 registry 共享狀態檢查 fixture 命名唯一性;跑單一測試檔對比爆炸範圍
測「快取載入」時測例極慢沒用預算好的 cache fixture,真的在跑 tree-sitter把抽取結果預先放進 graphify-out/cache/ fixture
改了 cluster 的 ANSI 處理,測試卻沒抓到沒有 capsys 測例覆蓋「不寫 stdout」契約仿 test_cluster_does_not_write_to_stdout 補測
某語言「實際 repo」抽錯但測試全綠sample fixture 太簡單,沒涵蓋該語言語法變體把真實 repo 縮小成 fixture 加進 tests;補 integration 層測試

④ 進階挑戰題

  1. capsys 驗證「cluster 不寫 stdout」。請列出這個契約可能被破壞的「非預期途徑」(例如 C 擴充、第三方套件直接寫 fd),並設計一個比 capsys 更強(甚至跨行程)的驗證方式。
  2. fixtures 是「一語料庫、多語言」的樣本。如果兩個語言該抽出的抽象數天生不同(Kotlin 的 data class vs Python 的 dataclass),「同構斷言」該怎麼寫才不會過度耦合或過度寬鬆?
  3. 「測試全綠 ≠ 抽取正確」。請為 graphify 設計一個「語法覆蓋率」指標:怎麼量化「這個語言有多少語法變體沒被 fixture 涵蓋」?