作品集 → negative-control
negative-control
綠燈要先親眼看過它變紅,才算證據。這個工具會故意改壞你的程式,跑那個應該抓到錯的測試,再把程式還原。測試變紅,代表它真的守著某樣東西;如果還是綠的,就代表沒有——現在你知道了。
左右滑動可以看到整張圖。
測試通過,可能根本沒碰到它聲稱要保護的行為;測試沒跑起來,也可能被誤當成有意義的結果。
我定義方法、判定結果與安全規則:分清「沒抓到錯」和「根本沒跑」,並從副本還原工作中的檔案;工具由 AI 寫程式代理實作。
沿著無效測試的範例、三種判定,以及安全規則背後的真實失敗查看設計依據。
測試通過的理由,可能是錯的
# passes today. Also passes with the fee set to 0, # because 0 >= 0 is true. It never checked the fee. def test_small_order_has_a_fee(): assert shipping_fee(50) >= 0 # fixed: now breaking the fee turns it red def test_small_order_has_a_fee(): assert shipping_fee(50) == 8.0
測試可能檢查錯的值、檢查一件永遠成立的事,或根本沒跑到它名稱所指的那段程式。這些看起來都跟正常的測試一模一樣:綠燈。
唯一分辨得出來的方法,是把測試聲稱在守的程式改壞,然後看結果。大家通常手動做:改一行、跑測試、再改回來。出錯的,就是最後那一步。
你負責寫出破壞,其餘由工具小心做
- 改的是決定,不是措辭。改「選哪一則訊息」,不改訊息本身的文字
- 要刪掉,不要註解掉。註解掉的那行還留在原始碼裡,搜尋原始碼的測試照樣對得上它
- 只指定一個測試。這樣一旦判定是綠燈,就直接指出是哪個測試太弱
- 配方會過時。
--check幾毫秒內就能確認每份配方還對得上程式碼;它也跑在測試組裡,配方一過時,就會自己變紅
「沒跑起來」不等於「什麼都沒找到」
抓到了
測試在原本的程式上通過,改壞之後失敗。什麼都不用做:這就是目標。
形同虛設
所有測試還是通過。該修的是測試,或它用的案例。絕不能只為變紅就改配方。
沒跑起來
找不到要改的文字、目標指到專案外面、測試在改壞之前就已經失敗,或是逾時。這不算結果,也絕不會被當成綠燈。
第三種判定最重要。在一長串結果裡,什麼都沒量到的執行,看起來就跟什麼都沒找到的執行一樣;而大家接下來通常會做的,就是把那個測試刪掉。
每一條安全規則,都來自真實的錯誤
早期版本用 git checkout 還原。這會拿回上一次 commit 的內容,結果把同一個檔案裡還沒 commit 的重構整段丟掉。
在輸出裡搜尋「failed」,會把測試收集階段的錯誤當成綠燈:判定「形同虛設」,但那次執行其實什麼都沒檢查。
一秒內做兩次同樣大小的破壞時,第二次跑起來其實還是第一次的破壞,因此被誤判為「形同虛設」。
同一個專案上的兩次執行,各自把對方改壞的檔案存成「原始檔」,結果有一處破壞留在程式碼裡。現在專案本身有一道鎖,會拒絕第二次執行。
像 ../x 這樣的路徑,或專案裡的資料夾捷徑,會讓破壞改到專案外的檔案。現在判斷目標時,看的是它實際指到哪裡。
本來就失敗的測試,不管怎麼改壞都是紅的,卻被算成「抓到了」。現在每個測試都會先在原本的程式上跑一次。
有什麼、證明了什麼,以及做到哪裡為止
已完成
- 單一 Python 檔,除了 pytest 不依賴其他套件
- 配方格式、檢查過時配方的
--check、一次只允許一個執行的鎖 - 一個完整範例,裡面藏了一個等你找出來的弱測試
已驗證 · 2026-09-29
- 它自己的 27 個測試都通過
- 它用 12 種方式破壞自己;每一種都讓測試變紅,檔案也都還原得乾乾淨淨
- 範例的輸出和說明文件寫的一致
- 發布前做過審查;修正了什麼、還剩哪些限制,都寫在 README 裡
範圍
- 它只涵蓋有人想到的破壞;它證明測試能夠失敗,但不會告訴你哪些東西從來沒被測過
- 它會執行你的測試,所以它不是沙箱:只用在你信任的程式碼上
- 被跳過的測試仍然會被當成綠燈;程式被強制終止時,也沒辦法還原檔案。遇到時該怎麼做,README 有寫
GitHub 上的 repo 與說明文件是英文。