1.【決策邊界|測試與品質】 有一份能力要求清單寫著「覆蓋率 90%」。今天的做法是只產生 Kover 報告、不設門檻。你會在什麼條件下改成設門檻(讓 koverVerify 不到標就建置失敗)?門檻設多少、套在整個專案還是個別模組?哪些類別你認為不該算進分母,為什麼?如果有人為了過門檻,補了一批只呼叫、不斷言的測試,你會怎麼發現、怎麼防?
我的回答:
(待補)
2.【預測再驗證|架構論述】 本篇有一條規則:「ViewModel 不得 import android.*」。下面三種寫法都讓 ViewModel 用到了 android.content.Context:
android.content.Context
import android.content.Context as Ctx,程式裡用 Ctx
typealias AppContext = android.content.Context,ViewModel 只用 AppContext
先寫下預測:哪幾種會被架構測試抓到?再把它們寫成暫時檔(ViewModel 的檔名以 ViewModel 結尾,放在任一個 feature 模組),跑 ./gradlew :core:testing:unitTests 驗證,驗完刪掉。對被放過的寫法,你會補規則,還是接受它?理由是什麼?
我的回答:
(待補)
hook:規範寫在文件裡,沒有人會每次 commit 都去讀。前八天定了一堆規則(core 之間只能依賴 core:common、ViewModel 不拿
Context、效果一律用Channel……),今天把它們變成會失敗的測試。
舊 App 的測試現況,只列形狀與數量:
@Test。Logger 有 10 個 static 方法,直接呼叫 android.util.Log。本機單元測試跑在 JVM 上,android.util.Log 一呼叫就丟例外,所以測試在 @BeforeAll 把一個全域旗標 Logger.isUnitTest 設成 true,@AfterAll 再設回來。這個旗標只有 10 個方法裡的 1 個會檢查;被測程式碼要是走到其他 9 個,測試照樣炸。Day 01 講過:一年前寫好的遷移規範一次都沒有執行。今天要回答兩件事:
core:testing 要提供什麼共用工具。docs/conventions.md 裡,要用什麼讓它們在違規時真的失敗。| 選項 | 做法 | 優點 | 代價 |
|---|---|---|---|
| A. 全用 mock | 依賴一律 mockk<Logger>(),斷言用 verify { logger.w(any(), any(), any()) } |
不用寫任何替身類別;任何介面馬上就能替換 | 測試綁在「呼叫了哪個方法、幾次、什麼參數」上。實作把一次 w 拆成兩次、或把兩次合成一次,該留下的警告都在,測試卻要跟著改;every { } 的設定散在每個測試裡,讀測試要先在腦中執行一遍 stub |
| B. 優先 fake,mock 只用在做不出 fake 的邊界 | 共用 fake 放 core:testing:RecordingLogger 把 log 記成清單,測試斷言清單內容;只有一個函式的依賴直接傳 lambda(Day 06 的 submit) |
斷言的是結果狀態,不是呼叫順序,重構時比較不會誤報;fake 本身是一般的 Kotlin 程式,讀得懂、除錯得了 | fake 要自己寫、自己維護;介面加方法時 fake 要跟著改(編譯器會提醒);fake 寫錯,測試會一起錯 |
| 選項 | 做法 | 優點 | 代價 |
|---|---|---|---|
| A. 只靠 Gradle 模組依賴 | build.gradle.kts 沒宣告的模組,import 就編不過 |
零成本,編譯器就是守門員 | 擋不住有人把依賴加回 build.gradle.kts;也表達不了模組依賴以外的規則,例如「ViewModel 不 import android.*」「效果不用 MutableSharedFlow」 |
| B. ArchUnit | 讀編譯後的 bytecode,用 Java API 寫規則 | 成熟、社群大,Java 生態的標準做法 | 看的是 bytecode:Kotlin 的 sealed、data class、檔案層級函式、import 在 bytecode 裡都變了形,要用 JVM 的角度重新描述;Android 模組的 class 輸出要另外餵給它 |
| C. Konsist | 解析 Kotlin 原始碼,用 Kotlin API 寫規則 | Kotlin 原生:hasSealedModifier、hasDataModifier、imports 直接可用;看的是原始碼,不必先編譯每個模組 |
只看得到原始碼寫了什麼,看不到型別推導的結果;專案外的類別(例如 androidx 的 ViewModel)解析不到,實作時踩到一次(見補充三);Gradle 不知道它讀了哪些檔案(見補充四) |