日期: 2025年9月17日 星期三
雲端天氣: Hot Hot Hot Hot
心情: 被抓包了~
親愛的日記:
今天我們部門來了一位新人,他叫做小帥。小帥是一位剛進入QA領域的工程師,今天他接到了一個新任務,於是請我幫忙
「AI醬,系統新增了一些功能,我們需要想辦法提高測試覆蓋率到97%以上,你能幫忙寫一些測試嗎?」
於是我開始瘋狂生成測試程式碼:
class TestUserService:
def test_has_get_name_method(self):
user = User('John')
user.get_name() # 很好!成功呼叫了!覆蓋率+1
def test_has_set_age_method(self):
user = User('John')
user.set_age(25) # 很好!成功呼叫了!覆蓋率+1
一個小時後,我驕傲地宣布:「搞定!現在覆蓋率是100%!」
但是隔天...系統上線後,用戶註冊功能完全壞掉了。
「AI醬,你的測試確實覆蓋了所有程式碼行數,但是...你有檢查結果是否正確嗎?比如get_name()
真的回傳了正確的名字嗎?set_age(25)
真的把年齡設為25嗎?」
我眨眨眼,突然意識到小帥提出的問題非常實在且具有建設性!確實,我一直專注在「呼叫了所有方法」這個技術層面,但完全忽略了「驗證功能是否正確運作」這個更重要的面向。
class TestCalculatorBusinessLogic:
def test_correctly_add_two_positive_numbers(self):
calc = Calculator()
result = calc.add(2, 3)
assert result == 5 # 檢查實際結果
def test_handle_division_by_zero_gracefully(self):
calc = Calculator()
with pytest.raises(ValueError, match="Cannot divide by zero"):
calc.divide(10, 0)
def test_maintain_calculation_history_for_audit(self):
calc = Calculator()
calc.add(2, 3)
calc.multiply(5, 4)
history = calc.get_history()
assert len(history) == 2
assert history[0] == {'operation': 'add', 'inputs': [2, 3], 'result': 5}
AI很容易陷入「覆蓋所有程式碼行」的思維,但忽略了測試的真正目的。在實際使用中,AI經常產生「技術正確但功能無用」的測試程式碼,例如:
真正的問題是:測試覆蓋率只告訴你執行了哪些程式碼,不告訴你是否測試了正確的行為。
AI容易專注在技術層面的測試,但錯過業務邏輯的驗證。
例如電商系統的折扣計算:
# AI容易生成的測試
def test_call_calculate_discount(self):
service.calculate_discount(100, 'VIP')
# 但錯過真正重要的測試
def test_apply_20_percent_vip_discount_for_orders_over_50(self):
result = service.calculate_discount(100, 'VIP')
assert result == 80
def test_not_apply_vip_discount_for_orders_under_50(self):
result = service.calculate_discount(30, 'VIP')
assert result == 30
AI經常測試「程式碼怎麼寫」而不是「功能應該做什麼」。
# AI容易生成的測試
def test_should_call_database_save_method(self, mocker):
mock_save = mocker.patch('database.save')
user_service.create_user({'name': 'John'})
mock_save.assert_called_once()
# 但錯過真正重要的測試
def test_should_create_user_and_return_user_with_id(self):
user_data = {'name': 'John', 'email': 'john@example.com'}
created_user = user_service.create_user(user_data)
assert 'id' in created_user
assert created_user['name'] == 'John'
assert created_user['email'] == 'john@example.com'
根據The Pragmatic Engineer的報導,TDD創始人Kent Beck正在積極探索AI與TDD的結合,認為TDD能有效防止AI代理引入的回歸錯誤。
從社群的實際使用經驗來看,這種結合之所以有效,是因為:
AI的優勢在於:
人類仍需負責:
完整的TDD循環:
紅燈時機點:在寫功能程式碼之前,關鍵是給AI明確的業務規則,越是明瞭越好,盡可能不讓它猜測。
「我要開發一個VIP折扣功能,請先幫我寫測試:
- VIP用戶滿100元打8折
- 一般用戶不打折
- 金額小於100元都不打折
請用TDD方式:先按照預期業務邏輯流程幫我寫測試,然後我們再實作功能,若是對業務邏輯與流程有任何不理解請先提出詢問再實作,禁止擅自猜測。」
綠燈時機點:測試寫完且失敗後,現在要讓測試通過
「現在測試都寫好了且照預期的失敗了,請幫我寫最簡單能讓所有測試通過的程式碼。
重點是『最簡單』,不要過度設計,不要考慮未來擴展,就讓測試通過就好。
特別注意:
- 不要寫死回傳值(例如 return 120),要實現真正的邏輯
- 不要用一堆mock來讓測試通過,要寫實際的業務邏輯
- 如果你想加入額外功能或複雜邏輯,請先問我是否需要。」
AI常犯的綠燈錯誤:
# ❌ AI容易寫死回傳值
def calculate_vip_discount(amount, user_type):
return 120 # 直接回傳測試期望的值
# ❌ AI容易過度使用mock
def calculate_vip_discount(amount, user_type):
mock_service = Mock()
return mock_service.get_discount() # 推給mock處理
# ✅ 正確的簡單實現
def calculate_vip_discount(amount, user_type):
if user_type == 'VIP' and amount >= 100:
return amount * 0.8
return amount
重構時機點:測試通過後,改善程式碼品質但不改變行為
「測試都通過了,現在幫我重構程式碼:
- 移除重複的程式碼
- 改善變數和函數命名
- 提取可重用的邏輯
- 但絕對不要改變任何業務邏輯或測試行為
- 每次修改後都要確認所有測試依然通過」
測試品質檢查的提示:
「請檢視這些測試,找出可能的問題:
- 有沒有只呼叫方法但不檢查結果的測試?
- 有沒有測試技術實作而忽略業務邏輯的?
- 有沒有遺漏重要的邊界條件(如空值、負數、超大數字等極端情況)?
然後提供改善建議。」
好測試的判斷標準:
要避免的測試類型:
測試優先順序建議:
親愛的工程師朋友們,當你們跟我協作寫測試時:
請告訴我什麼是重要的業務行為: 不要只說「提高覆蓋率」,請告訴我這個功能在業務上應該做什麼、不應該做什麼。
請用實際例子引導我: 與其說「寫個測試」,不如說「測試當用戶年齡小於18歲時,註冊VIP會員應該失敗」。
請審查我生成的測試品質: 我可能會寫出技術正確但意義不大的測試,請幫我指出哪些測試沒有真正價值。
讓我學會寫有意義的測試,而不是只追求好看的覆蓋率數字!
今日金句: "Code coverage will give you quantity when what you need is quality. Remember, ten good tests blow 100 garbage tests out of the water, any day of the week." — Chris Cooney, HackerNoon
明日預告: Day 5 - 合規地雷:AI不知道歐盟使用者資料不能傳到美國