# Day 52-53:verifyPassword 的第一個 Vitest 測試(續);HTML/CSS 基本架構
**Published by:** [雞蛋糕的前端修煉屋](https://paragraph.com/@gcake/)
**Published on:** 2026-02-04
**URL:** https://paragraph.com/@gcake/day-52-53
## Content
今日閱讀:《單元測試的藝術》3/e 2.6 p.56 ~ 2.8 p.64Vitest 生命週期 API書中示範用 Jest beforeEach API 把不同測試條件共同需要初始化的程式碼抽出來,Vitest 也有相對應完全相容的 APIAPI執行時機適用情境潛在問題beforeEach每個測試前• 初始化測試資料
• 建立新的物件實例
• 重置狀態⚠ 如果初始化成本高,會拖慢測試速度afterEach每個測試後• 清理資源
• 關閉連線
• 刪除測試檔案⚠ 如果忘記清理,可能影響其他測試beforeAll所有測試前一次• 建立昂貴的資源(DB連線)
• 載入大型設定檔⚠ 測試間可能共享狀態,導致互相影響afterAll所有測試後一次• 關閉資料庫連線
• 清理共享資源⚠ 如果測試失敗可能不會執行需要在測試前做初始化? ↓ 能用 beforeEach 嗎? ↓ 是 ✅ 用 beforeEach(預設選擇) ↓ 否(速度真的太慢) ↓ 能重構程式碼避免依賴嗎? ↓ 是 ✅ 重構(最佳解法) ↓ 否(物理限制) ↓ ⚠️ 用 beforeAll(最後手段) + 寫清楚的註解說明為什麼 + 確保狀態真的不會被修改 + 加上 afterAll 清理 // password-verifier1.test.js import { describe, it, expect, beforeEach } from "vitest"; import { PasswordVerifier1 } from "./password-verifier1"; describe("Password Verifier", () => { // 以 USE 原則為測試命名 describe("with a failing rule", () => { let verifier; let fakeRule; beforeEach(() => { // 設定測試的輸入 verifier = new PasswordVerifier1(); fakeRule = (input) => ({ passed: false, reason: "fake reason" }); verifier.addRule(fakeRule); }); it("has an error message based on the rule.reason", () => { // 用輸入來呼叫進入點 const errors = verifier.verify("any value"); // 檢查退出點 expect(errors[0]).toMatch("fake reason"); }); // 在同一個退出點檢查額外的最終結果,解決斷言輪盤問題 it("has exactly one error message", () => { // 用輸入來呼叫進入點 const errors = verifier.verify("any value"); // 檢查退出點 expect(errors.length).toBe(1); }); }); }); 執行順序describe("with a failing rule") → let verifier; let fakeRule; (變數宣告) → beforeEach 執行 (第1次) - 賦值給 verifier, fakeRule → it("has an error message...") 執行 → beforeEach 執行 (第2次) - **重新賦值** → it("has exactly one error") 執行 按照範例寫完覺得上下捲動閱讀似乎降低了可讀性,聯想到之前看過的工廠函式概念,嘗試重構改寫:// password-verifier1.test.js import { describe, it, expect, beforeEach } from "vitest"; import { PasswordVerifier1 } from "./password-verifier1"; describe("Password Verifier", () => { // 以 USE 原則為測試命名 describe("with a failing rule", () => { // 用工廠函式取代 beforeEach 初始化每個測試條件需要的輸入參數 function createVerifierWithFakeRule() { const verifier = new PasswordVerifier1(); const fakeRule = (input) => ({ passed: false, reason: "fake reason" }); verifier.addRule(fakeRule); return verifier; } it("has an error message based on the rule.reason", () => { // 用輸入來呼叫進入點 const verifier = createVerifierWithFakeRule(); const errors = verifier.verify("any value"); // 檢查退出點 expect(errors[0]).toMatch("fake reason"); }); // 在同一個退出點檢查額外的最終結果,解決斷言輪盤問題 it("has exactly one error message", () => { // 用輸入來呼叫進入點 const verifier = createVerifierWithFakeRule(); const errors = verifier.verify("any value"); // 檢查退出點 expect(errors.length).toBe(1); }); }); }); 比較三種方案:方案可讀性彈性複雜度重複代碼⭐⭐⭐ 完整獨立⭐⭐⭐ 每個測試都能客製化⭐ 最簡單beforeEach⭐ 需要跳轉閱讀⭐ 難以客製化⭐⭐ 中等工廠函式⭐⭐ 語意清楚⭐⭐⭐ 可以傳參數客製化⭐⭐ 中等註:自己做完工廠函式版本才發現這就是作者緊接著在 2.7 安排的內容,專有名詞是避免「捲動疲勞」,2.7 略讀帶過(可選)拆除嵌套 describe如果確認測試程式已封裝完整且不再需要 describe 結構,可以考慮移除,作者似乎是比較喜歡最後保留 test...expect 簡潔風格,跟 describe...it...expect 的結構化風格各有好處,實作時要拿捏可維護性和可讀性的平衡點今日閱讀: MDN Getting Started Modules 文件 目前進度到 Learn > Getting started modules > Web standards > The web standards modelHTML 基礎HTML 是標記語言,告訴瀏覽器該如何呈現畫面元素 element由起始標籤、內容、結束標籤組成,可以增加一或多個屬性以利設定元素色彩、對齊方式、格線等視覺特性屬性 attribute包在起始標籤內,與元素名稱或其他屬性間有空格,屬性名稱後接 =空元素img 是一種沒有內容的空元素,因為圖片元素是直接把圖檔嵌在 HTML 上,以下列範例程式碼來說,它有開始標籤、兩個屬性、沒有內容、沒有結束標籤
HTML 基本架構
用來表示文字段落清單無排序清單 unordered list: