サバンナ便り ~ソフトウェア開発の荒野を生き抜く~

第4回テストダブル ~忠実性と決定性のトレードオフを理解する

自動テストを書く際に使いどころをマスターしたいテクニックがテストダブルTest Doubleです。テストダブルを効果的に使えばテストの網羅性、速度、再現性を向上させますが、使いどころを誤れば変更や改善の妨げになりかねません。今回は、テストダブルの利点と注意点をまとめます。

テストダブルとは何か

テストダブルとは、自動テストに使用する偽物、代用品のことです。たとえば、データベースや外部サービスの動作を模倣した偽物(テストダブル)を作り、自動テストから使います。

自動テストで偽物を活用するテクニックを「モック」Mockと呼ぶ方も多いですが、より正確には、テストに偽物を使う技術を総称してテストダブルと呼びます。この場合の「ダブル」は身代わりや影武者のようなイメージでとらえてください。テストに使う身代わりなのでテストダブルです。テストダブルの種類として詳しくはスタブStub⁠、スパイSpy⁠、モック、フェイクFakeなどがありますが、本稿ではテストダブル全般の利点と注意点を説明します。

テストダブルのしくみは入出力の矢印でイメージするのがわかりやすいでしょう。テスト対象と、そのテスト対象が依存している依存対象があるとき、依存対象からテスト対象へ向かう入力の値をテストから制御したり、テスト対象から依存対象へ向かう出力の内容を記録して検証できるようにしたりするものがテストダブルです。

利点1:テストしにくいものをテスト可能にする

テストダブルの1つ目の利点は、自動テストが難しいコードをテスト可能にし、テストの網羅性を高められることです。

例外系のテストを書くとき、本物を使うと例外条件の再現が難しい場合があります。たとえば、システム内部のネットワーク障害や、ディスクへの書き込みエラーが発生した場合のエラーハンドリングのテストなどは、やってできないことはありませんが、事前状態の準備がとても難しいものです。また、連携先の社外サービスからエラーが発生した場合のロジックは、そのままではほぼテスト不可能でしょう。

このような場合でも、テストダブルから仮想的にエラーを発生させれば、エラーハンドリングに関するロジックのユニットテストを簡単に行えます。また、再現しにくい不具合の修正時にテストダブルを活用すると、不具合の原因を常に再現するテストを書けるようになります。

利点2:テストの速度と決定性を向上させる

テストダブルの2つ目の利点は、自動テストの実行速度と決定性の向上です。

テストダブルの多くはメモリ上やコンテナ上で高速に動作します。特にメモリ上で動作する場合は外部リソースを使いませんので、テストを簡単に並列動作させることができます。

テストダブルの動作は基本的にテストコードから指定するので、外部環境にテスト結果が左右されず、決定性のある安定した動作をします(コード以外にテスト失敗の要因がありません⁠⁠。ランダム性を伴うロジックや、現在時刻に依存したロジック、外部サービスとの通信に依存したロジックなどを、テストコードから完全に制御して再現性のある高速なテストを書けるようになります。

注意点1:テストが脆くなり、変更を妨げる

テストダブルには注意点もあります。その1つ目は、テストが脆くなる、つまり、テストがテスト対象の変更に不必要に影響されがちになることです。

テストダブルを過剰に使う、特にテスト対象から依存対象への向きのやりとりを細かく検証するようになると、テスト対象の詳細な振る舞いがテストコードに漏れ出し始めます。すると、外部から見た振る舞いやインタフェースとは関係ない些細な実装変更やリファクタリングであってもテストが失敗してしまい、皮肉にもコードの変化を妨げる方向に力が働いてしまいがちです。テストの脆さは、テスト対象との構造的結合から発生します。可能な限り構造的結合を避けたテストダブルの活用が重要です。

テストダブルの「テストが難しいものにもテストが書ける」という利点は、その強力さゆえに注意点にもなります。テストが書きにくいのは設計が悪い兆候であり、本当は設計を変更したほうが良いサインでもあります。一般的に、テスト容易性を高めようとすると低結合および高凝集といった良い設計に向かうものですが、テストダブルはそのままの設計でもテストを書けてしまいます。強力なテストダブルで無理矢理テストを書くと現状の追認になってしまい、設計改善のチャンスを逃してしまいかねません。

注意点2:テストの偽陰性を招く

テストダブルの2つ目の注意点は、偽陰性[1]を招きがちなことです。偽陰性とは、失敗してほしいテストが失敗してくれない状況のことです。

テストダブルを使う場合、基本的にはテストコードの書き手が本物の振る舞いを偽物に置き換えます。その際に、偽物の振る舞いが本物の振る舞いと同一であるかどうかは保証がありません。

テストを書いた時点では本物と動きが一致していたとしても、あとから本物のほうの動きが変わったときに、テストダブルはその動作変更に追随してくれません(これは、モックドリフトと呼ばれることがあります⁠⁠。本来は本物のほうの動きが変わった時点でテストが失敗してほしいのですが、依存対象がテストダブルに置き換わっているために、誤った仮定に基づいたテストが成功し続ける状況が続き、問題の発見が遅れてしまいます。

テストダブルの位置付けと使いどころ

テストダブルは、テストに偽物を使うことで、本物を模した度合い(忠実性)を下げる代わりに、速度と決定性を向上させる技術です。これはテスト設計におけるトレードオフであり、正解はありません。

具体的には、テストダブルは、テストサイズ[2]の観点ではテストサイズを下げ、テスト範囲の観点ではテスト範囲を狭めるための技術と言えます。

範囲が狭くサイズが小さいテストであるほど、高速かつ決定的な安定した動作をします。高速で安定していれば、コードがすべて動作することをごく短い時間で判定し、マージやデプロイ、リリースの判断に使えます。このような自動テストが、継続的デリバリに代表される現代の開発スタイルを支える基盤技術となります。

筆者のテストダブルの使い方を述べますと、筆者は自動テストの決定性を上げるためにはテストダブルを使いますが、本稿で説明した注意点もあるため、すでに決定性が高い動作をしているテストの速度を上げるためだけの目的にはテストダブルを使いません。たとえば、再現の難しい例外条件のテストや、遅く不安定なLargeサイズのテストをMediumやSmallにサイズダウンさせるためにはテストダブルを使います。しかし、本物が同一プロセスや同一マシン内で安定して決定性のある動作をする場合には、テストダブルで置き換えずになるべく本物を使います。一つの指針として参考になれば幸いです。

おわりに

テストダブルには、テストしにくいものをテスト可能にする利点と、自動テストの速度と決定性を高める利点があります。ただ、テストの脆さや偽陰性を招く注意点もあります。テストダブルのトレードオフを理解し、効果的に使いましょう。

おすすめ記事

記事・ニュース一覧