こんにちは、ナイトレイインターン生の鈴木です。
Wantedlyをご覧の方に、ナイトレイのエンジニアがどのようなことをしているか知っていただきたく、Qiitaに公開している記事をストーリーに載せています。
少しでも私たちに興味を持ってくださった方は下に表示される募集記事もご覧ください↓↓
はじめに
株式会社ナイトレイの和田です。普段、自社サービスの開発に取り組んでいます。
現在私が関わっているプロダクトにユニットテストを導入しました(そのプロダクトではユニットテストが省略されていました)。依存箇所はモックに置き換えてテストすることになり、その際に学習したテストタブルについて共有します。
テストダブルとは
ソフトウェアテストにおいて、SUTが依存しているコンポーネントを置き換える代用品のこと。
💡 SUTとは、「テストしている対象」を示すもの。ユニットテストの場合、テストスクリプトが実行する「テスト対象のクラスやメソッド」のことを指す。
5種類のテストダブル
①スタブ
スタブとは
SUTの依存コンポーネントを置き換えて、都合の良い任意の値を返すテストダブルのこと。
スタブを利用するメリットは以下が挙げられる。
- テスト対象が意図通りに動くか?をテストできる(=SUTが依存コンポーネントの出力に左右されずに意図した出力ができるかどうかをテストできる)
- 依存コンポーネントの部分に決まりきった正しい挙動をするオブジェクトを注入することで、例えテストが失敗しても「他の何か」ではなく、そのSUTに問題があることが分かる
補足
依存コンポーネントは他の場所でテストされているはずだし、その正しさまでテストすることは責務外のことである。
テスト例
*ここでのテスト例は、「PHPUnitでスタブとモックを理解する!【テストダブル】」という記事にあるものをそのまま引用させていただいています。
SUT
以下のようなSUTがあったとする。
〇sometimesErrorメソッドの返り値によって結果が変わる
- ランダムな値を返す
- エラーの場合は0を返し、catchに入る
これは単体テストがしずらい。
理由としては、somtimesErrorメソッドの結果によって返り値が異なるため(=依存している)。
テストコード
正常系パターン
以下の手順でsomtimesError()がエラーを返さないようにスタブを準備する
- createStub()を使い、引数にスタブにしたい対象クラスを渡す
〇$stub = $this->createStub(CheckNums::class); - スタブに任意の値を返させる
〇method()で引数に操作したいメソッドを渡す
■$stub->method('somtimesError');スタブに任意の値を返させる
〇その他にも、willReturn()で返り値を設定できる
■今回は何にも返さないようにする
準正常系パターン
somtimesError()がエラーを返すパターンをテストするためのスタブを用意する。
②モック
モックとは
SUTの依存コンポーネントを置き換えて、そのコンポーネントが正しく呼び出されているかを検証するために用意するテストダブルのこと。
モックを利用するメリットは、SUTの依存コンポーネントが意図通りに動くか?をテストできること(=実際にSUTの依存コンポーネントを実行せずに、呼び出した回数などをテストできる)。
テスト例
*ここでのテスト例は、「PHPUnitでスタブとモックを理解する!【テストダブル】」という記事にあるものをそのまま引用させていただいています。
SUT
あるAPIを実行するメソッド。
ランダムなユーザーデータを生成してくれる無料のオープンソースAPIを利用。
テストコード
テストの度に毎回APIが走るのは避けたいので、モックですり替える。
モックを利用し以下を検証する。
- APIが実際に1回だけ実行されているか?(2回呼ばれてたらまずい)
- 引数に適切なurlが指定されているか?(タイポなどのミス)
③スパイ
スパイとは
スパイはスパイとSUT間のやり取りを記憶し、後ほどやり取りをアサートできるようにするテストダブルのこと。
モックと同じように、テスト対象の依存コンポーネントが意図通りに動くか?をテストするためのテストダブル。
モックと異なる点
- モックは対象の処理の途中にリアルタイムに検証するのに対し、スパイはテスト対象の一連の処理をひととおり実行した後に検証する
- モックは全てのエクスペンションを検証しなければならないが、スパイの場合は調べたいエクスペンションのみ検証できる
メリット
- シンプルになり、テストコードが読みやすい
- 検証したい項目が明確化する
デメリット
- リファクタリングの必要性が浮き彫りにならない
〇モックの場合、全てのエクスペンションを指定する必要があるため、そのSTUが冗長であることを発見できる
〇スパイの場合、SUTの設計の一部を隠してしまう - デバッグしにくい
〇モックの場合、期待しない呼び出しを受けるとその時点ですぐに例外を投げ、きれいなスタックトレースか、デバッガーを起動することさえある
〇スパイの場合、呼び出し後に検証するので、モックのように「その時点で」「同じような」エラー情報を得ることができない - テストダブルに返り値を定義する必要がある場合、スパイでは行えない
コード例
*PHPモックオブジェクトフレームワークである「Mockery」を使って説明します
*ここでのテスト例は、「Mockery1.0 テストダブル作成」というMockeryのドキュメントにあるコードをそのまま利用させていただいています
モックの場合
スパイの場合
④フェイクオブジェクト
SUT の依存コンポーネントの代品として動作し、本物のコンポーネントと同等の挙動をするもの。
テストスパイやモックオブジェクトとは異なり、フェイクオブジェクトは**「検証」のために使用するものではない**。
使い所
- 本物のコンポーネントが未実装でまだ利用できない
- 本物のコンポーネントを使うとデータの変更や削除等の望ましくない副作用が発生する
- 本物のコンポーネントを使うとテストが大幅に遅くなる
フェイクオブジェクの代表例
- テスト中のみ使われれるインメモリのストレージ
- 外部の API に対応するゲートウェイクラス
⑤ダミーオブジェクト
テスト中で SUT の利用に必要なコンポーネントの代用品。
ただし、ダミーオブジェクトは、スタブ・スパイ・モック・フェイクオブジェクトで挙げた他のものとは異なり、何の機能も備えていない。
テスト対象の状況を作り出すのに便利ではあるが、 SUT がダミーオブジェクトを利用して動くわけではない。
その意味で、有名なユニットテストの書籍である『 xUnit Test Patterns: Refactoring Test Code 』( Gerard Meszaros 著)では「ダミーオブジェクトはテストダブルではない」と説明されている。
ダミーオブジェクトの例としては、関数の引数の条件を揃えるためだけに渡す実引数などがある。
補足
定番のテストダブルライブラリでは、スパイとモックがスタブの機能を兼ね備えていることもある。 その場合は次のとおりになる。
- スタブ: 指定された挙動をする機能
- スパイ: (スタブの機能) + 記録機能
- モック: (スタブの機能) + 処理中の検証機能
参考
最後に
私たちの会社、ナイトレイでは一緒に事業を盛り上げてくれるエンジニアメンバーを募集しています!
このような方は是非Wantedlyからお気軽にご連絡ください(もしくはこちらまで recruit@nightley.jp )
✔︎ 自社Webサービスの開発で事業の発展に携わってみたい
✔︎ 自分が開発したものが顧客にどのように使われているのか知りたい(顧客の声を聞いてみたい)
✔︎ セールスチームなど、他部署のメンバーとコミュニケーションが取れる環境に興味がある
✔︎ 地理や地図が好きで仕事中も眺めていたい