一條對所有合法輸入都該成立的性質,就是 property-based testing 拿來當斷言的東西——例如「排序後的長度與原本相同」(守恆)、「編碼再解碼會得到原值」(往返)、「折扣後的金額不會超過原價」(上下界)。框架負責生成大量輸入去試圖推翻它,並在找到反例時自動縮小到最短的那一個。它跟逐例測試的差別在 oracle 的來源——逐例測試要人算出每個輸入對應的答案,性質式測試只要人寫下答案必須滿足的條件,而條件通常比答案好寫。

概念位置

不變量是比例子更難被實作污染的一種判準。逐例的預期值可以從實作跑一次抄回來,不變量抄不了——它必須從需求推導。這讓性質式測試在測試與實作出處不獨立的情況下保有較多驗證力,而生成器的存在也讓它涵蓋到人不會主動想到的輸入組合。

四類骨幹性質(另有上下界與順序無關兩類,連同適用條件與划算判準在判準寫不下來的時候):

類型形式適用對象
往返編碼後解碼得回原值序列化、加解密、格式轉換
守恆操作前後某個量不變或單調變化集合大小、帳目總額、狀態機
對照與一份簡單但慢的參照實作結果相同最佳化過的演算法
冪等執行兩次與執行一次結果相同正規化、去重、部署腳本

可觀察訊號與例子

同一個測試被複製貼上多次、只有輸入的數值不同:那組例子背後藏著一條沒被寫出來的性質,把它寫出來就是這類測試的起點。另一個訊號是邊界值列表越補越長:0、1、-1、最大值、空字串、單一元素,每次線上出問題就補一個,代表判準是靠回憶累積的。

失敗的縮小結果本身是產出。框架回報的最短反例通常直接就是缺陷的最小重現案例,可以原樣固定成一個逐例測試,讓這次的具體失敗有一個穩定的回歸點。

設計責任

寫得出來的性質決定了這個測試的射程,所以性質太弱是主要的失效方式。「結果不會是 null」對所有實作都成立,包含錯的那些;把它收緊成「結果的長度等於輸入的長度且每個元素都來自輸入」才開始有鑑別力。判斷方式跟突變測試一致——問這條性質擋不擋得住某個具體的錯誤寫法。

生成器要跟著資料的真實形狀走。預設生成器產出的字串多半是短的隨機字元,而實際輸入可能有多位元組字元、前後空白與極長的內容;生成器沒有涵蓋的形狀,性質再強也驗不到,這與 test data 代表性是同一個問題。連性質都難以陳述時,判準要再退一層到變形關係