Help us understand the problem. What is going on with this article?

deno標準テストモジュールの使い方

概要

denoの標準ライブラリであるdeno_stdにはtestingモジュールが含まれています。

今回はその使い方について紹介します。

deno及びdeno_stdについては、下記バージョンを想定しています。

テストの書き方

sum.ts
export function sum(...numbers: number[]): number {
  return numbers.reduce((acc, x) => acc + x, 0);
}
sum_test.ts
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 = "")

exprfalseのとき失敗します。

assertEquals(actual: unknown, expected: unknown, msg?: string)

actualexpectedの深い比較をし、一致しなければ失敗します。
(actualexpectedがオブジェクトであれば各プロパティを再帰的に比較、配列であれば各要素を再帰的に比較します。)

assertNotEquals(actual: unknown, expected: unknown, msg?: string)

assertEqualsの否定版
actualexpectedが一致すると失敗します。

assertStrictEq(actual: unknown, expected: unknown, msg?: string)

actualexpectedを厳密に比較(actual === expected)し、一致しなければ失敗します。

assertStrContains(actual: string, expected: string, msg?: string)

expectedactualの部分文字列であれば成功します。

assertMatch(actual: string, expected: RegExp, msg?: string)

actualexpectedで指定された正規表現にマッチすれば成功します。

assertArrayContains(actual: unknown[], expected: unknown[], msg?: string)

actualexpectedで指定されたすべての要素を含んでいれば成功します。(要素の順番は問いません)

assertThrows(fn: () => void, ErrorClass?: Constructor, msgIncludes = "", msg?: string)

fnErrorClassで指定された型の例外を投げ、そのmessageプロパティにmsgIncludesで指定された文字列が含まれていれば成功します。

assertThrowsAsync(fn: () => Promise<void>, ErrorClass?: Constructor, msgIncludes = "", msg?: string)

fnErrorClassで指定された例外を投げるまたはrejectし、そのmessageプロパティにmsgIncludesで指定された文字列が含まれていれば成功します。

その他(Tips等)

特定のテストファイルのみを実行したい

sum_test.ts
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というファイルを用意し、すべてのモジュールを一括管理する。
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';
  • dinkdemのような専用ツールを利用する。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした