概要
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';