「這段去重邏輯的測試都寫了、也都綠燈,怎麼上線之後還是看到重複記錄?」
Day 24 講過一個去重相關的案例,根因是資料庫唯一鍵設計。今天這個案例乍看類似,其實是完全不同的坑:這次的分組鍵邏輯本身沒有錯,錯的是它假設了一種資料樣態,而正式環境的真實資料剛好不符合那個假設——而這種資料樣態,本機的測試資料裡完全不存在。
某段日誌去重邏輯,設計上會把幾個特定欄位組合成一組分組鍵,用來判斷「這是不是同一筆事件」。這段邏輯本身寫得沒問題,本機測試也涵蓋了正常情況、邊界情況、空值情況,全部綠燈。
上線之後,卻在正式環境發現同一件事被記錄成了好幾筆。追查後發現:分組鍵裡有一個欄位,設計上假設它永遠是單純的字串或數字,但正式環境的真實資料裡,這個欄位偶爾會是一段 JSON 格式的內容——而分組鍵組裝邏輯直接把這段內容當成一般字串串接,JSON 內容裡的欄位順序、格式差異,讓「應該是同一筆事件」的兩筆資料組出了不完全一樣的分組鍵字串,因此被誤判成不同事件。
本機的測試資料是人工設計出來的,設計者自然而然會按照「這個欄位應該長什麼樣子」的假設去造資料——而這正是問題所在:造測試資料的人跟寫邏輯的人,共用同一套對「這個欄位會長什麼樣」的假設。 如果這個假設本身就是錯的,測試資料會忠實地複製這個錯誤假設,不會意外造出一筆「假設不成立」的資料。
正式環境的真實資料不一樣——它不是照著任何人的假設產生的,而是照著系統實際運作中會發生的所有可能情況產生的。這正是這個系列反覆講的模式在測試資料層面的樣貌:「本機測試通過」這件事的可信度,取決於測試資料涵蓋的樣態夠不夠廣,而不是測試案例的數量有多少。
用一組對照來看這個差異:
❌ 只靠人工設計的測試資料驗證:
測試涵蓋:正常字串、空值、超長字串
→ 全部符合「這個欄位是單純字串」的假設,
假設本身如果錯了,測試資料不會意外戳破它
✅ 補上「這個欄位在正式環境實際長什麼樣」的查證:
先去正式環境抽樣查證這個欄位的真實資料分布,
發現偶爾會是 JSON 格式的內容,
把這種樣態也納入測試資料,才會讓原本的假設破功
→ 測試資料的來源不是「我覺得它應該長怎樣」,
而是「它實際上長怎樣」
「這段分組鍵邏輯已經測試過、沒問題」這句話裡的『已測試過』,其實只測試過設計者自己想像得到的資料樣態,沒有測試過正式環境真實存在、但設計者沒想到的樣態。 這是主題句在測試資料層面的具體體現——AI(或任何人)給出的「已驗證」結論,可信度取決於驗證時用的資料,涵蓋了多少真實世界的樣態,而不是涵蓋了多少「我以為會發生」的樣態。
修法很直接:分組鍵組裝邏輯要先正規化這個欄位(例如統一轉成固定排序的格式再組鍵),而不是假設它永遠是單純字串;但更重要的紀律是,任何跟「這個欄位長什麼樣」有關的假設,動手寫測試資料前,先去正式環境抽樣查證一次,不要只憑直覺想像。
回想你手上正在測試的某段邏輯:測試資料裡的每個欄位,是照著你的假設造出來的,還是照著正式環境真實的資料分布抽樣出來的?如果是前者,你有多少把握這個假設是對的?
明天是另一種案例:從 legacy 系統裡到處都是的 error_log/trigger_error,遷移成結構化的 logger——這兩種寫法背後藏著一個容易被忽略的行為差異。