CircleCI Orbsを使って、Vitestのテストをシンプルに実装する

CircleCI Orbsを使って、Vitestのテストをシンプルに実装する

AIコーディングが広まる中、コードレビューや自動テストなどの品質評価フェーズへの注目が高まりつつあります。今回は CircleCI で Vitestによるユニットテストを実行するケースを例に、できるだけ少ない設定コードで効果的なワークフローやレポートを得る方法を紹介します。

前提

Vite + TypeScript プロジェクトが既にあり、GitHub リポジトリに push すると CircleCI が動く状態になっているものとします。とはいえこのプロジェクトは、npm create viteでセットアップした直後の、シンプルなTypeScriptプロジェクトを想定しています。

% npm create vite

> first-cci-app@0.0.0 npx
> "create-vite"

│
◇  Project name:
│  first-cci-app
│
◇  Select a framework:
│  Vanilla
│
◇  Select a variant:
│  TypeScript
│
◇  Use rolldown-vite (Experimental)?:
│  No
│
◇  Install with npm and start now?
│  No
│
◇  Scaffolding project in /Users/hidetaka/sandboxes/first-cci-app/...
│
└  Done. Now run:

  cd tmp
  npm install
  npm run dev

ViteアプリにVitestで結合テストを導入する

Viteでセットアップしたばかりのアプリなので、ユニットテストをかける純粋関数がありません。そこで今回はVitestやJSDOMを利用した簡単な結合テストを実装してみましょう。

Step 1 – Vitest を導入する

まずはVitestとJSDOMなどの関連ライブラリを追加します。

npm install -D vitest @vitest/ui jsdom

続いてpackage.jsonscriptsにVitestを実行するコマンドを追加しましょう。CIやAIコーディングで利用する1回限りの実行コマンドと、人間がローカルで開発することを想定したWatchモードの2つを追加しておきます。

{
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "test": "vitest run",
    "test:watch": "vitest"
  }
}

Step 2 – TypeScript と Vitest の型設定

続いて TypeScript の設定を行いましょう。tsconfig.json に Vitest の型を追加しておきます。

{
  "compilerOptions": {
    // ...
    "types": ["vite/client", "vitest/globals"]
  },
  "include": ["src"]
}

これで、describeitexpect などを TypeScript で型エラーなく使えるようになります。

その後、vite.config.ts を更新しておきましょう。

import { defineConfig } from 'vite'

export default defineConfig({
  test: {
    globals: true,
    environment: 'jsdom',
    exclude: ['**/node_modules/**', '**/dist/**', '**/e2e/**'],
    reporters: ['verbose', 'junit'],
    outputFile: {
      junit: 'test-results/junit.xml',
    },
  },
})

globals: true を指定すると、describeitexpect をグローバルに使えるようになります。environment: 'jsdom' は、document.createElement など DOM を触るテスト用の設定です。

また、reporters: ['verbose', 'junit']outputFile.junit を追加しています。これはあとで紹介するCircleCIでテスト結果をチェックするために、テスト結果をJUnit XML として test-results/junit.xml に出力する設定です。

Step 3 – シンプルなテストを追加する

準備が整ったので、簡単なテストを実装しましょう。npm create viteでセットアップされりプロジェクトには、src/counter.tssetupCounterという関数が用意されています。この関数に対するテストを、 src/counter.test.ts として追加してみましょう。

// src/counter.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { setupCounter } from './counter'

describe('setupCounter', () => {
  let button: HTMLButtonElement

  beforeEach(() => {
    button = document.createElement('button')
    document.body.appendChild(button)
  })

  it('初期状態でカウントが0と表示される', () => {
    setupCounter(button)
    expect(button.innerHTML).toBe('count is 0')
  })

  it('クリックするとカウントが1増える', () => {
    setupCounter(button)
    button.click()
    expect(button.innerHTML).toBe('count is 1')
  })

  it('複数回クリックするとカウントが正しく増える', () => {
    setupCounter(button)
    button.click()
    button.click()
    button.click()
    expect(button.innerHTML).toBe('count is 3')
  })

  it('カウンターは独立して動作する', () => {
    const button1 = document.createElement('button')
    const button2 = document.createElement('button')
    document.body.appendChild(button1)
    document.body.appendChild(button2)

    setupCounter(button1)
    setupCounter(button2)

    button1.click()
    button1.click()
    button2.click()

    expect(button1.innerHTML).toBe('count is 2')
    expect(button2.innerHTML).toBe('count is 1')
  })
})

このテストコードが動くかどうかをテストしてみましょう。

npm run test

テスト結果はCLI画面だけでなく、test-results/junit.xml としても出力されます。これはCircleCIのパイプラインにて都度読み込みさせるファイルですので、.gitignoretest-results/を追加して除外しておきましょう。

Step 4 – CircleCI のジョブにユニットテストを追加する

テストコードの準備もできましたので、CircleCIのパイプラインで実行するようにしましょう。

.circleci/config.ymlファイルを作成し、以下のコードを追加しましょう。

version: 2.1
orbs:
  node: circleci/node@7.2.1

workflows:
  test:
    jobs:
      - node/test:
          name: integration_test
          test-results-path: test-results

とてもシンプルな定義になりました。これはCircleCIが提供するジョブやコマンドをまとめた仕組み「CircleCI Orb」から、Node.jsアプリ向けのOrbであるcircleci/nodeを利用しているためです。このOrbを利用することで、npm installやインストール時のキャッシュ管理、そしてテスト実行時の制御やテスト実行結果の保存などのステップを、CircleCI側が提供したジョブ定義に基づいて実行できます。

test-results-pathは、Vitest実行時に生成された junit.xmlが保存されるディレクトリを指定します。これによって中の junit.xml を CircleCI が自動検出して、テストタブに表示してくれます。

念の為、CircleCI Local CLIを利用して設定ファイルにミスがないかもチェックしておきましょう。

% circleci config validate                    
Config file at .circleci/config.yml is valid.

これで CircleCIを利用したパイプラインの準備が完了しました。あとはGitでcommitし、githubへpushしておきましょう。

Step 5 – CircleCIのパイプラインで結果をチェックする

CircleCIとGitHubリポジトリの連携が完了していれば、このようにパイプラインが実行されます。

JUnitを利用したレポート出力設定を行っていると、Testタブにてテストの詳細がチェックできるようになります。ここをチェックすることで、プロジェクトのCI パイプラインが健全かどうかが簡単に確認できます。

時間のかかるテストや、失敗しがちなテストのレポートも見れます。もしCIパイプラインの完了時間が長いと感じることがあれば、これらのレポートをチェックして時間のかかっているテストがないかを調査しましょう。

CircleCI Orbsを活用して、巨人の肩を乗りこなそう

このように、CircleCIを利用したCIパイプラインの設定は、思っているよりも少ない設定で最大限の効果を得ることができます。自前でCIパイプラインの内容を設定するイメージが多いかもしれませんが、このようにCircleCIが提供するcircleci/XXXのOrbを活用することで、やりたいタスクの定義を非常にシンプルかつ少ないコードで実現できます。

AIコーディングの普及に伴い、生成されたコードの品質チェックを行うゲートウェイとしてCIパイプラインやDevOpsの注目度はこれからより高まると思われます。しかしCIパイプラインの設定や運用保守などは、差別化につながらない重労働であるともいえ、できるだけシンプルかつチームで保守性の高い構成を目指したいポイントです。

CircleCIの場合、CircleCIが提供するOrbsを活用することで、コマンドベースの設定ではなく「実現したいタスク内容」だけを定義する形でパイプラインを構築できます。また、CircleCIがOrbsとしてジョブ内容を保守するため、キャッシュ戦略などのベストプラクティスにも簡単に従うことができるのも便利です。

新しいCLIコマンドやツールを覚えるのは億劫になりがちですが、巡り巡って効率化につながる要素として、ぜひお試しください。

参考リンク

Hidetaka Okamoto profile photo

Hidetaka Okamoto

ビジネスデベロップメント

DigitalCubeのBizDev。EC ASPの開発やStripeのDeveloper Advocateとしての経験を元に、SaaSやECサイトの収益を増やすための方法・生成AIを使った効率化や新しい事業モデルの模索などに挑戦する。