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

第6回自動テストのサイズダウン戦略 ~テストダブルを作る前に考えるべきこと

このコラムの主なテーマは、信頼できる実行結果にできるだけ短い時間でたどり着く自動テスト群の構築です。本稿では、テストダブルをうまく活用してテストピラミッドを構築する戦略を立てます。

信頼性の高い自動テスト群を求めて

自動テスト全体の中長期的な信頼性を守るために、図1のようにLargeからMediumへ、MediumからSmallへテストサイズを適宜下げ、テストピラミッドを構築します[1]。テストサイズとは、単一プロセスに閉じたテストをSmall、単一マシンに閉じたテストをMedium、そういった制約がないテストをLargeテストと呼ぶ分類基準です。

図1 テストピラミッド
図1

サイズダウンを考えるとき、テストダブルの活用が思い浮かびます。テストダブルとは、自動テストに使用する偽物、代用品のことです。単一プロセスや単一マシンに収まらない外部依存を偽物に置き換え、テストサイズを下げます[2]

テストダブルと互換性

テストダブルによって自動テストの実行速度や決定性(テストが毎回同じように安定して動く度合い)が向上しますが、その代わり忠実性(テストが本物の環境を再現している度合い)が下がります。ここで、⁠本物を再現している度合い」「偽物で本物を問題なく置き換えられるか」と言い換えると、ソフトウェア開発における「互換性」として考えられそうです。

互換性とは、ある部品やコンポーネント(構成要素)をほかのものと置き換えても同様に動作させられる性質です。ソフトウェアにおける互換性にはいくつかのレベルがありますが、本稿では単純化のためにソース互換性と機能互換性の2つに絞ります。テストダブルの文脈では、置き換え対象の型やAPIのスキーマとテストダブルの型やスキーマが一致していればソース互換、置き換え対象と同じ振る舞いをテストダブルができれば機能互換と考えます。

機能互換はソース互換よりも到達が難しく、一般にソース互換であっても機能互換ではない状況は多々あります。ただし、テストダブルにおいては、テストから使用する部分が機能互換であれば実用上は問題ありません。

互換性は、バージョンアップ時に失われることがあります。バージョンアップのタイミングは、管理できるものもあれば(バージョン固定など⁠⁠、管理できないものもあります(外部サービスなど⁠⁠。テストダブルも、最初は互換だったものがどこかのタイミングで互換ではなくなることがあります。それどころか、最初から互換ではないこともあり得ます。

以上、互換性の視点から整理すると、ソース互換と機能互換という異なるレベルの互換性において、最初から互換性がない(そもそも間違っている⁠⁠、あとから互換性がなくなる(置き換え対象の更新に気付かない)といったリスクがテストダブルにはあります。互換性がないテストダブルを放置すると、誤った仮定に基づいたテストが成功し続けるので、問題の発見が遅れてしまいます。

段階1:テストダブルを使わない

忠実性をできるだけ下げずにサイズダウンするには、テストダブルを使わない、作らない、工夫して作る、という順番で検討します。

まず、偽物に置き換える前に、本物がそのまま使えないかを調べます。本番環境ではマシンをまたぐ外部依存がMediumやSmallに収まるならば、忠実性をほぼ下げずにサイズダウンできます。たとえば、MySQLやRedisなどの公式イメージや、自社のバックエンドチームが開発しているイメージをコンテナで動かせば、サイズがMediumに収まります。ただし、イメージが本物であっても忠実性が100%ではないことに注意しましょう。設定内容やマシンスペック、ネットワーク構成などは異なり、それがLargeテストとの差分です。

次に、設計の改善を検討します。たとえば、抽象化によってレイヤを分け、結合度を減らし、テストダブルで置き換えたい依存がコードに登場する箇所が少なくなれば、ピラミッドの中段や下段でテストを書きやすくなります。良い設計を行うと、Smallテストで書ける範囲が自然と広がります。

段階2:テストダブルを作らない

テストダブルを使わざるを得ないと判断したら、次は互換性を意識して忠実性の低下を抑えます。多くの場合、自動テストの作者は置き換え対象の作者ではないので、テストダブルを作ってもソース互換性はまだしも機能互換性の高さは期待できません。

そこで、テストダブルを自分で作る前に、⁠誰かが作った信頼のおけるテストダブル」を探しましょう。探したいのは信頼のおける「フェイク」Fakeです。

フェイクはテストダブルの一種で、自動テスト用の軽量な代替実装です。信頼のおける開発元が作成したフェイクは、仕様の誤解や理解のずれに起因する非互換が少なくなります。

まず公式のフェイクを探します。フェイクの提供者が置き換え対象の開発元であるならば、高い機能互換性が期待できます。たとえば、DynamoDBに対応したDynamoDB localは、ローカル開発用にAWSAmazon Web Servicesが推奨している簡易実装、いわば公式のフェイクです。ローカルで動くなら、単一マシン内、つまりMediumテストに収まります。

公式のフェイクが見つからない場合、OSSのフェイク実装を探します。公式ではなくとも、OSSとして多数の利用者の目にさらされているフェイクは高い機能互換性を期待できます。たとえば、LocalStackプロジェクトはAWSの各サービスのフェイクを提供し、多くの開発者に使われています。

段階3:テストダブルを工夫して作る

探しても見つからなかった場合は、互換性が失われたタイミングをとらえられるように工夫してテストダブルを作ります。

テストダブルで置き換える対象を自社の別チームが開発している場合などでは、静的なスキーマをチーム間の仕様策定に使い、ソース互換性の喪失に備えます。機能互換性に関しては、中央のスキーマからテストコードとフェイクを生成してチーム間で共有するPactなどのConsumer-driven Contract(CDC) Testingのしくみを導入したり、開発元のチームにテスト用のフェイク提供を依頼したりすれば、高い機能互換性を期待できます。チーム間でのフェイクの共有は、追随が必要な機能変更を自動テストの失敗として伝えられるメリットがあります。

管理できない外部依存のテストダブルを作るときは、外部通信を記録して再生できるVCRなどのRecord & Replay型のテスト支援ライブラリを導入し、Largeテストで本物と通信した内容を記録しておき、それをテストダブルとしてSmallテストで再現できるようにします。記録内容を定期的に更新すれば、互換性が失われたことを検知できます。

おわりに

テストダブルを作る前に、まず使わないですまないか、作らずにすまないかを確認しましょう。

おすすめ記事

記事・ニュース一覧