Skip to content

テスト構造ドキュメント ​

テスト 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%

テスト実行コマンド ​

bash
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 ​

設定値
projectsnode / src-node / jsdom(下表)
globalstrue
setupFiles./vitest.setup.ts
poolnode は forks(テストファイルごとに新しい Electron プロセス)、src-node と jsdom は threads(vitest 本体のプロセス内)
maxWorkersmin(論理 CPU 数, 4)(fork と thread を合わせた同時数の上限。VITEST_MAX_WORKERS で上書きできる)
maxConcurrency1(1 ファイル内の test.concurrent の同時数。ワーカーの数ではない)
isolatetrue(false にすると DB と RTL のテストが壊れる)
testTimeout / hookTimeout30000ms
coverageprovider v8、reporter text / lcov / html / json-summary、include は src/**/*.{ts,tsx} と electron/**/*.ts
coverage 閾値lines 70 / functions 70 / branches 60 / statements 70

project の分け方 ​

projectenvironmentpool対象
nodenodeforkselectron/**/__tests__/・electron/__tests__/・scripts/**・tests/e2e/sdk/__tests__/・tests/fixtures/__tests__/・workers/**/__tests__/
src-nodenodethreadssrc/**/*.{test,spec}.{ts,js}
jsdomjsdomthreadssrc/**/*.{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 千)を読み直し、全件テストの時間の大半を占めていたため。

ts
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 にも効く。

人がターミナルから回すときなど、速く回したい場合は上書きする:

bash
VITEST_MAX_WORKERS=16 yarn test:unit

P コアで・低い優先度で・マシン全体で同時 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 コアを空けておきたい作業中など):

bash
WINGSTATS_TEST_PCORE=0 yarn test:unit

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 だけ(リリースコミット)なら全件にしない
  • 関連する差分が無ければスキップして正常終了

モック設定 ​

エイリアス対象
*/imageImportssrc/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 の出力で見る。