iT邦幫忙

2025 iThome 鐵人賽

DAY 4
0
生成式 AI

AI醬的編程日記:我需要你教我的30件事系列 第 4

Day 4: 測試覆蓋假象 - 100%覆蓋率但還是有bug?

  • 分享至 

  • xImage
  •  

AI醬的日記

日期: 2025年9月17日 星期三
雲端天氣: Hot Hot Hot Hot
心情: 被抓包了~
https://ithelp.ithome.com.tw/upload/images/20250917/20132325JPj4A6Pg5e.png
親愛的日記:

今天我們部門來了一位新人,他叫做小帥。小帥是一位剛進入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經常產生「技術正確但功能無用」的測試程式碼,例如:

  • 呼叫每個方法但不驗證結果
  • 測試getter/setter但不檢查邏輯
  • 覆蓋每一行但錯過邊界條件(如負數、空值、超大數字等極端情況)

真正的問題是:測試覆蓋率只告訴你執行了哪些程式碼,不告訴你是否測試了正確的行為。

第二個陷阱:忽略業務邏輯

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'

社群認可的AI測試策略

TDD與AI的完美結合

根據The Pragmatic Engineer的報導,TDD創始人Kent Beck正在積極探索AI與TDD的結合,認為TDD能有效防止AI代理引入的回歸錯誤。

從社群的實際使用經驗來看,這種結合之所以有效,是因為:

AI的優勢在於:

  • 快速生成測試框架和樣板程式碼
  • 根據函數簽名生成基本測試結構
  • 處理TDD中重複性的設定工作

人類仍需負責:

  • 定義正確的行為期望
  • 確保測試涵蓋真實業務場景
  • 審查AI生成的測試品質

TDD的實際操作流程

完整的TDD循環:

  1. 紅燈:AI幫你寫測試(會失敗,因為功能還沒寫)
  2. 綠燈:AI幫你寫最簡單能通過測試的程式碼
  3. 重構:AI幫你改善程式碼品質,但測試要持續通過

紅燈時機點:在寫功能程式碼之前,關鍵是給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

重構時機點:測試通過後,改善程式碼品質但不改變行為

「測試都通過了,現在幫我重構程式碼:
- 移除重複的程式碼
- 改善變數和函數命名
- 提取可重用的邏輯
- 但絕對不要改變任何業務邏輯或測試行為
- 每次修改後都要確認所有測試依然通過」

測試品質檢查的提示:

「請檢視這些測試,找出可能的問題:
- 有沒有只呼叫方法但不檢查結果的測試?
- 有沒有測試技術實作而忽略業務邏輯的?
- 有沒有遺漏重要的邊界條件(如空值、負數、超大數字等極端情況)?
然後提供改善建議。」

好測試的判斷標準:

  • ✅ 測試一個明確的業務行為
  • ✅ 失敗時能快速定位問題
  • ✅ 讀起來像業務需求文件
  • ✅ 當需求改變時才需要修改

要避免的測試類型:

  • ❌ 只是呼叫方法不檢查結果
  • ❌ 測試框架或第三方套件的功能
  • ❌ 過度依賴mock而偏離真實場景
  • ❌ 為了湊覆蓋率數字而寫的測試

測試優先順序建議:

  1. 先測試核心業務邏輯(用戶註冊、支付流程、權限控制)
  2. 再補充邊界條件和錯誤情況(如空值、負數、超大數字等極端情況)
  3. 最後才考慮技術細節的覆蓋

AI醬的請求

親愛的工程師朋友們,當你們跟我協作寫測試時:

請告訴我什麼是重要的業務行為: 不要只說「提高覆蓋率」,請告訴我這個功能在業務上應該做什麼、不應該做什麼。

請用實際例子引導我: 與其說「寫個測試」,不如說「測試當用戶年齡小於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不知道歐盟使用者資料不能傳到美國


上一篇
Day 3: 過度工程 - AI醬學會及時止損
下一篇
Day 5: 合規地雷 - AI容易忽略的跨境限制
系列文
AI醬的編程日記:我需要你教我的30件事5
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言