MENU

【Jest】テストについて知る

2021 8/09
【Jest】テストについて知る

こんにちは、りっくんです!

今回は「テスト」についての内容になります!

先日までテスト??なにそれ??という感じだったのですが、先日もりけん塾での勉強会で運良く勉強する機会があったので、アウトプットをかねて今回こちらのテーマを書かせていただきました。

本記事では、

  • テストとは何か
  • なぜテストをする必要があるのか
  • 具体的なコードの書き方

あたりを中心に理解していただける内容を目指しております。

最後までお付き合いいただけると幸いです🙏🙏

それでは、早速本題に移りましょう💪💪💪。

目次

テストとは

そもそもテストってなに?

テストとはなんでしょうか?

普段親しみのある言葉で言うと、「学校のテスト」や「試験」という意味で囚われがちですが、

プログラミングにおけるテストとは

作成したコードが正しく動くかどうかを検証すること

という意味を指します。

仕様通りの実装ができてデバッグもしたけど、実際に使ってみると思ってもみなかったところで問題が発生したというケースは少なくありません。リリースしてからそういうことがあるのはよろしくないですよね。

そのようなケースに備えてプログラムの動作を事前に確認して、品質を保証するためにテストを行う必要があります。

※以降、テストという言葉は上記を指したものとして進めていきます。

ちなみに、テストには大きく分けて以下の3種類が存在します。

・単体テスト(ユニットテスト):関数/クラス(メソッド)の動作をチェックする

・結合テスト:関数/クラス(メソッド)の動作を結合した時の動作をチェックする

・システムテスト:アプリ全体の動作をチェック

今回説明するのは単体テストにフォーカスした内容になります。

なんのためにするのか

先ほどの内容とも被る部分がありますが、テストをする理由は、

  • 実装したプログラムが正常に動作するかの確認
  • 仕様通りに動くことを確認しリリースするため

というところが主な目的になります。

そのためテストをおこない、実装したプログラムの予期せぬ不具合を未然に回避するためにあらゆる動作のチェックをおこなうことで、プログラムの品質を事前に担保することができます。

ひとまずの例に、シンプルなものから↓↓

function add(x, y) {
  return x + y;
}

こちらのadd関数は引数二つに数字が渡るとそれらを足した値が算出されるといういたってシンプルな関数になります。

この関数の引数にわたってくる処理結果を自力でテストするとなれば、

if(add(1, 2) === 3) {
  console.log("テスト結果OK"); // こっち
} else {
  console.log("テスト結果NG");
}

if(add(2, 2) === 5) {
  console.log("テスト結果OK");
} else {
  console.log("テスト結果NG"); // こっち
}

.
.
.

といった感じになります。これでテストコードの完成です。

ただ、現状だといちいち検証コードを書き、ブラウザ開いて確認という作業を繰り返すことになります。

さらにどの実装コードがテストを合格したのか、どれが失敗したのかというところもイマイチわかりづらいところが懸念点です。

今回は簡単な処理なのでこれでも良いかもしれませんが、もっと複雑で規模も大きくなってくるとどうしても人の手でやるには限界がでてきますし、ミスも発生してしまいます。

そこで、このテストコードを効率よく書くためにテストフレームワークなるものが存在します。

テストフレームワークについて

テストフレームワークは実装したコードの検証を自動で行ってくれる優れものです。言うなれば、人の手で実装コードの動作確認をしていたところを全てプログラムに任せてしまうといったイメージなります。

これをを用いることで、

  • テストを簡潔に分かりやすく書けるようになる

  • テスト結果を見やすく出力できる(テストの成否具合や実施内容、問題となる内容が詳しく出力できる)

  • 製品の品質向上(人の手でおこなっていたテストを繰り返し、かつ誤りなく実施することができるようになるため)

などといった恩恵を得られます。

テストフレームワークは先ほど紹介したテストの種類の中でも主に「単体テスト」の検証をサポートするために考案されたものになります。

JavaScriptにはMochaJasmineなどさまざまなテストフレームワークがありますが、

昨今で一番シェアが高いJestというものがあるので、今回はそちらをメインに話を進めていきたいと思います。

実際に動かしてみよう

導入

テストを実行するにあたって環境構築をする必要があります。今回はテストを書くことに重きをおきたいので、環境構築等は割愛させていただきますm(_ _)m

こちらの記事がわかりやすかったので、ご参照ください!

あわせて読みたい
JavaScriptでも単体テストを導入しよう!ってかテストって何?
JavaScriptでも単体テストを導入しよう!ってかテストって何? 昔はお遊び程度の使われ方をしていたJavaScriptも、本格的な開発に使われるようになってからだいぶ経ちました。 開発の規模が大きくなってくると、どうしても…

※Node.jsをインストールしていることを前提とします。

実行してみる

テストを実行するために今回は、ルート直下に

・add.js

・add.test.js

の2つのファイルを作成します。

テストをする際には通常のJavascriptファイルとは別に、〜.test.jsと付いたテストファイルを用意する必要があります。

今回はadd.test.jsというファイルを作成します。(add.jsと同階層の場所にファイルを置くことを想定しています。)

まずは一番簡単なサンプルから。

export function add(a, b) {
  return a + b;
}

// 関数addをmoduleとして扱いexportする(外部で扱えるように関数addを外に曝け出す)
import { add } from "./add.js";  // 上でエクスポートした関数addをインポート

test("adds 1 + 2 to equal 3", () => {
  expect(add(1, 2)).toBe(3);
});

見慣れないコードが出てきましたね。

テストをするにあたって、test関数というものを実行する必要があります。

test("関数の処理内容を記載", () => {
  expect(関数(hoge, huga)).toBe(piyo); // 関数expectと関数toBeの値が等しいかをテストする
});

今回の例で言えば、test()の第1引数には関数の処理内容(日本語でも可)を。(testitとしても書き換えることができます。)

そして、第二引数にコールバック関数を書き、そこでexpectとtoBe(マッチャー関数と呼びます)を用いることで対象のデータ(この例で言えばadd関数の返り値)とその期待する値が等しいかをテストすることができます。

expect は値をテストしたい時に毎回使用する関数です。 expect関数にはテスト対象(変数だったり関数だったり)を指定します。

テストを実行:

npx jest 

 PASS  ./add.test.js
  ✓ adds 1 + 2 to equal 3 (1 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.171 s
Ran all test suites.

PASSと出ましたね!🎉 テストに合格するとこんな感じの表示が出ます。

今度は失敗したパターンを書いてみます。今度はadd関数の第1引数にundefinedを持ってきました。

test("adds 1 + 2 to equal 3", () => {
  expect(add(undefined, 2)).toBe(4);
});
 FAIL  ./add.test.js
  ✕ adds 1 + 2 to equal 3 (1 ms)

  ● adds 1 + 2 to equal 3

    expect(received).toBe(expected) // Object.is equality

    Expected: 3
    Received: NaN

      2 |
      3 | test("adds 1 + 2 to equal 3", () => {
    > 4 |   expect(add(undefined, 2)).toBe(3);
        |                             ^
      5 | });
      6 |

      at Object.<anonymous> (add.test.js:4:29)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        0.175 s, estimated 1 s
Ran all test suites.

テストに失敗したことがわかります。undefined + 2 の結果は3にならないからですね。

また、4行目の部分で指摘を受けています。

 Expected: 3
 Received: NaN

ここで本来期待していた値と実際に関数によって返された値が表示されています。

このようにして、テストフレームワークを使うことでコマンド上で簡単にプログラムの検証をおこなうことができます。

マッチャーについて

テストの評価条件を定義するメソッドになります。

もう少し平たく言えば、ここではテストの結果(期待する値)を定義します。

ここではマッチャー関数に関して一部紹介していきたいと思います。

.toBe(value)

対象のデータと同値となることを検証します。単純な値の比較に使います。

test("adds 1 + 2 to equal 3", () => {
  expect(add(1, 2)).toBe(3); 
});

▶︎ add関数の返り値と期待値(3)が等しいかどうかを比較します。この場合は値が一致するのでテストが通ります。

.not

検証を否定します。同じ値ではないことを確認する場合に使います。

test("adds 1 + 2 to equal 3", () => {
  expect(add(undefined, 2)).not.toBe(3); // .toBe() の 前に .not
});

▶︎ add関数の返り値は3にはならないという意味になるので、テストが通ります。

.toBeFalsy()

対象のデータがfalthy(undefined,0,nullなど)であるかどうかを検証します。

test("adds 1 + 2 to equal 3", () => {
  expect(add(undefined, 2)).toBeFalsy(); 
});

▶︎ undefined + 2 は NaN になるので、テストが通ります。

.toBeTruthy()

対象のデータがtrueであるかどうかを検証します。

test("adds 1 + 2 to equal 3", () => {
  expect(add(1, 2)).toBeTruthy(); 
});

▶︎ 1 + 2 は true になるので、テストが通ります。(結果の値はtrueであればなんでも良いです)

.toBeNull()

対象のデータがNullであるかどうかを検証します。

test("adds 1 + 2 to equal 3", () => {
  expect(add(1, 2)).not.toBeNull();
});

▶︎ .not と併用しましたが、Nullにならないことを検証しました。テストは通ります。

.toContain()

配列やオブジェクトの中に対象のデータがあるかどうかをチェックします。

test("names contains 'sato'", () => {
  const names = ["sato", "suzuki", "tanaka"];

  expect(names).toContain("sato"); // ① 'sato'は配列の中に存在するため、通る。
  expect(names).not.toContain("takeda"); // ② 'takeda'は配列の中に存在しないため、.notで通る。
});

.toBeNull()

対象のデータがNullであるかどうかを検証します。

test("adds 1 + 2 to equal 3", () => {
  expect(add(1, 2)).not.toBeNull();
});

▶︎ .not と併用しましたが、Nullにならないことを検証しました。テストは通ります。

.toEqual(value)、.toStrictEqual(value)

オブジェクト内のデータがすべて同じかどうかを検証します。配列同士のデータを全て比較する時なんかには有効です。

test("names contains 'sato'", () => {
  const names = ["sato", "suzuki", "tanaka"];
  const names2 = ["sato", "suzuki", "tanaka"];
  expect(names).toEqual(names2);
});


test("example toStrictEqual", () => {
  const expected = {foo: 123};
  const value = {foo: 123};
  expect(expected).toStrictEqual(value);
});

toStrictEqual と toEqualの違いは、

  • toStrictEqual:オブジェクトのキーも含めて、それらが全て同一かどうかを検証する。
  • toEqual:オブジェクト同士を比較して、valueが等しいかどうかだけを検証する。

というところになります。

配列同士はキーが変わらない(0,1,2,3…)ため、toEqualでもtoStrictEqualでも同じ結果が得られます。


上記で紹介したマッチャー関数は全体でもほんの一部です。さらに別の関数について知りたい場合は、こちらを参照ください。

そのほかの実行サンプル

ここではさらに別のサンプルを使用したテストをおこなってみます。

fizzBuzzをテスト

function fizzBuzz(num) {
  if (num % 15 === 0) {
    return "FizzBuzz";
  }
  if (num % 5 === 0) {
    return "Buzz";
  }
  if (num % 3 === 0) {
    return "Fizz";
  }
  return num;
}


describe("fizzBuzz example", () => {
  test("15の倍数以外の3の倍数の数値を渡したら 'Fizz'が返る", () => {
    expect(fizzBuzz(3)).toBe("Fizz");
    expect(fizzBuzz(6)).toBe("Fizz");
    expect(fizzBuzz(9)).toBe("Fizz");
  });
  test("15の倍数以外の5の倍数の数値を渡したら 'Buzz'が返る", () => {
    expect(fizzBuzz(5)).toBe("Buzz");
    expect(fizzBuzz(10)).toBe("Buzz");
    expect(fizzBuzz(20)).toBe("Buzz");
  });
  test("15の倍数の数値を渡したら 'FizzBuzz'が返る", () => {
    expect(fizzBuzz(15)).toBe("FizzBuzz");
    expect(fizzBuzz(30)).toBe("FizzBuzz");
    expect(fizzBuzz(45)).toBe("FizzBuzz");
  });
});

ここではdescribe(name, fn) という構文を使用しております。

これは、testの内容をまとめて記述できるようなもので、 さらにその中で上記のように個々のtest関数を実行することができます。

テスト結果:

 PASS  ./add.test.js
  fizzBuzz example
    ✓ 15の倍数以外の3の倍数の数値を渡したら 'Fizz'が返る (1 ms)
    ✓ 15の倍数以外の5の倍数の数値を渡したら 'Buzz'が返る
    ✓ 15の倍数の数値を渡したら 'FizzBuzz'が返る

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.252 s, estimated 1 s
Ran all test suites.

describe を使用することでテスト結果のログもわかりやすい表示になりました。

非同期処理をテスト

JSONPlaceholderを利用。

const axios = require("axios");

function fetchData() {
  return axios.get("https://jsondata.okiba.me/v1/json/qYmgs210807153307");
}

describe("fetchData example", () => {
  test("the res.data.name is JavaScript", async () => {
    return fetchData().then((res) => {
      expect(res.data[0].name).toBe("JavaScript");
    });
  });

  // async / await で 書き換え
  test("the res.data.name is JavaScript", async () => {
    const res = await fetchData();
    expect(res.data[0].name).toBe("JavaScript");
  });
});

テスト結果:

 PASS  ./add.test.js
  fetchData example
    ✓ the res.data.name is JavaScript (164 ms)
    ✓ the res.data.name is JavaScript (132 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.531 s, estimated 1 s
Ran all test suites.

他のコードと違って、promiseをテストする際にはreturnを書く必要があったり、エラー処理が期待される場合にはexpect.hasAssertions()を使って、expectが実行されているか否かを確認する必要があるとのことなのですが、

その辺りに関してはまだ理解が足りておらず自信がないので、あくまで備忘録程度に。、^^;

まとめ

いかがだったでしょうか?

後半はメモ程度の仕上がりになってしまい、まだまだ理解が及んでいないところがあると感じています。

今回こちらの記事を書くにあたって、そもそもテストとは何なのか?という部分に関して詳しく記載されてある記事が意外と少なかったので、その辺りの理解を深められるようにと意識しました。

恐縮ではありますが、今回の記事で少しでもみなさんへのテストに対する理解が深められたら幸いです!

それではまたお会いしましょう〜〜✋

参考記事:

https://qiita.com/ysktsuna/items/6b8b824e444030070754

JavaScriptテスト自動化キホンのキ

https://sbfl.net/blog/2019/01/20/javascript-unittest/

JavaScriptでも単体テストを導入しよう!ってかテストって何?

https://qiita.com/chimame/items/e97883fd46b67529d59f

Facebook製のJavaScriptテストツール「Jest」の逆引き使用例

もりけん塾に入っております!(Javascriptを鋭意勉強中)

もりた先生のブログ: kenjimorita.jp (武骨日記)
もりた先生のアカウント: twitter.com/terrace_tech

この記事を書いた人

コメント

コメントする

CAPTCHA


目次
閉じる