
文章同步發表在我的個人 Blog
測試全部通過,代表規則的每一個條件都有被保護嗎?
Day 19 寫的留言規則,14 個新測試全部通過。但「測試通過」只代表目前的規則沒問題,不代表每個條件都有測試在守:如果某個條件被誰不小心拿掉,會有測試失敗嗎?今天用變異測試來回答。
先講結果:規則裡 11 個條件,逐一拿掉後,有 9 個會讓測試失敗;剩下 2 個拿掉後 93 個測試仍然全過。其中一個是真的缺口:文字欄位如果是清單或 map,規則會放行。補上測試之後,這個條件也被守住了。
做法很單純:故意把程式改壞一點點,再跑測試。
Day 14、Day 20 我做過兩次手動版本:放一個違規的假檔案、把一個顏色改壞。這次把它做成系統化的:對規則裡的每個條件都做一次。
true,跑完整的規則測試留言與完成的規則,一共有 11 個可以獨立拿掉的條件:
每個條件的處理方式:把它換成 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 裡,只連本機,不碰正式環境。
| 拿掉的條件 | 結果 | 對應的測試 |
|---|---|---|
| 是陣列 | 測試仍通過 | 沒有 |
| 剛好多一筆 | 測試失敗 | 不能刪除留言、不能一次加兩則 |
| 最多 100 則 | 測試失敗 | 超過 100 則被拒 |
| 舊留言不能被改寫 | 測試失敗 | 不能改寫既有留言 |
| 作者是自己 | 測試失敗 | 不能冒用對方當作者 |
| 文字是字串 | 測試仍通過 | 沒有 |
| 文字至少 1 字 | 測試失敗 | 空字串被拒 |
| 文字最多 500 字 | 測試失敗 | 501 字被拒 |
| 取消完成(兩欄同為 null) | 測試失敗 | 取消完成被接受 |
| 完成的人必須是自己 | 測試失敗 | 設成對方被拒、只清一欄被拒 |
| 留言或完成時清掉已知狀態 | 測試失敗 | 留言、完成時 ack 還有值被拒 |
11 個裡 9 個拿掉後測試會失敗,2 個拿掉後測試仍然全過。
拿掉 text is string 之後,所有測試仍然通過。我另外寫了一個暫時的測試,把留言的文字換成不同型別,在拿掉這個條件的規則下試:
| 文字的型別 | 結果 |
|---|---|
| 清單 | 通過 |
| map | 通過 |
| 數字 | 被擋 |
清單和 map 都有 size(),所以「長度 1 到 500」的條件擋不住它們;數字沒有 size(),運算會出錯,剛好被擋掉。
這代表:規則本身寫了 text is string,是對的;問題出在測試。沒有任何測試會在這個條件消失時失敗。App 這一端讀取留言時,文字不是字串就會略過那一則,照這個寫法推論,這種留言寫進去之後在畫面上是看不到的(這是從程式推論的,沒有在手機上實測)。
補上測試:文字是清單、數字、map 時,寫入必須被拒絕。補上之後重新拿掉這個條件,新測試失敗了,這個條件現在有測試守著。規則沒有改。
after is list 補了測試之後,拿掉它測試仍然全過。我試了兩種:comments 是字串、comments 是 map,拿掉這個條件,兩種還是被擋下來。推測原因是後面的條件要用索引取第幾筆,型別不對的時候運算出錯,規則就拒絕了。
所以這個條件在這組規則裡是多餘的。我決定留著:寫明「這裡必須是陣列」比依賴後面的運算出錯來擋,更容易讀。不是每個拿掉後測試仍通過的條件都要處理,有些是多餘的。這裡我只試了字串和 map,其他型別沒有逐一驗證。
true。沒有試過把比較運算子改反、把數字改一點點之類的。| 項目 | 結果 |
|---|---|
| 變異 | 11 個條件,9 個拿掉後測試失敗,1 個補測試後也會失敗,1 個是多餘條件 |
| 規則測試 | 95 個(原本 93 個,新增 2 個) |
| 規則檔 | 沒有修改,雜湊值與備份一致 |
verify.sh |
VERIFY: PASS(規則測試 95、App 測試 172) |
Day 25 要加登出與登入。目前 App 用 Firebase 匿名登入,登入狀態不見(登出、重裝)之後就找不回同一個身份。打算用 Google 帳號綁定現有的匿名帳號,這樣使用者識別碼不變,用它判斷權限的規則不用改。Apple 登入需要付費帳號才有的 capability,這個專案用免費帳號,所以不列入。明天會寫實作過程和測試方式。
另外簽章約 10/10 到期,到期前後各檢查一次。
官方文件
size() 與索引運算