Property-Based Testing(性質式測試)
一條對所有合法輸入都該成立的性質,就是 property-based testing 拿來當斷言的東西——例如「排序後的長度與原本相同」(守恆)、「編碼再解碼會得到原值」(往返)、「折扣後的金額不會超過原價」(上下界)。框架負責生成大量輸入去試圖推翻它,並在找到反例時自動縮小到最短的那一個。它跟逐例測試的差別在 oracle 的來源——逐例測試要人算出每個輸入對應的答案,性質式測試只要人寫下答案必須滿足的條件,而條件通常比答案好寫。
概念位置
不變量是比例子更難被實作污染的一種判準。逐例的預期值可以從實作跑一次抄回來,不變量抄不了——它必須從需求推導。這讓性質式測試在測試與實作出處不獨立的情況下保有較多驗證力,而生成器的存在也讓它涵蓋到人不會主動想到的輸入組合。
四類骨幹性質(另有上下界與順序無關兩類,連同適用條件與划算判準在判準寫不下來的時候):
| 類型 | 形式 | 適用對象 |
|---|---|---|
| 往返 | 編碼後解碼得回原值 | 序列化、加解密、格式轉換 |
| 守恆 | 操作前後某個量不變或單調變化 | 集合大小、帳目總額、狀態機 |
| 對照 | 與一份簡單但慢的參照實作結果相同 | 最佳化過的演算法 |
| 冪等 | 執行兩次與執行一次結果相同 | 正規化、去重、部署腳本 |
可觀察訊號與例子
同一個測試被複製貼上多次、只有輸入的數值不同:那組例子背後藏著一條沒被寫出來的性質,把它寫出來就是這類測試的起點。另一個訊號是邊界值列表越補越長:0、1、-1、最大值、空字串、單一元素,每次線上出問題就補一個,代表判準是靠回憶累積的。
失敗的縮小結果本身是產出。框架回報的最短反例通常直接就是缺陷的最小重現案例,可以原樣固定成一個逐例測試,讓這次的具體失敗有一個穩定的回歸點。
設計責任
寫得出來的性質決定了這個測試的射程,所以性質太弱是主要的失效方式。「結果不會是 null」對所有實作都成立,包含錯的那些;把它收緊成「結果的長度等於輸入的長度且每個元素都來自輸入」才開始有鑑別力。判斷方式跟突變測試一致——問這條性質擋不擋得住某個具體的錯誤寫法。
生成器要跟著資料的真實形狀走。預設生成器產出的字串多半是短的隨機字元,而實際輸入可能有多位元組字元、前後空白與極長的內容;生成器沒有涵蓋的形狀,性質再強也驗不到,這與 test data 代表性是同一個問題。連性質都難以陳述時,判準要再退一層到變形關係。