この記事はこんな方に向けています
- TypeScript/Vitestのテスト実行時間が気になっている方
- 高速化のために何を見直せばよいか、具体例を知りたい方
- 設定やコードを変更したあと、効果を自分の環境で比較したい方
今回は、必要なモジュールから直接importすることで、不要な初期化を避ける例を紹介します。
変更前後を自動で計測する無料サンプル「Vitest CI Practice」を公開しました。小さなプログラムを動かしながら、変更方法と結果の読み方を確認できます。
無料サンプル:Vitest CI Practice v0.1.0
MIT Licenseで公開しています。
importの見直しで、どんな高速化が見込めるのか
複数のモジュールをまとめて再exportするファイルは、一般に「barrel」と呼ばれます。利用側のimportをまとめられる一方、その入口を経由して、必要のないモジュールの初期化まで動く構成もあります。
今回のサンプルには48個のカタログモジュールがあり、それぞれが読み込み時に検索用のindexを作ります。変更前は、それらをまとめたsrc/features/index.tsからimportします。
変更後は、そのテストで必要なモジュールから直接importします。
-import { key01, lookup01 } from '../src/features/index.js'
+import { key01, lookup01 } from '../src/features/feature-01.js'
ソースファイルは.tsですが、このサンプルのimport指定は.js表記です。
試したい仮説は、使わないモジュールの準備処理を避ければ、テストの実行時間を短くできるのではないかということです。外部APIやDBへのアクセス、時間を引き延ばすためのsleepは使っていません。
この方法は、不要な初期化が実行時間に影響している場合に候補になります。一方、時間の大部分がDB通信や別の処理に使われている場合、importだけを変えても効果は小さいかもしれません。
また、barrelが公開APIの入口なら、その入口を通って正しく利用できるかを確認するテストも必要です。すべてを直接importへ置き換えればよい、という意味ではありません。
今回変えるもの・維持するもの
計測スクリプトが全16ファイルのimport先を切り替えるため、利用者が手作業で変更する必要はありません。
| 変えるもの | 維持するもの |
|---|---|
| 各テストのimport先 | 32テスト・48 assertions・16 inline snapshots |
| barrel経由 → 必要なモジュールから直接 | coverage対象と閾値、最大ワーカー数、isolation設定 |
テスト対象をモックに置き換えたり、テストを削除したりする改善は行っていません。実際の実装を使い、読み込み先だけを変更します。
以下では、変更前をbaseline、変更後をcandidateと呼びます。
実行できる環境
ローカルで試すには、Node.jsとnpmが必要です。
| 項目 | 条件 |
|---|---|
| Node.js | 22系の22.12.0以降、24系、または26.0.0以降 |
| npm | 依存ソフトのインストールに使用 |
| Vitest / @vitest/coverage-v8 | 5.0.1 / 5.0.1 |
| Vite / TypeScript | 8.3.0 / 5.9.2 |
| テスト実行 | Node環境、forks、isolate有効、最大4 workers |
Vitestなどの依存ソフトは、npm ciで同梱のlockfileに記録されたバージョンを導入します。
LinuxとWindowsで計測できます。Windowsでは開始から終了までの経過時間を比較し、CPU時間は取得しません。macOSでの実機確認は、まだ行っていません。
OS・アーキテクチャ・Node.js・npmが基準環境と異なる場合は、差を警告として記録します。対応範囲内であれば、完全に同じ環境を用意する必要はありません。対応していないNode.jsや、必要な依存ソフトが不足している場合などは実行を止めます。
変更前後は同じ環境で比較してください。計測中に環境や依存ソフトのバージョンが変わった場合は、比較を無効にします。
サンプルを取得して実行する
この記事の対象版はv0.1.0です。
Releaseページの「Assets」から「Source code (zip)」をダウンロードして展開します。この方法ではGitは不要です。
展開したフォルダでターミナルを開き、次を実行してください。
node --version
npm --version
npm ci
# Check the tests before measurement.
npm run verify:manifest
# Run all six measurements and restore the original tests.
npm run measure
# Check the tests again after measurement.
npm run verify:manifest
npm run measureは1回だけ実行します。 内部で次の6回を自動計測します。
- baseline
- candidate
- baseline
- candidate
- baseline
- candidate
それぞれ3回の中央値を比較します。import先の切り替えと、終了時のファイル復元も自動です。
npm ciは依存関係をインストールするコマンドで、Node.jsやnpm自体を用意するものではありません。
計測中はテストファイルを一時的に書き換えるため、編集を加えていない作業用コピーを使い、同時に別の計測や編集を行わないでください。コマンドが失敗した場合は、理由を確認してから次へ進みます。
Gitで取得する場合は、次の操作で対象版に切り替えてから、同じ実行手順を使ってください。
git clone https://github.com/Issey-Kubota/vitest-ci-practice.git
cd vitest-ci-practice
git checkout v0.1.0
なぜ前後で同じverify:manifestを実行するのか
verify:manifestは、その時点のテストを固定の基準と照合する、読み取り専用のコマンドです。テスト自体を実行するものではありません。
| タイミング | 確認すること |
|---|---|
| 計測前 | テストの期待値やsnapshotなどが基準と一致し、比較を始められる状態か |
| 計測後 | importの切り替えと復元を終えた後も、テストが許可された内容か |
前後で基準が変わるわけではありません。同じ基準を、異なるタイミングで確認しています。成功するとvalid: trueになります。
この照合は、全ファイルがbaselineかcandidateのどちらかにそろっている状態を許可します。そのため、計測後の照合だけで「開始時と完全に同じ内容へ戻った」とは判断できません。
開始時とのバイト単位の一致は、measure自身が別途確認します。 復元結果はsummary.jsonのrestoration.restoredに保存されます。
結果は「完了したか」と「速くなったか」を分けて読む
正常に完了した場合、コマンド出力で次を確認できます。
{
"complete": true,
"validCount": 6,
"invalidCount": 0,
"fatalReasons": []
}
これは確認すべき項目の抜粋です。実際の出力には保存先、警告、計測方法、比較結果も含まれます。
読む順番は次のとおりです。
-
complete: trueかを確認する。 - 有効6回・無効0回で、致命的なエラーがないか確認する。
- baselineとcandidateの
medianWallMsを比較する。 - 保存ファイルで各回の値も確認する。
medianWallMsは経過時間の中央値で、単位はミリ秒です。
complete: trueは、高速化できたという意味ではありません。 変更後が遅くても、計測・検証・復元が正常に終わればtrueです。
警告とCPU時間の見方
| 項目 | 意味 |
|---|---|
reference_environment_differs |
基準環境との違いを記録した警告 |
timing.mode: "wall-only" |
経過時間のみを計測 |
medianCpuMs: null |
CPU時間を取得していない |
警告だけで計測失敗を意味するわけではありません。nullも、CPU時間が0ミリ秒だったという意味ではありません。
Windowsではwall-onlyで比較します。BashによるCPU時間の取得を利用できる非Windows環境では、CPU時間も記録します。
CPU時間は並列ワーカーを含む合計なので、経過時間を上回ることがあります。そのままCIの占有時間や課金時間には読み替えられません。
公開初期のCIでは、中央値が約31.9%短くなった
2026年9月21日のGitHub Actionsで得られた結果です。
| 測定の組 | baseline:経過時間 | candidate:経過時間 |
|---|---|---|
| 1 | 2.226秒 | 1.525秒 |
| 2 | 2.176秒 | 1.517秒 |
| 3 | 2.228秒 | 1.497秒 |
| 中央値 | 2.226秒 | 1.517秒 |
この実行では中央値が0.709秒、約31.9%短くなりました。有効6回・無効0回で、全6回の32テスト・16 snapshotsが成功しました。coverageはlines・statements・functionsが100%、branchesが75%でした。
根拠は2026年9月21日のCI実行です。対象commitは655c467です。
これは当時の版による結果で、この記事で配布するv0.1.0を同じCI環境で測り直した結果ではありません。
実行環境はUbuntu 24.04/Linux x64、Node.js 24.19.0、npm 11.9.0、Vitest 5.0.1で、利用可能な論理CPU数は4でした。
依存取得は計測区間の外に置き、各回でViteキャッシュを削除し、Node compile cacheを無効にしています。ただし、OSのキャッシュやホスト負荷は制御していません。
この短縮率を、別のプロジェクトの効果予測には使えません。別セッションのローカル計測には、変更前5.193秒に対して変更後5.281秒と遅くなった組もありました。
環境や実行を混ぜて集計せず、各回の値も残して判断することが大切です。
保存ファイルはどこから読むか
詳しい結果は、コマンド出力のoutputに表示されたフォルダへ保存されます。
| ファイル | 内容 |
|---|---|
summary.json |
全6回の集計、環境、比較条件、復元結果 |
1-baseline/record.json |
1回目の時間、有効・無効の判定と理由 |
1-baseline/vitest.json |
個々のテストとsnapshotの結果 |
1-baseline/coverage/coverage-summary.json |
coverage集計 |
1-baseline/stdout.txt・stderr.txt
|
実行ログ |
2回目以降も、2-candidate/、3-baseline/のように別々に保存されます。
まずsummary.jsonで全体を確認し、気になる回のrecord.jsonやログを読むと追いやすくなります。
終了コードが0でも、古いJSONを読んでいたり、必要なテストやcoverageがそろっていなかったりすれば無効になります。有効な6回がそろわなければ比較は出しません。
詳しいフィールドとファイルの例は、v0.1.0の出力ガイドにまとめています。
計測前に止まったとき
対応していないNode.js、依存ソフトの不足、サンプルの保護対象ファイルの変更などが原因で、計測前に停止する場合があります。
その場合、次のような状態になります。
{
"complete": false,
"validCount": 0,
"invalidCount": 0,
"comparison": null
}
原因はfatalReasonsを確認してください。有効0・無効0は、計測を一度も始めていないという意味です。6回のテストがすべて失敗したわけではありません。
Node.jsの対応範囲を確認し、依存ソフトが不足している場合はnpm ciを実行します。ファイル変更が原因なら、その内容を確認するか、Releaseから新しい作業用コピーを展開してください。
CRLFとLFの改行の違いは照合時に吸収します。それ以外のコードや期待値の変更は、引き続き検出します。
既知の不具合を検出できるかも確認する
時間の比較とは別に、決まった不具合をテストが見つけられるか確認できます。
npm run failure-check
npm run verify:manifest
期待値137を変えず、実装側の検索結果に1を加える不具合を入れ、指定テストが実値138で失敗することをbaselineとcandidateの両方で確認します。
正常な実装、既知の実装不具合、意図した環境エラーを区別します。単に起動できなかっただけで「不具合を検出できた」とは判定しません。終了後はソースとテストを元に戻します。
この確認は対象2テスト・coverage無効の別実行で、性能計測の6回には含めません。また、既知の不具合1件を検出できても、品質全体を保証するものではありません。
GitHub Actionsで試す場合
クラウド上で試す場合は、リポジトリをforkして手動workflowを実行する方法もあります。
- リポジトリをforkする。
- fork先でGitHub Actionsを有効にする。
- Actions → Vitest validationを開く。
-
Run workflowから
mainを選ぶ。 - 完了したrunのログとArtifactsを確認する。
この手順はfork先のmainを実行します。記事の対象版v0.1.0と同じ内容とは限らないため、対象commitを確認してください。
含まれるworkflowは手動起動のみです。再実行する場合はRe-run jobsではなく、Run workflowから新しいrunを開始します。
Artifactsの保存期間は30日なので、必要な結果は手元に保存してください。詳しくはv0.1.0のCI説明をご覧ください。
自分のプロジェクトで試すなら
この一式は同梱サンプル専用です。自分のコードを置くだけで、遅い原因を自動診断・修正する汎用ツールではありません。
自分の開発へ持ち帰ってほしいのは、次の方法です。
- どこに時間がかかっているかを確認し、変更候補を1つ選ぶ。
- テスト内容、coverage、並列数など、維持する条件を決める。
- 同じ環境で変更前後を複数回計測する。
- 中央値と各回の値を残し、必要な確認を保てたか確かめる。
- 速くならなかった結果も含めて、変更を採用するか判断する。
今回のimport見直しは、不要なモジュール初期化が疑われる場合の一例です。DOM、DB、外部I/O、テスト間の状態・順序依存などは扱っていません。
今後目指していること
今後は、高速化が見込める条件と具体的な変更方法を、実測結果とともに示していきたいと考えています。
importの見直しを足掛かりに、並列実行数や準備処理など、別の改善候補も一つずつ比較する方針です。この記事で紹介するv0.1.0には、import比較を収録しています。
最終的には、「どの変更を試すか」「どう比較するか」「結果から採用を判断するか」を、別の開発者も繰り返し使える形に整えたいと考えています。
任意のプロジェクトの自動診断・自動修正や、有料版の提供は、まだ決めていません。
試していただける方へ
感想や質問、不具合はGitHub Issuesへお願いします。
- どの環境で、どこまで実行できたか。
- 手順や結果の見方で迷った箇所。
- 自分のプロジェクトで試したい改善候補。
- 比較方法が役立った場面や、足りなかった点。
実行できなかった報告も歓迎します。利用した版、OS、Node.js・npmのバージョン、コマンド、エラーの要点を、分かる範囲で添えてください。
会社のコード、個人情報、認証情報を含むログは不要です。公開できる範囲の要約で十分です。