Vitestではじめるテスト

本文書は、Vitestを使ってテストを行うための入門ガイドです。 Vitestを使ってどうやってテストするのか?といった疑問に答えます。 テストを行っていくための最初の一歩になればと思います。

それでは、さっそく学んでいきましょう!

Vitestとは

Vitest公式サイト: https://vitest.dev/

VitestはJavaScriptのテストを行うためのフレームワークです。 2023年現在、JavaScriptを使用する開発者の中で最も関心を集めているテストツールです。

ランキング

―― 画像の出典: 2023.stateofjs.com

事前準備

VitestはNode.jsで実行します。 あらかじめNode.jsの実行環境を構築してからはじめます。

StackBlitzではじめる

次のリンクにアクセスすると、StackBlitzで新しいNode.jsの実行環境を構築できます。

Edit on StackBlitz

StackBlitzではじめる場合は、以降の準備は不要です。

ローカル環境ではじめる

ローカル環境にNode.jsの実行環境を構築する場合、まずはじめにNode.jsをインストールします。 インストール方法はNode.jsのインストール - Node.jsを使うをご参照ください。 プロジェクトの作成方法はpackage.jsonファイル - Node.jsを使うをご参照ください。

はじめてのテスト

ターミナルから npm コマンドでVitestをインストールします。

npm i -D vitest

または

npm install --save-dev vitest

いずれかのコマンドを実行することでVitestがインストールされます。 ここでインストールしたVitestは、このプロジェクトの開発用の依存関係として追加されます。 つまり、これ以降このプロジェクトは npm install コマンドを実行することでVitestを導入できるようになります。

インストールしたVitestは、npx vitest コマンドを使用することで実行できます。

npx vitest

しかし、まだテストが1件も存在しないのでこのコマンドは失敗します。

$ npx vitest
include: **/*.{test,spec}.?(c|m)[jt]s?(x)
exclude:  **/node_modules/**, **/dist/**, **/cypress/**, **/.{idea,git,cache,output,temp}/**, **/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*
watch exclude:  **/node_modules/**, **/dist/**

No test files found, exiting with code 1

実際にテストを作成し、実行していきましょう。

次のファイルを作成します。

// hello.test.js
import { expect, test } from "vitest";

test("1と2の合計は3です", () => {
  expect(1 + 2).toBe(3);
});

この作成した hello.test.js は、npx vitest コマンドを実行するときにテストとして実行されるようになります。

$ npx vitest
 ✓ hello.test.js (1)
   ✓ 1と2の合計は3です

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  16:38:04
   Duration  264ms (transform 20ms, setup 0ms, collect 9ms, tests 2ms, environment 0ms, prepare 68ms)


 PASS  Waiting for file changes...
       press h to show help, press q to quit

問題なく実行できましたか? 実行終了するには、キーボードの q を押します。

 ✓ hello.test.js (1)

画面に表示されたこの部分は「テスト hello.test.js が実行され、そのテストは合格しました ✅」ということを意味しています。

このようにしてVitestは簡単にテストを行うことができます。

はじめてのテストのコードの説明

テストのコードについてより詳しく説明します。

はじめてのテストのコード:

// hello.test.js
import { expect, test } from "vitest";

test("1と2の合計は3です", () => {
  expect(1 + 2).toBe(3);
});

このコードは、「1と2の合計は3です」というテストを意味します。 式 1 + 2 が、 3 と等しいことを検証するテストです。

Vitestを使用するため、最初の行は import 文によって vitest を指定しています。

import { expect, test } from "vitest";

そして、下記のVitestの機能が使われています。

test() 関数

テストを宣言するための関数です。

  • 第一引数には、このテストの説明を人間が読める形式で記述します
  • 第二引数には、テストの本体を記述します

expect() 関数

引数に与えた値を検証します。

expect(<検査される値>).toBe(<期待する値>)

「検査される値」と「期待する値」の同一性を検証します。

このコードは、Vitestの基本的な機能を確認するための極めて単純なテストですが、テスト環境自体の検証を行うことでもあります。 テスト環境の検証は、テストを行う上で最初に確認しておく重要なポイントです。

基本的な機能

Vitestの機能について説明します。

テストファイルの検出

Vitestは、デフォルトで下記のファイルをテストファイルとして検出します。

  • 名前の末尾に .test あるいは .spec の含まれる .js.jsx.ts.tsx ファイル

プロジェクトでのテストコマンドの設定

この設定を行うと、npm test コマンドでVitestを実行できるようになります。

package.jsonscripts プロパティの中を下記のように変更します。

{
  "scripts": {
    "test": "vitest run"
  }
}

NPMコマンドでのテストの実行:

npm test

npx vitest コマンドの実行と同様のテスト結果が得られます。

テストの実践 ――「うるう年」問題

ここでは「うるう年」を判定するモジュールを作成します。 「うるう年」の判定は、通常広く使われている date-fns などのNPMパッケージを使用することが多いですが、ここではテストを学ぶためにあえて自分で実装します。 設計して、テストを書き、コードを書くという一連のステップでより実践的なテストとの付き合い方を学びましょう。

ECMAScriptモジュール

ECMAScriptモジュール (ESM) とは、JavaScriptをモジュールとして再利用できるようにするための仕組みです。

Node.jsでESMを取り扱えるようにするためには package.json ファイルに "type": "module" プロパティを加えます。

{
  "type": "module"
}

このように書き加えると、プロジェクトの .js ファイルはESMとして取り扱われます。

目標の決定

まず「何を作るか」明らかにしましょう。 何を作るか曖昧なまま、ただ無為にソフトウェア開発を進めるとムダを生む恐れがあります。 ムダを生まないためにできるだけ「何を作るか」を明確にしておきましょう。

「うるう年」を判定するということは、「西暦年号がうるう年ならば true を返し、そうでなければ false を返す関数」ということと決めます。

「うるう年」とは何であるかは、ここでは日本の法令を参考にして決めます。 日本の法令上の取り扱いは、明治時代に制定された「閏年ニ関スル件」によって決められています。

神武天皇即位紀元年数ノ四ヲ以テ整除シ得ヘキ年ヲ閏年トス但シ紀元年数ヨリ六百六十ヲ減シテ百ヲ以テ整除シ得ヘキモノノ中更ニ四ヲ以テ商ヲ整除シ得サル年ハ平年トス

―― https://elaws.e-gov.go.jp/document?lawid=131IO0000000090

「神武天皇即位紀元」は、通常の西暦年号でいう紀元前660年を元年とした暦を意味します。したがって「紀元年数ヨリ六百六十ヲ減シテ」とあるのは通常の西暦年号を意味します。 端的に言うと「グレゴリオ暦法に基づいています」ということを意味します。 このままだと大変読みにくいですね。 書き換えると下記のようになります。

西暦年号が4で割り切れる年はうるう年。ただし、西暦年号が100で割り切れる年はうるう年でない。ただし、西暦年号が400で割り切れる年はうるう年。

これを「うるう年」とします。整理するとこうなります。

  • 西暦年号が4で割り切れる年はうるう年
    • たとえば、西暦2024年、2028年、2032年は4で割り切れるので、うるう年です。
  • 西暦年号が4で割り切れない年はうるう年でない
    • たとえば、西暦2021年、2022年、2023年は4で割り切れないので、うるう年ではありません。
  • ただし、西暦年号が100で割り切れる年はうるう年でない
    • たとえば、西暦2100年、2200年、2300年は100で割り切れるので、うるう年ではありません。
  • ただし、西暦年号が400で割り切れる年はうるう年
    • たとえば、西暦2000年、2400年、2800年は400で割り切れるので、うるう年です。

これで「何を作るか」ということが明らかになりました。 それでは、順番にテストとコードを書いていきましょう。

「西暦年号が4で割り切れる年はうるう年」

ファイル isLeapYear.test.js を作成します。 「何を作るか」ということを忘れないようにコメントに転載します。

// isLeapYear.test.js
/** TODO:
西暦年号が4で割り切れる年はうるう年
  たとえば、西暦2024年、2028年、2032年は4で割り切れるので、うるう年です。
西暦年号が4で割り切れない年はうるう年でない
  たとえば、西暦2021年、2022年、2023年は4で割り切れないので、うるう年ではありません。
ただし、西暦年号が100で割り切れる年はうるう年でない
  たとえば、西暦2100年、2200年、2300年は100で割り切れるので、うるう年ではありません。
ただし、西暦年号が400で割り切れる年はうるう年
  たとえば、西暦2000年、2400年、2800年は400で割り切れるので、うるう年です。
*/

うるう年であることを判定するので isLeapYear という名前に決めました。 この名前のモジュールと関数を作成することに決めます。

テストを書いていきましょう。

// isLeapYear.test.js
import { expect, test } from "vitest";

test("西暦年号が4で割り切れる年はうるう年", () => {
  expect(isLeapYear(2024)).toBe(true);
});

これをテストし、失敗することを確認します。 この失敗は、テスト環境自体の検証を行うことでもあります。

NPMコマンドでのテストの実行:

npm test

テストの自動監視:

npx vitest

テスト結果:

 ❯ isLeapYear.test.js (1)
   × 西暦年号が4で割り切れる年はうるう年

失敗しますね。 この失敗によって、次の2点を実証できました。

  • 目標の「西暦年号が4で割り切れる年はうるう年」というテストが実行されること
  • 未実装のコードが意図せず合格しない ❌ ということ

テスト環境の検証は、テストを行う上での重要なポイントです。

それでは、関数を実装していきましょう。 最初からすべての実装を書こうとせず、小さい変更のみで済ませるのがポイントです。

// isLeapYear.js
function isLeapYear(year) {
  return year % 4 === 0;
}

export default isLeapYear;

ファイルを作成したら、テスト側で import 文によって実装した関数を読み込みます。

// isLeapYear.test.js
import { expect, test } from "vitest";
import isLeapYear from "./isLeapYear";

test("西暦年号が4で割り切れる年はうるう年", () => {
  expect(isLeapYear(2024)).toBe(true);
});

テストを実行します。

テスト結果:

 ✓ isLeapYear.test.js (1)
   ✓ 西暦年号が4で割り切れる年はうるう年

これでテストは合格 ✅ しました。 念の為、西暦2024年のケースだけでなくほかのケースもテストしてみましょう。

「西暦年号が4で割り切れる年はうるう年」という目標を達成したと判断したら、コメントからは消しておきます。

// isLeapYear.test.js
import { expect, test } from "vitest";
import isLeapYear from "./isLeapYear";

test("西暦年号が4で割り切れる年はうるう年", () => {
  expect(isLeapYear(2024)).toBe(true);
  expect(isLeapYear(2028)).toBe(true);
  expect(isLeapYear(2032)).toBe(true);
});

/** TODO:
西暦年号が4で割り切れない年はうるう年でない
  たとえば、西暦2021年、2022年、2023年は4で割り切れないので、うるう年ではありません。
ただし、西暦年号が100で割り切れる年はうるう年でない
  たとえば、西暦2100年、2200年、2300年は100で割り切れるので、うるう年ではありません。
ただし、西暦年号が400で割り切れる年はうるう年
  たとえば、西暦2000年、2400年、2800年は400で割り切れるので、うるう年です。
*/

次の目標「西暦年号が4で割り切れない年はうるう年でない」に進めていきます。

テストを書き、実行します。

必要に応じて実装を修正します。

これらのテストも問題なく合格するようになれば、「西暦年号が4で割り切れない年はうるう年でない」という目標も達成したと判断して、コメントから消しておきます。

// isLeapYear.test.js
import { expect, test } from "vitest";
import isLeapYear from "./isLeapYear";

test("西暦年号が4で割り切れる年はうるう年", () => {
  expect(isLeapYear(2024)).toBe(true);
  expect(isLeapYear(2028)).toBe(true);
  expect(isLeapYear(2032)).toBe(true);
});

test("西暦年号が4で割り切れない年はうるう年でない", () => {
  expect(isLeapYear(2021)).toBe(false);
  expect(isLeapYear(2022)).toBe(false);
  expect(isLeapYear(2023)).toBe(false);
});

/** TODO:
ただし、西暦年号が100で割り切れる年はうるう年でない
  たとえば、西暦2100年、2200年、2300年は100で割り切れるので、うるう年ではありません。
ただし、西暦年号が400で割り切れる年はうるう年
  たとえば、西暦2000年、2400年、2800年は400で割り切れるので、うるう年です。
*/

続きの課題

残りの目標に関しても同様に進めていきましょう。

  • ただし、西暦年号が100で割り切れる年はうるう年でない
    • たとえば、西暦2100年、2200年、2300年は100で割り切れるので、うるう年ではありません。
  • ただし、西暦年号が400で割り切れる年はうるう年
    • たとえば、西暦2000年、2400年、2800年は400で割り切れるので、うるう年です。

Vitestの機能

Vitestの代表的な機能を紹介します。

Matcher

マッチャー (matcher) とは、与えた値を検証するためのメソッドです。

同一性の検証

  • toBe … 与えた値との同一性 (===) を検証します
  • toEqual … オブジェクトまたは配列のすべてのプロパティの同一性を再帰的に検証します
  • not … 検証の結果を反転させます

真偽値とそれに類する値の検証

  • toBeNull … null
  • toBeUndefined … undefined
  • toBeDefined … undefined でない (つまり not.toBeUndefined と等価)
  • toBeTruthy … if ステートメントが真であるとみなすもの
  • toBeFalsy … if ステートメントが偽であるとみなすもの

数値

  • toBeGreaterThan … >
  • toBeGreaterThanOrEqual … >=
  • toBeLessThan … <
  • toBeLessThanOrEqual … <=
  • toBeCloseTo … 浮動小数点数の丸め誤差を考慮した同一性

文字列

  • toMatch … 正規表現のパターン

配列と反復可能なオブジェクト

  • toContain … 配列や反復可能なオブジェクトに特定のアイテムが含まれているかを検証します

例外

  • toThrow … 例外を発報するかどうかを検証します

その他

より詳しい情報は Vitest公式リファレンス をご参照ください。

Promise

Vitestは、test に渡す関数の前に async キーワードを記述するだけで、非同期テストを実行できます。

より詳しい情報は Vitest公式ドキュメント resolves をご参照ください。

beforeEach と afterEach

beforeEachafterEach を使用することでテストの実行の前に繰り返し行う準備や、後片付けの処理を宣言できます。

より詳しい情報は Vitest公式ドキュメント Setup and Teardown をご参照ください。

モック

モック関数を使用することでコード間の繋がりをテストできます。

より詳しい情報は Vitest公式ドキュメント Mock Functions をご参照ください。

テストの作法

テストを書くときの代表的な作法を紹介します。

Arrange・Act・Assert (AAA) パターン

テストを書くときの作法の1つです。 準備 (Arrange)・実行 (Act)・検証 (Assert) というプロセスで分けて書きます。 準備・実行・検証をそれぞれ分けて書いておくことで比較的読みやすいテストを書くことができます。

例:

import { expect, test } from "vitest";

test("正しくJSONをパースできる", () => {
  // 準備
  const json = `{ "name": "Claude Monet", "birth": "1840" }`;

  // 実行
  const parsed = JSON.parse(json);

  // 検証
  expect(parsed).toEqual({ name: "Claude Monet", birth: "1840" });
});

参考文献・動画

Vitest

Vitest公式サイト

テスト駆動開発

和田卓人 (2010)「TDD のこころ」

和田卓人 (2020)「TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング」

安井力 (2021)「『テスト自動化とテスト駆動開発』講演動画」

質問・提案・問題の報告

もし何か気になることがあれば、GitHub Issues からお気軽にお寄せください。