指南:為代碼變更添加單元測試)
開發(fā)工具桌面應用【免費下載鏈接】desktopFork of GitHub Desktop to support various Linux distributions項目地址https://gitcode.com/gh_mirrors/des/desktop點擊查看免費下載本篇技術指南以 GitHub Desktopdesktop倉庫的 adding-tests.md 為骨架系統(tǒng)講解該項目的測試基礎設施、單元測試的編寫規(guī)范以及針對AppStore狀態(tài)更新這類復雜邏輯的測試策略。讀完本文你將掌握在app/test目錄下創(chuàng)建與組織 Jest 測試模塊、理解yarn test:unit的運行機制并學會如何借助純函數(shù)抽取與現(xiàn)有輔助模塊寫出可長期維護的單元測試。測試基礎設施總覽倉庫中的所有測試都集中在app/test目錄下并按照測試粒度與用途劃分為若干子目錄。根據(jù) adding-tests.md 的說明其組織方式如下unit—— 針對代碼庫中較小單元的單元測試目前占測試總量的絕大多數(shù)。其內(nèi)部子目錄的劃分意圖與app/src/的源碼布局保持一致例如unit/git/對應app/src/lib/git/、unit/stores/對應app/src/lib/stores/但該對應關系并未被嚴格強制且會隨著源碼布局的演進計劃原文檔提及的 #5645 重構而調(diào)整。integration—— 端到端測試涉及啟動應用并通過 UI 自動化驅(qū)動它。此類測試與單元測試的運行環(huán)境相互獨立。除上述兩類測試外還有三個支撐性目錄fixtures—— 存放可直接用于測試的 Git 倉庫如test-repo、repository-with-105-commits、merge-parser等大量預置倉庫與文件見 app/test/fixtures。helpers—— 包含用于搭建、管理與銷毀測試環(huán)境的邏輯模塊例如git.ts、temp.ts、random-data.ts、changes-state-helper.ts以及一批repository-builder-*腳手架。__mocks__—— Jest 的特殊目錄用于存放 Electron API 的 mock 實現(xiàn)僅在被測試代碼需要時生效當前倉庫中僅有一個 electron.ts。此外app/test頂層還有若干基礎設施文件globals.ts、unit-test-env.ts、setup-test-framework.ts、esm-transformer.js、resolver.js它們共同構成了 Jest 的運行環(huán)境具體細節(jié)見下文Jest 配置與運行環(huán)境一節(jié)。單元測試的定位與適用場景單元測試最適合不依賴 DOM 或 Electron API 的純函數(shù)與模塊。當你修改的代碼符合這一特征時就應當考慮為其配套測試。這也正是倉庫中絕大多數(shù)測試的形態(tài)從 app/test/unit 的清單可以看到enum-test.ts、email-test.ts、diff-parser-test.ts、fuzzy-find-test.ts、path-test.ts、status-parser-test.ts等模塊無一例外地對應app/src/下某個具體源模塊。以 enum-test.ts 為例它直接測試app/src/lib/enum.ts導出的parseEnumValue函數(shù)import { parseEnumValue } from ../../src/lib/enum enum TestEnum { Foo foo, Bar bar is the thing, } describe(parseEnumValue, () { it(parses an enum type from a string, () { expect(parseEnumValue(TestEnum, foo)).toBe(TestEnum.Foo) expect(parseEnumValue(TestEnum, bar is the thing)).toBe(TestEnum.Bar) }) it(returns undefined when enum value doesnt exist, () { expect(parseEnumValue(TestEnum, baz)).toBe(undefined) }) })這個例子展示了倉庫單元測試的典型特征describe塊對應被測模塊it塊對應一個具體行為場景斷言使用 Jest 的expect匹配器且測試目標始終是單個函數(shù)。創(chuàng)建新的測試模塊在開始寫測試之前先檢查app/test/unit下是否已有與你所改動的區(qū)域?qū)臏y試模塊。項目約定每個測試模塊對應一個具體的應用模塊命名規(guī)則為[app-module]-test.ts其中[app-module]是被測應用模塊的文件名。如果不存在對應測試模塊則新建一個同名文件并以如下骨架起步describe(module being tested, () { it(can test some code, () { expect(true).toEqual(false) }) })注意這里的斷言expect(true).toEqual(false)是刻意寫錯的當你從 shell 運行yarn test:unit時會看到該用例失敗——這恰恰證明你的新測試文件已被 Jest runner 成功加載并執(zhí)行。確認測試確實在跑之后再把骨架替換成真正有意義的測試邏輯。編寫真實測試時可參考現(xiàn)有測試套件如 app/test/unit 下的各類*-test.ts并遵循以下準則聚焦單一模塊或函數(shù)復雜冗長的單元測試通常是代碼組織不利于測試、或測試本身管得太多的信號。遇到這種情況優(yōu)先考慮重構被測代碼而不是堆砌測試。覆蓋值得長期守護的場景優(yōu)先測試那些一旦回歸會造成實際傷害的行為這類測試能有效防止你的工作被后續(xù)改動意外破壞。保持簡單易讀好的測試本身即是文檔通常不需要代碼注釋來解釋。對測試編寫不熟悉時采用 Arrange-Act-Assert 模式起步先準備輸入與前置狀態(tài)Arrange再執(zhí)行被測代碼Act最后斷言結果Assert。寫作過程中記得反復運行yarn test:unit驗證測試是否符合預期。特定測試場景AppStore中的狀態(tài)更新單元測試中最棘手的場景之一是應用全局狀態(tài)如AppStore的 repository state的更新邏輯——這類邏輯往往隱含著大量上下文與副作用。原文檔給出的核心建議是把復雜的狀態(tài)更新規(guī)則從AppStore中抽取為獨立的純函數(shù)。這樣做有三個關鍵收益抽取后得到的是無隱式狀態(tài)的純函數(shù)其依賴通過參數(shù)顯式聲明簽名即契約遵循接收當前狀態(tài)、產(chǎn)出新狀態(tài)的模式每個函數(shù)只專注于單一職責由于函數(shù)只依賴傳入?yún)?shù)測試時無需搭建龐大的 store 環(huán)境可測性顯著提升。文檔給出的典型案例是updateChangedFiles其源碼位于 app/src/lib/stores/updates/changes-state.ts。該函數(shù)接收當前的IChangesState、IStatusResult以及一個布爾開關clearPartialState返回一個描述應如何變更狀態(tài)的結果對象export function updateChangedFiles( state: IChangesState, status: IStatusResult, clearPartialState: boolean ): ChangedFilesResult { // 以當前工作目錄狀態(tài)構建 file id - file 的映射 const filesByID new Mapstring, WorkingDirectoryFileChange() state.workingDirectory.files.forEach(f filesByID.set(f.id, f)) // 逐個文件合并舊選擇狀態(tài)clearPartialState 為 true 時 // 將部分選中的文件重置為全不選中再按路徑不區(qū)分大小寫排序 const mergedFiles status.workingDirectory.files .map(file { const existingFile filesByID.get(file.id) if (existingFile) { if (clearPartialState) { if ( existingFile.selection.getSelectionType() DiffSelectionType.Partial ) { return file.withIncludeAll(false) } } return file.withSelection(existingFile.selection) } else { return file } }) .sort((x, y) caseInsensitiveCompare(x.path, y.path)) // ... 繼續(xù)處理選擇狀態(tài)與 diff 的保留/清理 }關于返回類型需要說明一點原文檔引用的歷史版本中ChangedFilesResult形如{ workingDirectory, selectedFileIDs, diff }而在當前倉庫中該內(nèi)部類型已演化為包含workingDirectory與selection兩個只讀字段見 changes-state.ts參數(shù)簽名(state, status, clearPartialState)則保持不變。閱讀本文時請以當前源碼為準。調(diào)用方AppStore內(nèi)部隨后把該結果合并進當前狀態(tài)this.repositoryStateCache.updateChangesState(repository, state updateChangedFiles(state, status, clearPartialState) )正是這種純函數(shù) 狀態(tài)合并的模式讓針對狀態(tài)更新的測試變得非常直接。倉庫中對應的測試模塊為 app/test/unit/stores/updates/update-changed-files-test.ts它利用app/test/helpers/changes-state-helper.ts提供的createState、createStatus工廠構造輸入再直接調(diào)用updateChangedFiles并斷言返回的workingDirectory與選擇狀態(tài)import { updateChangedFiles } from ../../../../src/lib/stores/updates/changes-state import { createState, createStatus } from ../../../helpers/changes-state-helper describe(updateChangedFiles, () { it(clears partial selection on file when clearPartialState is true, () { const prevState createState({ workingDirectory: oldWorkingDirectory }) const status createStatus({ workingDirectory: oldWorkingDirectory }) const { workingDirectory } updateChangedFiles(prevState, status, true) const partialFile workingDirectory.findFileWithID(partiallySelectedFile.id) expect(partialFile!.selection.getSelectionType()).toBe( DiffSelectionType.None ) }) })整個測試過程完全不涉及 AppStore 實例或 Electron 運行時——這正是抽取純函數(shù)以提升可測性這一原則在倉庫中的直接落地。Jest 配置與運行環(huán)境yarn test:unit背后由 app/jest.unit.config.js 驅(qū)動的 Jest runner 支撐。理解這份配置有助于你判斷自己的測試文件能否被正確發(fā)現(xiàn)與執(zhí)行module.exports { roots: [rootDir/src/, rootDir/test/], transform: { ^.\\.tsx?$: ts-jest, \\.m?jsx?$: rootDir/test/esm-transformer.js, }, resolver: rootDir/test/resolver.js, testMatch: [**/unit/**/*-test.ts{,x}], moduleFileExtensions: [ts, tsx, js, jsx, json, node], setupFiles: [rootDir/test/globals.ts, rootDir/test/unit-test-env.ts], setupFilesAfterEnv: [rootDir/test/setup-test-framework.ts], reporters: [default, rootDir../script/jest-actions-reporter.js], // For now, github Node modules required to be transformed by jest-esm-transformer transformIgnorePatterns: [node_modules/(?!(github))], testEnvironment: jsdom, }幾個關鍵點testMatch只匹配**/unit/**/*-test.ts{,x}即只有unit目錄下以-test.ts/-test.tsx結尾的文件才會被當作測試運行——這解釋了為什么命名規(guī)則如此重要roots同時包含src/與test/意味著測試可以像上面enum-test.ts那樣通過相對路徑直接導入被測源碼TypeScript 由ts-jest即時轉(zhuǎn)換而github命名空間下的 ESM 依賴則交給 esm-transformer.js 處理測試環(huán)境為jsdomglobals.ts 與 unit-test-env.ts 在用例執(zhí)行前完成全局環(huán)境注入setup-test-framework.ts 則在測試框架就緒后執(zhí)行公共初始化。Electron API 的 mock 機制也是單元測試能夠脫離 Electron 運行的關鍵app/test/__mocks__/electron.ts以jest.fn()為shell、remote、ipcRenderer等模塊提供樁實現(xiàn)。例如shell.moveItemToTrash被替換為 mockipcRenderer.on/send/invoke亦然這樣被測代碼即便觸碰到 Electron API 也不會真正去操作系統(tǒng)或渲染進程而測試可以通過這些jest.fn()斷言調(diào)用行為。測試的輔助設施fixtures 與 helpers對于需要真實 Git 倉庫或更復雜環(huán)境的測試尤其是app/test/unit/git/下的各模塊如status-test.ts、diff-test.ts、branch-test.ts、log-test.ts等倉庫提供了兩套輔助設施fixtures預置的 Git 倉庫與數(shù)據(jù)文件。例如repository-with-105-commits/含 322 個無擴展名對象文件與配套README.md、.sh腳本、merge-parser/204 個解析用.txt文件、test-repo-with-tags/、detached-head/等測試可直接指向這些倉庫執(zhí)行真實的 git 命令。helpers邏輯復用層典型如 git.tsgit 命令封裝、temp.ts臨時目錄管理、repositories.ts、repository-scaffolding.ts倉庫搭建、repository-builder-*系列為分支裁剪、cherry-pick、rebase、pull 等場景生成特定狀態(tài)的倉庫以及databases/、stores/、menus/子目錄下的測試專用基礎設施。測試組織約定與演進方向綜上倉庫的測試約定可以歸納為測試文件一律放在app/test/unit下命名[app-module]-test.ts與app/src中的被測模塊一一對應需要模擬 Electron 時優(yōu)先復用/擴充app/test/__mocks__/electron.ts需要真實倉庫或復雜狀態(tài)時優(yōu)先復用fixtures與helpers而不是在測試內(nèi)部臨時搭建涉及AppStore狀態(tài)更新等復雜邏輯時先抽取純函數(shù)再測試具體模式可參照updateChangedFiles與其測試模塊。原文檔同時指出unit子目錄與app/src布局的對應關系尚未被嚴格定義并會隨源碼布局演進計劃#5645而調(diào)整。這意味著在新增測試目錄時不必過度糾結層級是否與源碼完全鏡像——真正重要的是保持每個測試模塊對應一個應用模塊的粒度約定讓測試與代碼的映射關系清晰可循。贊分享開發(fā)工具桌面應用【免費下載鏈接】desktopFork of GitHub Desktop to support various Linux distributions項目地址https://gitcode.com/gh_mirrors/des/desktop點擊查看免費下載相關推薦Cycle.js遺留代碼單元測試為非響應式代碼添加可靠測試Cycle.js遺留代碼單元測試為非響應式代碼添加可靠測試 你是否還在為遺留代碼的測試覆蓋率發(fā)愁面對非響應式架構的Cycle.js項目如何快速構建可靠的測前端Web框架Klavis 中 GitHub MCP Server 的測試體系單元測試、Schema 快照與 E2E 測試實戰(zhàn)Klavis 中 GitHub MCP Server 的測試體系單元測試、Schema 快照與 E2E 測試實戰(zhàn) 本文以 Klavis 倉庫中 github_AI 應用LLM 網(wǎng)關MCP 服務工具調(diào)用KOReader 單元測試指南busted 測試體系與 ./kodev test 實戰(zhàn)KOReader 單元測試指南busted 測試體系與 ./kodev test 實戰(zhàn) 本文圍繞 doc/Unit_tests.md https://link桌面應用跨平臺嵌入式上一篇深度解析6自由度KUKA機械臂自主搬運系統(tǒng)從理論到實踐的完整指南下一篇Mac鼠標滾輪卡頓終極解決方案Mos讓外接鼠標體驗如絲般順滑創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考