<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      為React組件庫引入自動化測試:從零到完善的實踐之路

      為什么我們需要測試?

      我們的 React+TypeScript 業務組件庫已經穩定運行了一段時間,主要承載各類UI展示組件,如卡片、通知等。項目初期,迫于緊張的開發周期,我們暫時擱置了自動化測試的引入。當時團隊成員對組件邏輯了如指掌,即便沒有測試也能游刃有余。

      然而隨著時間推移,問題逐漸顯現。當新成員加入或老組件需要迭代時,我們常常陷入兩難:修改代碼可能破壞原有功能,但不修改又無法滿足新需求。特別是在處理那些具有多種交互狀態的復雜組件時,手動測試變得既耗時又不可靠。這時,引入自動化測試的必要性就凸顯出來了。

      搭建測試環境

      依賴安裝

      我們首先從安裝核心測試依賴開始,這些工具將構成我們測試體系的基礎框架:

      • 測試運行核心:jest和jsdom環境包
      • TypeScript支持:確保類型安全的測試環境
      • React測試工具:專門為React組件設計的測試工具鏈
      npm install jest jest-environment-jsdom @types/jest ts-jest @testing-library/react @testing-library/jest-dom @testing-library/user-event --save-dev
      

      配置Jest

      創建jest.config.ts配置文件時,有幾個關注點:

      • 針對TypeScript項目的特殊處理
      • 瀏覽器環境的模擬
      • 測試初始化流程
      • 文件轉換規則
      module.exports = {
        preset: "ts-jest", // 為 TypeScript 項目準備的 Jest 配置預設
        testEnvironment: "jsdom", // 測試運行在模擬的瀏覽器環境中
        setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"], // 指定在測試環境初始化后立即執行的文件
        transform: {
          "^.+\\.(ts|tsx)$": "ts-jest", // 使用 ts-jest 處理所有 .ts 和 .tsx 文件
        },
        testPathIgnorePatterns: ["/node_modules/", "/dist/"], // 忽略指定目錄下的測試文件
        moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], // 定義 Jest 能識別的模塊文件擴展名
      };
      
      export {}; // 使文件成為模塊
      

      創建jest.setup.ts文件引入斷言庫:

      import "@testing-library/jest-dom";
      

      TypeScript配置

      修改tsconfig.json包含測試相關文件:

      {
        "include": [
          "src",
          "jest.config.ts",
          "jest.setup.ts",
          "__mocks__/**/*.ts"
        ]
      }
      

      測試用例編寫

      我們以一個通知組件為例,該組件有兩種UI形態:

      • 標題和描述組合的時間內容文案提示
      • 帶有喇叭圖標的提示,點擊關閉按鈕時調用接口保存用戶狀態

      特殊依賴處理

      組件中有三類特殊引入需要處理:

      import './index.less';
      import { noticeIcon, closeIcon } from "$src/common/icon";
      import request from "$src/request";
      

      1、處理 CSS/LESS 資源
      Jest 默認無法解析 CSS/LESS 文件,我們可以通過配置將其模擬為空對象:

      // jest.config.js
      module.exports = {
        moduleNameMapper: {
          "\\.(less|css)$': '<rootDir>/__mocks__/styleMock.ts", // 指向一個空文件
        },
      };
      
      // __mocks__/styleMock.ts
      module.exports = {};
      

      2、配置路徑別名
      對于 $src 這樣的路徑別名,需要在 Jest 配置中映射:

      // jest.config.js
      module.exports = {
        moduleNameMapper: {
          '^\\$src/(.*)$': '<rootDir>/src/$1',
        },
      };
      

      3、模擬圖標資源
      對于圖標這類靜態資源,我們可以在測試文件中直接模擬:

      // __tests__/index.test.tsx
      jest.mock('$src/common/icon', () => ({
        noticeIcon: 'notice-icon-path',
        closeIcon: 'close-icon-path',
      }));
      

      4、模擬 API 請求

      對于網絡請求模塊,我們可以將其轉換為 Jest 模擬函數:

      // __tests__/index.test.tsx
      import request from '$src/request';
      
      const mockedRequest = request as jest.MockedFunction<typeof request>;
      
      jest.mock('$src/request', () => ({
        __esModule: true, // 標識這是 ES Module
        default: jest.fn(() => Promise.resolve({ data: {} })),
      }));
      

      通過以上配置,我們能夠有效地隔離組件測試環境,專注于組件邏輯本身的測試,而不受樣式、靜態資源和網絡請求等外部因素的影響。

      基礎測試框架搭建

      我們首先建立測試的基本結構:

      describe("Notification組件", () => {
        // 公共props定義
        const baseProps = {
          body: {},
          tokenId: "test-token",
          urlPrefix: "https://api.example.com",
        };
      
        // 每個測試用例前的清理工作
        beforeEach(() => {
          jest.clearAllMocks();
          mockedRequest.mockReset();
        });
      });
      

      核心測試場景覆蓋

      在配置好 Jest 測試環境后,我們將針對通知組件編寫全面的測試用例。該組件具有兩種展示形態和交互邏輯,我們將從四個關鍵維度進行測試覆蓋:

      1、邊界情況測試

      我們首先考慮最極端的場景——當傳入無效props時,組件是否能夠優雅處理:

      it("當傳入無效body時應安全地返回null", () => {
        const { container } = render(<Notification {...baseProps} body={null} />);
        expect(container.firstChild).toBeNull();
      });
      

      2、日期類型展示驗證
      對于日期類型的通知,我們需要確認:

      • 關鍵文本是否正確渲染
      • DOM結構是否符合預期
      • 樣式類是否準確應用
      it("應正確渲染日期類型通知", () => {
        render(<Notification {...dateProps} />);
        
        expect(screen.getByText("今日公告")).toBeInTheDocument();
        expect(screen.getByText("2023-06-15")).toBeInTheDocument();
        
        const dateContainer = screen.getByText("今日公告").parentElement;
        expect(dateContainer).toHaveClass("notice-header-date");
      });
      

      3、廣播類型交互測試

      廣播通知的測試更加復雜,需要驗證:

      • 初始狀態下的元素展示
      • 圖標資源是否正確加載
      • 點擊關閉后的行為
      describe("BROADCAST_TYPE 類型", () => {
        const broadcastProps = {
          ...baseProps,
          body: {
            type: BROADCAST_TYPE,
            content: "重要通知內容",
            closeUrl: "/close-notice",
          },
        };
      
        it("初始狀態下應該顯示廣播內容", () => {
          render(<Notification {...broadcastProps} />);
      
          // 驗證內容
          expect(screen.getByText("重要通知內容")).toBeInTheDocument();
      
          // 驗證圖片
          const images = screen.getAllByRole("img");
          expect(images[0]).toHaveAttribute("src", "notice-icon-path");
          expect(images[1]).toHaveAttribute("src", "close-icon-path");
      
          // 驗證類名
          const broadcastContainer = screen
            .getByText("重要通知內容")
            .closest(".notice-header-broadcast");
          expect(broadcastContainer).toBeInTheDocument();
        });
      
        it("點擊關閉按鈕后應該隱藏廣播內容", () => {
          render(<Notification {...broadcastProps} />);
      
          // 找到關閉按鈕(假設是最后一個img元素)
          const closeButton = screen.getAllByRole("img")[1].parentElement;
          fireEvent.click(closeButton!);
      
          expect(screen.queryByText("重要通知內容")).not.toBeInTheDocument();
        });
      });
      

      4、網絡請求場景全覆蓋
      對于涉及API調用的場景,我們設計了多維度測試:

      • 正常請求流程
      • 無請求場景請求
      • 失敗處理
      • 請求中的狀態管理
      describe("網絡請求測試", () => {
        const broadcastPropsWithCloseUrl = {
          ...baseProps,
          body: {
            type: BROADCAST_TYPE,
            content: "重要通知內容",
            closeUrl: "/close-notice",
          },
        };
      
        const broadcastPropsWithoutCloseUrl = {
          ...baseProps,
          body: {
            type: BROADCAST_TYPE,
            content: "重要通知內容",
            // 沒有closeUrl
          },
        };
      
        it("點擊關閉時應該發送請求", async () => {
          // 模擬請求成功
          mockedRequest.mockResolvedValue({ data: {} });
      
          render(<Notification {...broadcastPropsWithCloseUrl} />);
      
          const closeButton = screen.getAllByRole("img")[1].parentElement;
      
          await act(async () => {
            fireEvent.click(closeButton!);
          });
      
          //驗證請求參數
          expect(request).toHaveBeenCalledWith({
            url: "https://api.example.com/close-notice",
            method: "post",
            data: {},
            headers: {
              tokenId: "test-token",
            },
          });
      
          // 驗證UI更新
          expect(screen.queryByText("重要通知內容")).not.toBeInTheDocument();
        });
      
        it("當沒有closeUrl時不發送請求", async () => {
          render(<Notification {...broadcastPropsWithoutCloseUrl} />);
      
          const closeButton = screen.getAllByRole("img")[1].parentElement;
      
          await act(async () => {
            fireEvent.click(closeButton!);
          });
      
          expect(request).not.toHaveBeenCalled();
          // 驗證UI仍然會更新
          expect(screen.queryByText("重要通知內容")).not.toBeInTheDocument();
        });
      
        it("請求失敗時仍然關閉通知", async () => {
          // 模擬請求失敗
          mockedRequest.mockResolvedValue(new Error("Request failed"));
      
          render(<Notification {...broadcastPropsWithCloseUrl} />);
      
          const closeButton = screen.getAllByRole("img")[1].parentElement;
      
          await act(async () => {
            fireEvent.click(closeButton!);
          });
      
          // 驗證即使請求失敗,UI也會更新
          expect(screen.queryByText("重要通知內容")).not.toBeInTheDocument();
          expect(request).toHaveBeenCalled();
        });
      
        it("請求期間UI應保持響應", async () => {
          // 創建一個未立即resolve的Promise
          let resolveRequest: any;
          const promise = new Promise((resolve) => {
            resolveRequest = resolve;
          });
          mockedRequest.mockReturnValue(promise);
      
          render(<Notification {...broadcastPropsWithCloseUrl} />);
      
          const closeButton = screen.getAllByRole("img")[1].parentElement;
      
          // 第一次點擊
          fireEvent.click(closeButton!);
      
          // 驗證UI已立即更新
          expect(screen.queryByText("重要通知內容")).not.toBeInTheDocument();
      
          // 完成請求
          await act(async () => {
            resolveRequest({ data: {} });
          });
      });
      

      測試執行與覆蓋率

      基礎測試執行

      在完成通知組件的測試用例編寫后,可以在 package.json 中配置測試腳本:

      {
        "scripts": {
          "test": "jest"
        }
      }
      

      執行 npm run test 命令后,如下圖所示,Jest 會在終端輸出測試結果,包括:

      • 測試文件數量
      • 通過的測試用例數
      • 失敗的測試用例詳情(包含錯誤堆棧信息)

      覆蓋率報告配置

      為了更全面地評估測試質量,可以通過修改 jest.config.ts 啟用覆蓋率統計:

      module.exports = {
        collectCoverage: true, // 啟用覆蓋率收集
        coverageDirectory: "coverage", // 指定覆蓋率報告的輸出目錄
        coverageReporters: ["text", "html", "lcov", "clover"], //指定生成的覆蓋率報告格式
        coverageThreshold: {
          // 設置覆蓋率的最低閾值,如果未達標,Jest 會報錯
          global: {
            // 全局覆蓋率要求
            branches: 80,
            functions: 80,
            lines: 80,
            statements: 80,
          },
          "./src/components/**/*.tsx": {
            // 針對特定目錄/文件設置更高要求
            branches: 90,
            functions: 90,
            lines: 90,
            statements: 90,
          },
        },
      }  
      

      執行測試后:終端會顯示各維度的覆蓋率百分比,在 coverage/ 目錄下生成詳細報告:index.html 提供可視化分析可逐層查看未覆蓋的代碼路徑。

      示例輸出中顯示 common/util.ts 僅 32.39% 覆蓋率,低于預設閾值。此時應該優先補充核心工具函數的測試用例。通過持續完善測試覆蓋,可以有效提升組件迭代的可靠性,并為后續重構提供安全保障。

      通過引入自動化測試,我們實現了從"人肉測試"到系統化保障的轉變。精心設計的測試用例覆蓋了各種邊界情況,配合覆蓋率分析,構建了多層次的質量防護體系。

      如果你對前端工程化有興趣,或者想了解更多相關的內容,歡迎查看我的其他文章,這些內容將持續更新,希望能給你帶來更多的靈感和技術分享~

      posted @ 2025-05-05 20:35  一顆冰淇淋  閱讀(291)  評論(2)    收藏  舉報
      主站蜘蛛池模板: 日韩欧美视频一区二区三区| 午夜成人理论无码电影在线播放| 国产一级黄色片在线观看| 中文字幕国产精品日韩| av在线播放国产一区| 亚洲一区二区精品极品| 乱码精品一区二区三区| 肉色丝袜足j视频国产| 激情的视频一区二区三区| 日韩精品中文字幕人妻| 四虎影视永久在线精品| 久久精品夜色国产亚洲av| 国产不卡一区二区四区| 晴隆县| 鲁一鲁一鲁一鲁一澡| 亚洲精品成人福利网站| 97亚洲熟妇自偷自拍另类图片| 人妻日韩人妻中文字幕| 国产精品制服丝袜无码| 国产三级视频网站| 亚洲av无码精品色午夜蛋壳| 久久综合色之久久综合色| 成人一区二区不卡国产| 孕妇怀孕高潮潮喷视频孕妇| 国产特色一区二区三区视频| 男人的天堂av社区在线| 狠狠色综合久久狠狠色综合| 亚洲欧美日韩愉拍自拍美利坚| 韩国福利片在线观看播放| 99精品高清在线播放| 中文字幕免费不卡二区| 亚洲中文字幕精品第一页| 在线a人片免费观看| 国内外成人综合免费视频| 国产情侣激情在线对白| 潮喷失禁大喷水无码| 又大又粗又爽的少妇免费视频| 亚洲精品漫画一二三区| √天堂资源地址在线官网| 日日麻批免费40分钟无码| 亚洲天堂精品一区二区|