概要
denoの標準ライブラリであるdeno_stdにはtestingモジュールが含まれています。
今回はその使い方について紹介します。
deno及びdeno_stdについては、下記バージョンを想定しています。
テストの書き方
export function sum(...numbers: number[]): number {
return numbers.reduce((acc, x) => acc + x, 0);
}
import { test, runIfMain } from 'https://deno.land/std@v0.26.0/testing/mod.ts';
import { assertStrictEq } from 'https://deno.land/std@v0.26.0/testing/asserts.ts';
import { sum } from './sum.ts';
test(function returnsSumOfNumbers() {
const actual = sum(1, 2);
const expected = 3;
assertStrictEq(actual, expected);
});
test(function returnsZeroWhenCalledWithNoArgs() {
const actual = sum();
const expected = 0;
assertStrictEq(actual, expected);
});
runIfMain(import.meta);
- テストファイルの名前にはサフィックスとして
_testをつけます。(※後述)
テストケースの記述
testing/mod.tsでエクスポートされているtest関数を使用します。
下記いずれかの形式でテストを記述します。
関数形式
関数のnameプロパティがテストの名前として扱われます。
test(function returnsSumOfNumbers() {
const actual = sum(1, 2);
const expected = 3;
assertStrictEq(actual, expected);
});
オブジェクト形式
nameプロパティでテストの名前、fnプロパティでテスト関数を指定します。
test({
name: 'sum() returns sum of numbers',
fn() {
const actual = sum(1, 2);
const expected = 3;
assertStrictEq(actual, expected);
}
});
非同期処理のテスト
テスト関数でPromiseを返却します。
返却したPromiseがresolveされた場合は成功、rejectされると失敗とみなされます。
アサーションに失敗するとAssertionErrorが投げられるため、テスト関数をasync関数として定義しておくと、期待どおりに動作してくれます。
test(async function shouldWorkProperly() {
const actual = await someAsyncFunc();
const expected = { msg: 'hello' };
assertEquals(actual, expected);
});
コマンドラインからテストを実行する。
下記コマンドにより、ファイル名が_test.tsで終わるテストがまとめて実行されます。
(--allow-netオプションをつけないと、実行に失敗してしまうようです)
$ deno test --allow-net
require is not definedと表示され、テストが実行されない。
eslint等を使用している関係でnode_modulesディレクトリが存在すると、テストが失敗してしまうことがあります。
その際は、-eオプションで除外対象のディレクトリまたはURLを指定できます。(複数指定したいときは、コンマで区切る)
$ deno test -e './node_modules' --allow-net
アサーション
アサーション関数はtesting/asserts.tsファイルでexportされています。
それぞれの関数はアサーションに失敗すると、AssertionErrorを投げます。
各アサーション関数は、末尾のmsg引数でアサーション失敗時の出力メッセージをカスタマイズできます。
assert(expr: boolean, msg = "")
exprがfalseのとき失敗します。
assertEquals(actual: unknown, expected: unknown, msg?: string)
actualとexpectedの深い比較をし、一致しなければ失敗します。
(actualとexpectedがオブジェクトであれば各プロパティを再帰的に比較、配列であれば各要素を再帰的に比較します。)
assertNotEquals(actual: unknown, expected: unknown, msg?: string)
assertEqualsの否定版
actualとexpectedが一致すると失敗します。
assertStrictEq(actual: unknown, expected: unknown, msg?: string)
actualとexpectedを厳密に比較(actual === expected)し、一致しなければ失敗します。
assertStrContains(actual: string, expected: string, msg?: string)
expectedがactualの部分文字列であれば成功します。
assertMatch(actual: string, expected: RegExp, msg?: string)
actualがexpectedで指定された正規表現にマッチすれば成功します。
assertArrayContains(actual: unknown[], expected: unknown[], msg?: string)
actualがexpectedで指定されたすべての要素を含んでいれば成功します。(要素の順番は問いません)
assertThrows(fn: () => void, ErrorClass?: Constructor, msgIncludes = "", msg?: string)
fnがErrorClassで指定された型の例外を投げ、そのmessageプロパティにmsgIncludesで指定された文字列が含まれていれば成功します。
assertThrowsAsync(fn: () => Promise<void>, ErrorClass?: Constructor, msgIncludes = "", msg?: string)
fnがErrorClassで指定された例外を投げるまたはrejectし、そのmessageプロパティにmsgIncludesで指定された文字列が含まれていれば成功します。
その他(Tips等)
特定のテストファイルのみを実行したい
import { test, runIfMain } from 'https://deno.land/std@v0.26.0/testing/mod.ts';
import { assertStrictEq } from 'https://deno.land/std@v0.26.0/testing/asserts.ts';
import { sum } from './sum.ts';
test(function returnsSumOfNumbers() {
const actual = sum(1, 2);
const expected = 3;
assertStrictEq(actual, expected);
});
runIfMain(import.meta);
テストファイルの末尾でimport.metaを渡してrunIfMain関数を実行します。
すると、下記コマンドにより、対象のテストファイルのみを実行できます。
$ deno run ./sum_test.ts
テストファイルの配置場所について
このあたりはモジュールによって異なるようですが、現状下記のような方式をよく見かけます。
- テスト対象モジュールと同一ディレクトリに配置する。
-
testsディレクトリに配置する。
タイムアウト
現状、タイムアウト機能はありませんが、下記のようなラッパーを用意すれば、対応できます。
function testWithTimeout(fn: () => void | Promise<unknown>, timeout: number = 3000): void {
test({
name: fn.name,
async fn() {
const timeoutPromise = new Promise((resolve, reject) => setTimeout(() => reject(new Error('Timeout exceeded')), timeout));
const testPromise = fn();
await Promise.race([testPromise, timeoutPromise]);
}
});
}
testWithTimeout(async function shouldWorkProperly() {
const actual = await someAsyncFunc();
const expected = { msg: 'hello' };
assertEquals(actual, expected);
}, 2000);
サードパーティモジュールの管理について
現状、下記いずれかの手法を取ることが多いようです。
-
deps.tsというファイルを用意し、すべてのモジュールを一括管理する。
export { test, runIfMain } from 'https://deno.land/std@v0.26.0/testing/mod.ts';
export { assertStrictEq } from 'https://deno.land/std@v0.26.0/testing/asserts.ts';
import { test } from './deps.ts';