iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
Vibe Coding

神隊友.swift:30 天 Vibe Coding 打造育兒 iOS App 系列 第 24 篇

[Day 24] 規則的每個條件都有測試守著嗎:對留言規則做變異測試,11 個條件拿掉後 9 個會讓測試失敗,找出一個真的缺口

  • 分享至 

  • xImage
  •  

Day 24

文章同步發表在我的個人 Blog

規則的每個條件都有測試守著嗎:對留言規則做變異測試,11 個條件拿掉後 9 個會讓測試失敗,找出一個真的缺口

測試全部通過,代表規則的每一個條件都有被保護嗎?

Day 19 寫的留言規則,14 個新測試全部通過。但「測試通過」只代表目前的規則沒問題,不代表每個條件都有測試在守:如果某個條件被誰不小心拿掉,會有測試失敗嗎?今天用變異測試來回答。

先講結果:規則裡 11 個條件,逐一拿掉後,有 9 個會讓測試失敗;剩下 2 個拿掉後 93 個測試仍然全過。其中一個是真的缺口:文字欄位如果是清單或 map,規則會放行。補上測試之後,這個條件也被守住了。

1. 變異測試是什麼

做法很單純:故意把程式改壞一點點,再跑測試。

  • 測試失敗了:有測試在守這個地方。
  • 測試仍然全部通過:沒有任何測試會發現這個地方不見了,這個地方沒被保護。

Day 14、Day 20 我做過兩次手動版本:放一個違規的假檔案、把一個顏色改壞。這次把它做成系統化的:對規則裡的每個條件都做一次。

2. 做法:每個條件換成 true,跑完整的規則測試

留言與完成的規則,一共有 11 個可以獨立拿掉的條件:

  • 留言:是陣列、剛好多一筆、最多 100 則、舊留言沒被改動、作者是自己、文字是字串、至少 1 字、最多 500 字(8 個)
  • 完成:取消完成時兩欄同為 null、完成的人必須是自己(2 個)
  • 通知:留言或完成時,已知狀態必須清掉(1 個)

每個條件的處理方式:把它換成 true(取消完成那一個換成 false),寫回規則檔,用完整的 93 個規則測試去跑,記錄結果,再換下一個。腳本核心大概長這樣:

for name, original, mutated in MUTANTS:
    open(RULES, "w").write(source.replace(original, mutated, 1))
    result = run("npm --prefix firebase/tests test")
    record(name, "KILLED" if result.failed > 0 else "SURVIVED")
# always restore the original rules file
shutil.copyfile(BACKUP, RULES)

有三個安全措施:改之前先備份規則檔;用 finally 保證一定還原;跑完比對雜湊值,確認規則檔跟備份完全一樣。11 個條件跑在同一個 Firebase Emulator 裡,只連本機,不碰正式環境。

3. 結果

拿掉的條件 結果 對應的測試
是陣列 測試仍通過 沒有
剛好多一筆 測試失敗 不能刪除留言、不能一次加兩則
最多 100 則 測試失敗 超過 100 則被拒
舊留言不能被改寫 測試失敗 不能改寫既有留言
作者是自己 測試失敗 不能冒用對方當作者
文字是字串 測試仍通過 沒有
文字至少 1 字 測試失敗 空字串被拒
文字最多 500 字 測試失敗 501 字被拒
取消完成(兩欄同為 null) 測試失敗 取消完成被接受
完成的人必須是自己 測試失敗 設成對方被拒、只清一欄被拒
留言或完成時清掉已知狀態 測試失敗 留言、完成時 ack 還有值被拒

11 個裡 9 個拿掉後測試會失敗,2 個拿掉後測試仍然全過。

4. 測試沒發現的缺口一:「文字是字串」是真的缺口

拿掉 text is string 之後,所有測試仍然通過。我另外寫了一個暫時的測試,把留言的文字換成不同型別,在拿掉這個條件的規則下試:

文字的型別 結果
清單 通過
map 通過
數字 被擋

清單和 map 都有 size(),所以「長度 1 到 500」的條件擋不住它們;數字沒有 size(),運算會出錯,剛好被擋掉。

這代表:規則本身寫了 text is string,是對的;問題出在測試。沒有任何測試會在這個條件消失時失敗。App 這一端讀取留言時,文字不是字串就會略過那一則,照這個寫法推論,這種留言寫進去之後在畫面上是看不到的(這是從程式推論的,沒有在手機上實測)。

補上測試:文字是清單、數字、map 時,寫入必須被拒絕。補上之後重新拿掉這個條件,新測試失敗了,這個條件現在有測試守著。規則沒有改。

5. 測試沒發現的缺口二:「是陣列」拿掉也沒關係

after is list 補了測試之後,拿掉它測試仍然全過。我試了兩種:comments 是字串、comments 是 map,拿掉這個條件,兩種還是被擋下來。推測原因是後面的條件要用索引取第幾筆,型別不對的時候運算出錯,規則就拒絕了。

所以這個條件在這組規則裡是多餘的。我決定留著:寫明「這裡必須是陣列」比依賴後面的運算出錯來擋,更容易讀。不是每個拿掉後測試仍通過的條件都要處理,有些是多餘的。這裡我只試了字串和 map,其他型別沒有逐一驗證。

6. 這個方法的限制

  • 我只做了一種變異:把條件換成 true。沒有試過把比較運算子改反、把數字改一點點之類的。
  • 只對留言與完成這一段規則做。
  • 「測試失敗」只代表有測試擋住,不代表失敗的原因就是我想要的那個。

7. 目前的狀態

項目 結果
變異 11 個條件,9 個拿掉後測試失敗,1 個補測試後也會失敗,1 個是多餘條件
規則測試 95 個(原本 93 個,新增 2 個)
規則檔 沒有修改,雜湊值與備份一致
verify.sh VERIFY: PASS(規則測試 95、App 測試 172)

明天預告

Day 25 要加登出與登入。目前 App 用 Firebase 匿名登入,登入狀態不見(登出、重裝)之後就找不回同一個身份。打算用 Google 帳號綁定現有的匿名帳號,這樣使用者識別碼不變,用它判斷權限的規則不用改。Apple 登入需要付費帳號才有的 capability,這個專案用免費帳號,所以不列入。明天會寫實作過程和測試方式。

另外簽章約 10/10 到期,到期前後各檢查一次。

參考資源

官方文件


上一篇
[Day 23] 不用兩支手機也能測同步:用 protocol 加記憶體版的後端,在一個測試裡模擬兩個人
系列文
神隊友.swift:30 天 Vibe Coding 打造育兒 iOS App 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言