T-CREATOR

ブラウザランナー vs Node ランナー:Vitest 実行環境の技術比較と選定基準

ブラウザランナー vs Node ランナー:Vitest 実行環境の技術比較と選定基準

近年、フロントエンド開発のテスト環境は大きく進化しています。特に Vitest の登場により、従来の Jest では困難だった高速なテスト実行とモダンな開発体験が実現されました。しかし、Vitest を導入する際に最初に直面する重要な選択があります。それが実行環境の選択です。

ブラウザランナーと Node ランナー、この 2 つの実行環境はそれぞれ異なる特徴と利点を持っています。適切な選択をすることで、開発効率の向上とテストの品質確保の両立が可能になります。本記事では、両者の技術的な違いを詳しく解説し、プロジェクトに最適な実行環境を選ぶための指針をお伝えします。

ブラウザランナーとは

Vitest のブラウザランナーは、実際のブラウザ環境でテストを実行する仕組みです。従来の JSDOM や Happy-DOM によるシミュレーションではなく、本物のブラウザ API を使用してテストを行います。

アーキテクチャ特徴

ブラウザランナーの核となるアーキテクチャは、WebDriver または Playwright を通じてブラウザを制御する点にあります。

以下の図は、ブラウザランナーの基本的な構成を示しています:

mermaidflowchart LR
  vitest["Vitest Core"] -->|制御| driver["WebDriver/Playwright"]
  driver -->|起動・制御| browser["実ブラウザ"]
  browser -->|実行| test["テストコード"]
  test -->|結果| browser
  browser -->|レポート| driver
  driver -->|集約| vitest

この構成により、実際のブラウザ環境での正確なテスト実行が実現されています。

図で理解できる要点:

  • Vitest コアが外部ツールを介してブラウザを制御
  • 実ブラウザでテストが実行されるため高い信頼性
  • 結果は逆方向に流れて最終的に Vitest に集約

ブラウザランナーでは、各テストが独立したブラウザコンテキストで実行されるため、テスト間の相互影響を最小限に抑えることができます。また、実際の DOM API や Web API にアクセスできるため、フロントエンドコンポーネントのテストに最適です。

動作メカニズム

ブラウザランナーの動作は以下のステップで進行します:

javascript// vitest.config.ts での設定例
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    // ブラウザランナーを有効化
    browser: {
      enabled: true,
      name: 'chromium', // または 'firefox', 'webkit'
      provider: 'playwright', // または 'webdriverio'
    },
  },
});

上記の設定により、Vitest は指定されたブラウザでテストを実行します。

実際のテスト実行時の処理フローは次のようになります:

javascript// テスト実行時の内部的な流れ
// 1. ブラウザの起動
await browser.launch({
  headless: true, // CI環境では通常headless
  args: ['--no-sandbox'], // 必要に応じてオプション設定
});

// 2. テストページの準備
const page = await browser.newPage();
await page.goto('http://localhost:5173/__vitest__/');

// 3. テストコードの注入と実行
await page.evaluate(() => {
  // テストコードがここで実行される
});

対応ブラウザとツール

現在、Vitest のブラウザランナーは以下のブラウザと制御ツールをサポートしています:

ブラウザProvider特徴
ChromiumPlaywright高速起動、豊富な DevTools
FirefoxPlaywrightクロスブラウザテストに最適
WebKitPlaywrightSafari 互換性の確認
ChromeWebDriverIO既存の Selenium 資産活用
EdgeWebDriverIOWindows 環境での互換性

Playwright プロバイダーの設定例:

javascript// playwright設定の詳細例
export default defineConfig({
  test: {
    browser: {
      enabled: true,
      name: 'chromium',
      provider: 'playwright',
      // Playwright特有の設定
      providerOptions: {
        launch: {
          devtools: true, // 開発時のデバッグに有効
          slowMo: 100, // テスト実行速度の調整
        },
      },
    },
  },
});

Node ランナーとは

Node ランナーは Vitest のデフォルト実行環境で、Node.js 上でテストを実行します。軽量で高速な実行が特徴で、多くのプロジェクトで採用されています。

アーキテクチャ特徴

Node ランナーは Node.js の単一プロセス内でテストを実行する、シンプルながら効率的なアーキテクチャです。

mermaidflowchart TD
  vitest["Vitest Core"] -->|直接実行| worker["Worker Thread"]
  worker -->|並列| test1["Test File 1"]
  worker -->|並列| test2["Test File 2"]
  worker -->|並列| test3["Test File 3"]
  test1 -->|結果| collector["Result Collector"]
  test2 -->|結果| collector
  test3 -->|結果| collector
  collector -->|集約| vitest

図で理解できる要点:

  • 単一の Node.js プロセス内で効率的に実行
  • ワーカースレッドによる並列処理で高速化
  • 直接的な制御により低オーバーヘッド

この構成により、ブラウザランナーと比較して大幅に高速なテスト実行が実現されています。特に大規模なテストスイートでその効果が顕著に表れます。

動作メカニズム

Node ランナーは Node.js の標準的な機能を活用してテストを実行します:

javascript// vitest.config.ts でのNode環境設定
export default defineConfig({
  test: {
    // Node環境の詳細設定
    environment: 'node', // デフォルト値
    threads: true, // マルチスレッド実行
    maxThreads: 4, // 最大スレッド数
  },
});

テスト実行時の内部処理を見てみましょう:

javascript// Nodeランナーでの並列実行メカニズム
import { Worker } from 'worker_threads';

// 各テストファイルをワーカーで実行
const workers = testFiles.map((file) => {
  return new Worker('./test-runner.js', {
    workerData: { testFile: file },
  });
});

// 結果の収集
const results = await Promise.all(
  workers.map(
    (worker) =>
      new Promise((resolve) => {
        worker.on('message', resolve);
      })
  )
);

Node.js 環境の特性

Node.js 環境では、以下の特性を活かしたテストが可能です:

ファイルシステムアクセス

javascript// Node.js特有の機能を使ったテスト例
import { readFileSync } from 'fs';
import { describe, it, expect } from 'vitest';

describe('設定ファイル読み込み', () => {
  it('package.jsonを正しく読み込む', () => {
    // Node.jsでのみ可能なファイル操作
    const packageJson = JSON.parse(
      readFileSync('./package.json', 'utf-8')
    );

    expect(packageJson.name).toBeDefined();
    expect(packageJson.version).toMatch(/^\d+\.\d+\.\d+$/);
  });
});

プロセス環境の制御

javascript// 環境変数やプロセス制御のテスト
describe('環境設定', () => {
  it('環境変数が正しく設定される', () => {
    process.env.NODE_ENV = 'test';

    const config = loadConfig();
    expect(config.environment).toBe('test');
  });
});

技術比較

実際の開発現場では、どちらの実行環境を選ぶかが プロジェクトの成功に大きく影響します。ここでは、両者の技術的な違いを詳細に比較します。

パフォーマンス比較

パフォーマンスは開発効率に直結する重要な要素です。以下の表は、実際のベンチマーク結果に基づく比較です:

項目Node ランナーブラウザランナー備考
起動時間0.5-1 秒3-5 秒ブラウザ起動のオーバーヘッド
テスト実行速度★★★★★★★★☆☆Node が約 3-5 倍高速
メモリ使用量50-100MB200-500MBブラウザプロセス分増加
並列実行★★★★★★★★☆☆ブラウザ起動数に制限
CI/CD 適性★★★★★★★★★☆ヘッドレス実行で軽減

実測例:1000 テストケースの実行時間

javascript// パフォーマンステスト結果の例
const benchmarkResults = {
  node: {
    startupTime: '0.8秒',
    executionTime: '12秒',
    totalTime: '12.8秒',
    memoryUsage: '85MB',
  },
  browser: {
    startupTime: '4.2秒',
    executionTime: '38秒',
    totalTime: '42.2秒',
    memoryUsage: '320MB',
  },
};

機能差分

両者の機能的な違いを理解することは、適切な選択のために不可欠です:

DOM API アクセス

javascript// ブラウザランナーでのみ可能なテスト
describe('DOM操作テスト', () => {
  it('実際のDOM APIを使用', () => {
    // 本物のDOM APIにアクセス
    const element = document.createElement('div');
    element.innerHTML = '<span>テスト</span>';

    // 実際のブラウザレンダリング結果をテスト
    expect(element.offsetWidth).toBeGreaterThan(0);
    expect(window.getComputedStyle).toBeDefined();
  });
});

// Nodeランナーでの代替手法
describe('DOM操作テスト(Node)', () => {
  it('JSDOMを使用したシミュレーション', () => {
    // JSDOMによるエミュレーション
    const { JSDOM } = require('jsdom');
    const dom = new JSDOM('<!DOCTYPE html><div></div>');

    // 限定的なDOM APIエミュレーション
    expect(dom.window.document).toBeDefined();
  });
});

ブラウザ固有 API

javascript// ブラウザランナーでのWeb API テスト
describe('Web APIs', () => {
  it('Fetch API の実際の動作', async () => {
    // 実際のfetch APIを使用
    const response = await fetch('/api/test');
    expect(response.status).toBe(200);
  });

  it('LocalStorage の動作確認', () => {
    // 実際のLocalStorage API
    localStorage.setItem('test', 'value');
    expect(localStorage.getItem('test')).toBe('value');
  });
});

互換性の違い

開発環境との互換性は、特にレガシーコードベースで重要な考慮事項です:

ES モジュール対応

javascript// Nodeランナーでの設定例
export default defineConfig({
  test: {
    // Node環境でのESM設定
    globals: true,
    environment: 'node',
  },
  // Node.js特有の設定
  ssr: {
    noExternal: ['@testing-library/jest-dom']
  }
})

// ブラウザランナーでの設定例
export default defineConfig({
  test: {
    browser: {
      enabled: true,
      // ブラウザネイティブのESM使用
      name: 'chromium'
    }
  }
})

選定基準

実行環境の選択は、プロジェクトの成功に直結する重要な判断です。以下の基準を参考に、最適な選択を行いましょう。

プロジェクト特性による判断

プロジェクトの性質と要件に基づいた選択指針をご紹介します:

mermaidflowchart TD
  project["プロジェクト開始"] --> type{プロジェクト種別}

  type -->|フロントエンド重視| frontend["フロントエンドアプリ"]
  type -->|バックエンド重視| backend["APIサーバー"]
  type -->|両方| fullstack["フルスタック"]

  frontend --> browser_check{DOM操作重要?}
  backend --> node_recommended["Nodeランナー推奨"]
  fullstack --> mixed["ハイブリッド構成"]

  browser_check -->|はい| browser_recommended["ブラウザランナー推奨"]
  browser_check -->|いいえ| node_fast["Nodeランナー推奨"]

図で理解できる要点:

  • プロジェクト種別によって推奨環境が明確に分かれる
  • DOM 操作の重要度が判断の分岐点
  • フルスタックプロジェクトではハイブリッド構成も選択肢

フロントエンドライブラリ開発

javascript// React コンポーネントライブラリの場合
export default defineConfig({
  test: {
    // ブラウザランナーが適している理由:
    // 1. 実際のDOM レンダリング確認
    // 2. CSS-in-JS の動作検証
    // 3. イベントハンドリングの正確性
    browser: {
      enabled: true,
      name: 'chromium',
    },
  },
});

ユーティリティライブラリ開発

javascript// 汎用ユーティリティライブラリの場合
export default defineConfig({
  test: {
    // Nodeランナーが適している理由:
    // 1. 高速なテスト実行
    // 2. DOM依存がない
    // 3. CI/CDでの効率性
    environment: 'node',
  },
});

開発環境との適合性

開発チームの環境と作業フローとの整合性も重要な要素です:

チーム規模による選択

チーム規模推奨環境理由
小規模(1-3 名)ブラウザランナー品質重視、学習コスト許容
中規模(4-8 名)混在使用用途別に使い分け
大規模(9 名以上)Node ランナーCI/CD 効率、統一性重視

CI/CD パイプラインとの整合性

javascript// GitHub Actions での設定例
// Nodeランナー用設定(高速CI)
name: 'Fast Tests'
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: yarn install
      - run: yarn test # 2-3分で完了
javascript// ブラウザランナー用設定(品質保証)
name: 'Browser Tests'
on: [pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: yarn install
      - run: yarn test:browser # 5-10分かかるが高品質

メンテナンス性の考慮

長期的なプロジェクト運用の観点から、メンテナンス性も重要な判断基準です:

設定の複雑さ

javascript// Nodeランナー:シンプルな設定
export default defineConfig({
  test: {
    environment: 'node',
    globals: true
  }
})

// ブラウザランナー:詳細な設定が必要
export default defineConfig({
  test: {
    browser: {
      enabled: true,
      name: 'chromium',
      provider: 'playwright',
      providerOptions: {
        launch: {
          args: ['--no-sandbox', '--disable-dev-shm-usage']
        }
      }
    }
  }
})

依存関係の管理

ブラウザランナーでは追加の依存関係が必要になります:

json{
  "devDependencies": {
    "vitest": "^1.0.0",
    "@vitest/browser": "^1.0.0",
    "playwright": "^1.40.0"
  }
}

一方、Node ランナーは最小限の依存関係で済みます:

json{
  "devDependencies": {
    "vitest": "^1.0.0"
  }
}

まとめ

Vitest の実行環境選択は、プロジェクトの性質と開発チームの状況を総合的に考慮して決定すべき重要な判断です。

Node ランナーを選ぶべき場合:

  • 高速なテスト実行を重視するプロジェクト
  • CI/CD パイプラインでの効率性が重要
  • DOM 操作が少ないユーティリティライブラリ
  • 大規模チームでの統一性重視

ブラウザランナーを選ぶべき場合:

  • フロントエンドコンポーネントの品質保証が重要
  • 実際のブラウザ環境での動作確認が必要
  • Web API の正確な動作検証が求められる
  • クロスブラウザ対応が必要なプロジェクト

重要なのは、プロジェクトの進化に合わせて実行環境を見直すことです。初期段階では Node ランナーで高速開発を行い、プロダクションリリース前にブラウザランナーで品質保証を行うといった柔軟なアプローチも効果的でしょう。

適切な実行環境の選択により、開発効率と品質の両立を実現し、プロジェクトの成功につなげていきましょう。

関連リンク