ここまで数々の動作確認をしてきましたが、[[console.log]]の確認だけでは限界があります。値が期待値通りかを確かめるため、テストコードの書き方を学びましょう。通常は[[Jest]]や[[Vitest]]を使うことが多いですが、[[Deno]]にはビルトインのテストランナー [[deno test]] がバンドルされているため、それを使います。
## Reference
<div class="link-card-v2">
<div class="link-card-v2-site">
<img class="link-card-v2-site-icon" src="https://docs.deno.com/favicon.ico" />
<span class="link-card-v2-site-name">Deno</span>
</div>
<div class="link-card-v2-title">
Testing
</div>
<div class="link-card-v2-content">
In-depth documentation, guides, and reference materials for building secure, high-performance JavaScript and Typ ...
</div>
<img class="link-card-v2-image" src="https://docs.deno.com/img/og.webp" />
<a href="https://docs.deno.com/runtime/fundamentals/testing/"></a>
</div>
## Lesson
以下が最小限のテストコードです。
`main.js`
```js
import { assertEquals } from "jsr:@std/assert";
Deno.test("test", () => {
const x = 1 + 2;
assertEquals(x, 3);
});
```
ファイルを指定して実行します。
```console
$ deno test main.js
running 1 test from ./main.js
test ... ok (0ms)
ok | 1 passed | 0 failed (2ms)
```
`--watch` フラグをつけると、コード変更時に自動でテストが再実行されるので便利です。
```console
deno test --watch main.js
```
もっと凝った書き方もありますが、[[📒TDQ]]を進める上ではイコールかの判定ができれば十分でしょう。
[[Jest]]のような `expect(...).toBe` スタイルが好みの場合は `jsr:@std/expect` をインポートしましょう。
```js
import { expect } from "jsr:@std/expect";
Deno.test("test", () => {
const obj = { a: 1 };
const obj1 = { a: 1 };
const obj2 = { a: 1, hoge: undefined };
const obj3 = { a: 2 };
// toBeはオブジェクトの同一性を見る
expect(obj).toBe(obj);
expect(obj).not.toBe(obj1); // 値がまったく同じでも参照が異なるのでNG
expect(obj).not.toBe(obj2);
expect(obj).not.toBe(obj3);
// toEqualはオブジェクトの値の等価性を見る
expect(obj).toEqual(obj);
expect(obj).toEqual(obj1); // 値が同じなのでOK
expect(obj).toEqual(obj2); // obj.hogeもobj2.hogeもundefinedには変わりないのでOK
expect(obj).not.toEqual(obj3);
// toStrictEqualはtoEqualをベースにundefinedも考慮して等価性を見る
expect(obj).toStrictEqual(obj);
expect(obj).toStrictEqual(obj1);
expect(obj).not.toStrictEqual(obj2); // strictの場合はkeyの有無でもNG
expect(obj).not.toStrictEqual(obj3);
});
```
個人的には `expect(...).toBe` スタイルが好みなので、今後のクエストで登場する出題のテストコードには、その方式を利用します。
## Mission 1
#😁EASY
以下の `add` を動作確認するためのテストコードを、同一ファイルに書いてください。
```js
function add(x, y) {
return x + y;
}
```
%%
解答例
```js
function add(x, y) {
return x + y;
}
import { expect } from "jsr:@std/expect";
Deno.test("test", () => {
expect(add(1, 2)).toBe(3);
});
```
%%
## Mission 2
#🙂NORMAL
[[📗TDQ-008 関数#Mission 1]] の解答を検証するためのテストコードを書いてください。
```js
// maxの実装例
function max(x, y) {
return x > y ? x : y;
}
```
%%
解答例
`x > y`, `x < y`, `x === y` の3パターンが検証できていればOK。
```js
import { expect } from "jsr:@std/expect";
Deno.test("test", () => {
expect(max(1, 2)).toBe(2);
expect(max(2, 1)).toBe(2);
expect(max(2, 2)).toBe(2);
});
```
%%
## Mission 3
#😵HARD
[[📗TDQ-007 論理演算子とNull合体演算子#Mission 1]] の実装が間違っていることを証明するためのテストコードを書いてください。
%%
解答例
4種類のパターンが検証できており、すべて失敗すればOK。`t.step` を使うと、その中で失敗しても `Deno.test` は終了せず次のstepが実行されるので、今回のようなケースでは便利です。
また、`toBeTruthy` や `toBeFalsy` を使ってはいけません。それらは[[Truthy (JavaScript)|Truthy]]や[[Falsy (JavaScript)|Falsy]]を検証するものであり、`true` や `false` を検証するものではないからです。
```js
import { expect } from "jsr:@std/expect";
Deno.test("test", async (t) => {
await t.step("空文字はなし", () => {
expect(notIncludesEmpty("a", "b", "c")).toBe(true);
});
await t.step("第1引数が空文字", () => {
expect(notIncludesEmpty("", "b", "c")).toBe(false);
});
await t.step("第2引数が空文字", () => {
expect(notIncludesEmpty("a", "", "c")).toBe(false);
});
await t.step("第3引数が空文字", () => {
expect(notIncludesEmpty("a", "b", "")).toBe(false);
});
});
```
別解1: `Deno.test` を都度呼び出すのはスタンダードな方法です。
```js
import { expect } from "jsr:@std/expect";
Deno.test("空文字はなし", () => {
expect(notIncludesEmpty("a", "b", "c")).toBe(true);
});
Deno.test("第1引数が空文字", () => {
expect(notIncludesEmpty("", "b", "c")).toBe(false);
});
Deno.test("第2引数が空文字", () => {
expect(notIncludesEmpty("a", "", "c")).toBe(false);
});
Deno.test("第3引数が空文字", () => {
expect(notIncludesEmpty("a", "b", "")).toBe(false);
});
```
別解2: 最初の失敗以外に興味がない場合は `expect` の連続でもOKです。
```js
import { expect } from "jsr:@std/expect";
Deno.test("test", () => {
expect(notIncludesEmpty("a", "b", "c")).toBe(true);
expect(notIncludesEmpty("", "b", "c")).toBe(false);
expect(notIncludesEmpty("a", "", "c")).toBe(false);
expect(notIncludesEmpty("a", "b", "")).toBe(false);
});
```
別解3: [[Parameterized Test]]を書きたい場合の一例です。関数の引数に対する[[分割代入 (JavaScript)|分割代入]]を2重で行っています。
```js
[
["空文字はなし", ["a", "b", "c"], true],
["第1引数が空文字", ["", "b", "c"], false],
["第2引数が空文字", ["a", "", "c"], false],
["第3引数が空文字", ["a", "b", ""], false],
].forEach(([name, [str1, str2, str3], expected]) => {
Deno.test(name, () => {
expect(notIncludesEmpty(str1, str2, str3)).toBe(expected);
});
});
```
%%
> [!hint]- Hint 1
> `toBeTruthy` や `toBeFalsy` を使ってはいけません。
---
*NEXT* >> [[📗TDQ-011 for文はfor of]]