テスト構造ドキュメント
テスト 3 層(Unit / Browser UI / Electron E2E)の保証範囲と使い分けは browser-ui-testing.md を参照。
概要
| 項目 | 値 |
|---|---|
| テストフレームワーク | Vitest 4 |
| 実行ランタイム | electron-as-node (ELECTRON_RUN_AS_NODE, scripts/dev/run-vitest-electron.ts) |
| テスト環境 | 3 project(node / src-node / jsdom。下記「project の分け方」) |
| カバレッジ閾値 | lines / functions / statements 70%、branches 60% |
テスト実行コマンド
yarn test # Vitest (watch mode)
yarn test:unit # Unit テスト一括実行
yarn test:related # push する範囲の差分に関連するテストだけ実行(pre-push フックも使う)
yarn test:ci # CI用(カバレッジ付き。型チェックと lint も含めるなら yarn ci)
yarn coverage # カバレッジレポート生成実行ランタイムについて: これらのテストは electron-as-node で実行され (
scripts/dev/run-vitest-electron.tsがELECTRON_RUN_AS_NODEで vitest を起動)、DB 層をアプリと同じ Electron ランタイムで動かす。better-sqlite3は 13 系から N-API の prebuilt バイナリなので、Node / Electron の ABI に合わせたrebuild は不要(起動時にensure-native-rebuildが Electron から読めることだけ確かめる)。アプリを起動したまま unit テストを回しても衝突は起きない。
vitest runを直接起動しない: ラッパーを通らないと素の Node で動き、アプリと違うランタイムで DB テストを走らせることになる。 DB テストは代わりにskipped (native addon missing)の 1 本だけを走らせるため、以前は「pass」に見えていた。 いまはvitest.setup.tsの beforeEach がこの skip 用テストを失敗に変える(scripts/dev/nativeAddonSkipGuard.ts)。 1 ファイルだけ回すときもyarn test:unit <file> --reporter=verboseで、個々のテストに ✓ が付くのを見る。 承知で skip させたいときだけVITEST_ALLOW_NATIVE_SKIP=1。新しい DB テストで skip 用テストを書くときは同じ名前にする (名前が違うとガードをすり抜ける。scripts/dev/__tests__/nativeAddonSkipGuard.test.tsが全テストの名前を検査する)。
設定ファイル
vitest.config.ts
| 設定 | 値 |
|---|---|
| projects | node / src-node / jsdom(下表) |
| globals | true |
| setupFiles | ./vitest.setup.ts |
| pool | node は forks(テストファイルごとに新しい Electron プロセス)、src-node と jsdom は threads(vitest 本体のプロセス内) |
| maxWorkers | min(論理 CPU 数, 4)(fork と thread を合わせた同時数の上限。VITEST_MAX_WORKERS で上書きできる) |
| maxConcurrency | 1(1 ファイル内の test.concurrent の同時数。ワーカーの数ではない) |
| isolate | true(false にすると DB と RTL のテストが壊れる) |
| testTimeout / hookTimeout | 30000ms |
| coverage | provider v8、reporter text / lcov / html / json-summary、include は src/**/*.{ts,tsx} と electron/**/*.ts |
| coverage 閾値 | lines 70 / functions 70 / branches 60 / statements 70 |
project の分け方
| project | environment | pool | 対象 |
|---|---|---|---|
node | node | forks | electron/**/__tests__/・electron/__tests__/・scripts/**・tests/e2e/sdk/__tests__/・tests/fixtures/__tests__/・workers/**/__tests__/ |
src-node | node | threads | src/**/*.{test,spec}.{ts,js} |
jsdom | jsdom | threads | src/**/*.{test,spec}.{tsx,jsx} |
- src の
.tsテストは既定で node 環境(src-node)。DOM(document/window/ RTL のrenderHook等)が要るファイルは、 先頭に/** @vitest-environment jsdom */を書く。書き忘れるとdocument is not defined等で即落ちるので、黙って壊れることはない .tsxのテストは常にjsdom- native addon(better-sqlite3)を読む
nodeだけが forks。threads で native addon を終了させると segfault するため - Playwright の E2E(
tests/e2e/scenarios/**・tests/e2e/helpers/**・*.e2e.ts)は vitest の対象外
MUI はパスで import する
@mui/material / @mui/icons-material の入口からの import は ESLint の no-restricted-imports で禁止している。 入口 import はテスト 1 ファイルごとに大量のモジュール(icons は約 1 万 1 千)を読み直し、全件テストの時間の大半を占めていたため。
import Box from '@mui/material/Box'; // 正
import { useTheme } from '@mui/material/styles'; // 正
import Close from '@mui/icons-material/Close'; // 正
import { Box } from '@mui/material'; // 誤(lint エラー)ワーカーの上限を 4 にしている理由
AI エージェントが起動した窓なしのプロセスは、Windows が効率コア(開発機の i7-12700 では 4 つ)へ寄せる。 上限なし(論理 CPU 数 = 20)で全件実行すると、20 本の fork が 4 コアを取り合って遅くなるうえ、kill 済みの fork の 後始末も CPU 待ちで進まない。後始末が終わるまで 1 本あたり 50MB 前後のメモリが解放されず、全件実行 1 回で 数百本・十数 GB 溜まっていた(2026-10-10 実測。上限 4 ではピーク 25 本前後・約 1.4GB)。 fork を起動するのは node project だけ(src-node / jsdom は threads)だが、上限は thread にも効く。
人がターミナルから回すときなど、速く回したい場合は上書きする:
VITEST_MAX_WORKERS=16 yarn test:unitP コアで・低い優先度で・マシン全体で同時 2 本まで
ワーカーを 4 に絞っても、E コア 4 つの上では全件が 20 分前後かかり、fork の起動が間に合わずに Failed to start forks worker で push が落ちていた。そこで scripts/dev/run-vitest-electron.ts (test:unit / test:related / push フックはすべてここを通る)が、vitest を起動する前に次を行う(scripts/dev/testScheduling.ts)。
| やること | 内容 |
|---|---|
| P コアに固定 | 自分のプロセスの affinity を P コアだけにする。子の Electron と fork が引き継ぐ。P コアは GetLogicalProcessorInformationEx の EfficiencyClass で判定し、ハイブリッドでない CPU では固定しない(判定結果は %TEMP%\wingstats-test-scheduling\pcore-mask.json にキャッシュ) |
| 優先度を下げる | BELOW_NORMAL。ゲーム・OBS・人の操作が常に先に CPU を取り、テストは空いた分だけ使う |
| 同時 2 本まで | run / related --run の実行は、全セッション共通の枠(%TEMP%\wingstats-test-scheduling\slots)を 1 つ取るまで待つ。持ち主のプロセスが死んでいる枠は奪う。--watch は対象外。CI では待たない |
| 起動タイムアウトのやり直し | 失敗が「ワーカーが 60 秒以内に起動しなかった」(Failed to start forks/threads worker)だけで、走ったテストが全部 pass なら、そのファイルだけ 1 回やり直す。vitest のこのタイムアウトは設定できず、優先度を下げているので Warudo など配信ソフトが CPU を使っていると起きる。テストの失敗が 1 つでも混じっていればやり直さない |
実測(2026-10-10、同じ 40 ファイル): E コアのまま 249 秒 → P コア固定 + BELOW_NORMAL で 33 秒。
P コア固定と優先度下げを外したいとき(P コアを空けておきたい作業中など):
WINGSTATS_TEST_PCORE=0 yarn test:unittest:related(push 範囲の関連テスト)
scripts/dev/test-related.ts(判定は testRelatedSelection.ts、git の収集は testRelatedGit.ts)。
- 対象は push するコミット範囲(merge-base..HEAD)だけ。未コミットの変更・未追跡ファイルは見ない(同じチェックアウトのほかのセッションの書きかけを拾わないため)
--stagedを付けると、これからコミットする index の差分も足す(commit 前用)- 基点の解決順:
TEST_RELATED_BASE環境変数 > upstream(@{u})>origin/develop>origin/main - 削除したファイルは、それを import していたファイルを
git grepで拾って渡す - 次のときは全件(
test:unit)に切り替える: テスト基盤のファイル(vitest.config.ts/vitest.setup.ts/package.json/yarn.lock/vite.config.ts/ 起動ラッパー一式 /src/utils/__mocks__/imageImports.ts。一覧はFULL_RUN_TRIGGERS)が変わったとき、related に渡すファイルが 150(MAX_RELATED_FILES)を超えたとき、基点が解決できないとき package.jsonの差分がversionだけ(リリースコミット)なら全件にしない- 関連する差分が無ければスキップして正常終了
モック設定
| エイリアス | 対象 |
|---|---|
*/imageImports | src/utils/__mocks__/imageImports.ts |
グローバルセットアップ (vitest.setup.ts)
@testing-library/jest-domは DOM がある環境(documentが定義されているとき)だけ読む。node 環境のテストで毎回読むと全件で数十秒かかるためfs.rmSyncのrecursive呼び出しに再試行(maxRetries: 10)を足す(全件実行中のファイルハンドル解放待ちで後片付けが落ちるのを防ぐ。テストだけ)- beforeEach で
skipped (native addon missing)を失敗に変える(scripts/dev/nativeAddonSkipGuard.ts) WINGSTATS_BACKGROUND_WINDOWS=0(バックグラウンド窓モードを切って、実行者による差をなくす)localStorageのモックをグローバルに設定
テストファイル配置規則
- テスト対象のソースファイルと同じディレクトリに
__tests__/フォルダを配置 - またはソースファイルと同階層に
<filename>.test.ts(x)を配置 - 拡張子:
.test.ts,.test.tsx,.spec.ts,.spec.tsx
テストの置き場所
| 置き場所 | 内容 |
|---|---|
electron/**/__tests__/ | main プロセス(DB・IPC・サービス・窓)。node project |
src/**/__tests__/・src/**/*.test.ts(x) | renderer(hook・画面・ログ解析・store・ユーティリティ) |
scripts/** | 開発スクリプト(テスト実行の仕組み・リリース・データ生成など) |
tests/e2e/sdk/__tests__/・tests/fixtures/__tests__/ | E2E SDK とフィクスチャ自体の単体テスト |
workers/**/__tests__/ | Cloudflare Worker の純粋ロジック |
ファイル数・件数は変わり続けるのでここには書かない。一覧は yarn test:unit --reporter=verbose の出力で見る。