8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DenoAdvent Calendar 2019

Day 17

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

Last updated at Posted at 2019-12-16

概要

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のような専用ツールを利用する。
8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?