BETA

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

投稿日:2019-12-20
最終更新:2019-12-20

概要

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/[email protected]/testing/mod.ts';  
import { assertStrictEq } from 'https://deno.land/[email protected]/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等)

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

import { test, runIfMain } from 'https://deno.land/[email protected]/testing/mod.ts';  
import { assertStrictEq } from 'https://deno.land/[email protected]/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/[email protected]/testing/mod.ts';  
export { assertStrictEq } from 'https://deno.land/[email protected]/testing/asserts.ts';  
import { test } from './deps.ts';  
  • dinkdemのような専用ツールを利用する。
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@uki00aの技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう