作品集 → negative-control

系統與工具 · 測試工具negative-control

negative-control

綠燈要先親眼看過它變紅,才算證據。這個工具會故意改壞你的程式,跑那個應該抓到錯的測試,再把程式還原。測試變紅,代表它真的守著某樣東西;如果還是綠的,就代表沒有——現在你知道了。

方法示意:先從副本還原工作中的檔案,再回報判定結果。
原始測試先確認會通過 故意改壞一個指定改動 執行測試指定的測試 還原從副本還原 判定回報結果 抓到了 形同虛設 沒跑起來 測試沒跑起來,不代表它抓到了錯。

左右滑動可以看到整張圖。

方法示意:先從副本還原工作中的檔案,再回報判定結果。
挑戰

測試通過,可能根本沒碰到它聲稱要保護的行為;測試沒跑起來,也可能被誤當成有意義的結果。

我的貢獻

我定義方法、判定結果與安全規則:分清「沒抓到錯」和「根本沒跑」,並從副本還原工作中的檔案;工具由 AI 寫程式代理實作。

證據入口

沿著無效測試的範例、三種判定,以及安全規則背後的真實失敗查看設計依據。

3種判定,不是兩種:抓到了 · 形同虛設 · 沒跑起來
6種早期版本真的出過的錯,現在各自變成一條規則
12種工具破壞自己的方式,全部被它自己的測試抓到 · 2026-09-29
30 → 0用這個方法在 AICC 找到的假綠燈,之後全部清除 · 2026 年 8 月
綠燈為什麼不算證據

測試通過的理由,可能是錯的

tests/test_pricing.py
# 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

測試可能檢查錯的值、檢查一件永遠成立的事,或根本沒跑到它名稱所指的那段程式。這些看起來都跟正常的測試一模一樣:綠燈。

唯一分辨得出來的方法,是把測試聲稱在守的程式改壞,然後看結果。大家通常手動做:改一行、跑測試、再改回來。出錯的,就是最後那一步。

運作方式

你負責寫出破壞,其餘由工具小心做

寫一份配方→改壞一行→跑指定的測試→從副本還原→判定結果
{ "label": "small orders ship free by mistake", "file": "pricing.py", "replace": " return SHIPPING_FEE", "with": " return 0.0", "test": "tests/test_pricing.py::test_small_order_has_a_fee" }
  • 改的是決定,不是措辭。改「選哪一則訊息」,不改訊息本身的文字
  • 要刪掉,不要註解掉。註解掉的那行還留在原始碼裡,搜尋原始碼的測試照樣對得上它
  • 只指定一個測試。這樣一旦判定是綠燈,就直接指出是哪個測試太弱
  • 配方會過時。--check 幾毫秒內就能確認每份配方還對得上程式碼;它也跑在測試組裡,配方一過時,就會自己變紅
三種判定

「沒跑起來」不等於「什麼都沒找到」

red ✓

抓到了

測試在原本的程式上通過,改壞之後失敗。什麼都不用做:這就是目標。

green ✗

形同虛設

所有測試還是通過。該修的是測試,或它用的案例。絕不能只為變紅就改配方。

broken ?

沒跑起來

找不到要改的文字、目標指到專案外面、測試在改壞之前就已經失敗,或是逾時。這不算結果,也絕不會被當成綠燈。

第三種判定最重要。在一長串結果裡,什麼都沒量到的執行,看起來就跟什麼都沒找到的執行一樣;而大家接下來通常會做的,就是把那個測試刪掉。

從失敗來的規則

每一條安全規則,都來自真實的錯誤

從副本還原

早期版本用 git checkout 還原。這會拿回上一次 commit 的內容,結果把同一個檔案裡還沒 commit 的重構整段丟掉。

看結束代碼

在輸出裡搜尋「failed」,會把測試收集階段的錯誤當成綠燈:判定「形同虛設」,但那次執行其實什麼都沒檢查。

清掉位元組碼快取

一秒內做兩次同樣大小的破壞時,第二次跑起來其實還是第一次的破壞,因此被誤判為「形同虛設」。

一次只跑一個

同一個專案上的兩次執行,各自把對方改壞的檔案存成「原始檔」,結果有一處破壞留在程式碼裡。現在專案本身有一道鎖,會拒絕第二次執行。

不離開專案

像 ../x 這樣的路徑,或專案裡的資料夾捷徑,會讓破壞改到專案外的檔案。現在判斷目標時,看的是它實際指到哪裡。

測試要先通過

本來就失敗的測試,不管怎麼改壞都是紅的,卻被算成「抓到了」。現在每個測試都會先在原本的程式上跑一次。

現況與範圍

有什麼、證明了什麼,以及做到哪裡為止

已完成

  • 單一 Python 檔,除了 pytest 不依賴其他套件
  • 配方格式、檢查過時配方的 --check、一次只允許一個執行的鎖
  • 一個完整範例,裡面藏了一個等你找出來的弱測試

已驗證 · 2026-09-29

  • 它自己的 27 個測試都通過
  • 它用 12 種方式破壞自己;每一種都讓測試變紅,檔案也都還原得乾乾淨淨
  • 範例的輸出和說明文件寫的一致
  • 發布前做過審查;修正了什麼、還剩哪些限制,都寫在 README 裡

範圍

  • 它只涵蓋有人想到的破壞;它證明測試能夠失敗,但不會告訴你哪些東西從來沒被測過
  • 它會執行你的測試,所以它不是沙箱:只用在你信任的程式碼上
  • 被跳過的測試仍然會被當成綠燈;程式被強制終止時,也沒辦法還原檔案。遇到時該怎麼做,README 有寫

GitHub 上的 repo 與說明文件是英文。

在新分頁開啟