ここまで数々の動作確認をしてきましたが、[[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]]