テスト対象を隔離するべき7つのケース:効率的なユニットテストのための C言語およびC++言語のモック化

コードをユニットテストする場合、どの程度の隔離が必要か?これは、ユニットテストを作成するときに繰り返し議論される重要な質問です。私がここで言っているのは、同僚の開発者からの隔離、つまりオープンスペースの隣の席に座り、ヘッドフォンで音楽を聴きながらリズムを取っている誰かからの隔離という意味ではありません(ついでながら、これも高品質のコードの開発には非常に重要ですが)。テスト対象コードをその周囲の環境、いわゆるコラボレーターから隔離することについて話しています。

CおよびC++言語でのスタブとモックについて議論するときは、通常、CとC++が区別されます。その理由は、言語レベルでの違いが、典型的なモックフレームワークに関する複雑さ、機能、期待にも差異をもたらすからです。いっぽう、Parasoft C/C++testでは、ほとんどのフレームワーク機能が両方の言語で使用できるため、状況は少し異なります。この記事では、CまたはC++のどちらかでサンプルを示しますが、特にどちらか一方でしかサポートされていないという断りがないかぎり、該当の機能は両方の言語で提供されているとみなしてかまいません。

隔離するべきか、せざるべきか?

1 つの意見としては、正当な理由がないかぎりは隔離するべきではないでしょう。結局のところ、実際のコラボレーターを使ってテストすれば、より多くのコードがテストされます。コードカバレッジが上がり、バグを見つけられる可能性も増えるのに、それを手放さなければならない理由があるでしょうか?ところが、いくつか理由があるようなのです – それについてはこのあとすぐ説明します。

もう一方で、保守的なユニットテスト実践者は、ユニットテストとは隔離されたユニットをテストすることであり、そこから外れるべきではないと主張します。実際のコラボレーターを使ったテストは、統合フェーズの領域です。実際のコラボレーターをテスト範囲に含めることで、テストのノイズが増えることはよく知られています。実際のコラボレーターに依存しているテストは、テスト対象コードの変更だけでなく、依存するコンポーネントの変更によっても結果が変わります。ノイズの多いテストはメンテナンスコストを増大させ、集中の妨げになります。長期的にみると、このような障害は、ユニットテストが放棄される大きな理由になります。

では、テスト対象コードの隔離に関して取るべき戦略とはどのようなものでしょうか?上記を考えると、ただ1つの万能のルールを決めて、どのコラボレーターをモック化する必要があるかを判断し、テスト対象コードを適切に隔離することは困難です。テストの効率と有効性の観点から見て、「できるだけ隔離」アプローチと「可能なかぎり隔離を回避」アプローチには、それぞれメリットとデメリットがあります。

迷わずモックするべき3つの理由

次のような状況では、隔離するべきであることは明白です。

1. コラボレーターがまだ実装されていないか開発中である

これは簡単です。選択の余地はなく、モックの実装が必要です。次の図は、この典型的なユニットテスト環境(SUT – テスト対象システム、DOC - 依存コンポーネント/コラボレーター)を示しています。

Stubbing mocking for more efficient unit testing with C C++test

2. ハードウェアへの依存性の排除

デスクトップアプリケーションを開発する開発者にとって、このような問題はなじみがないかもしれませんが、組み込み開発者にとっては、ユニットテストをハードウェアから独立させることは、ハードウェアを必要とせずに高レベルのテスト自動化と実行を可能にする重要な側面です。わかりやすい例を挙げると、テスト対象ユニットがGPSハードウェアと連携する場合です。たとえば、速度を計算するために、一連の位置座標が提供されるとします。私たちの運動を増やすという意味では良い考えですが、ユニットテストが必要になるたびに、必要なテスト入力値を生成するためだけに、動きをシミュレートしようとして、テスターがデバイスを持って走り回るなどという事態は想像もできません。次の例は、開発時にハードウェアからの独立が不可能な場合、開発ライフサイクルにおいてデバイスのGPSテストがどれほど遅れるかを示しています。

Stubbing mocking for more efficient unit testing with C C++test_2

3. フォールトインジェクション

意図的にエラーを注入するのは、テストでは一般的なシナリオです。これは、たとえばメモリ割り当てが失敗した場合や、ハードウェアコンポーネントが故障した場合をテストするためなどに行われます。テスト初期化フェーズで実際のコラボレーターを操作して、テスト対象コードから呼び出されたときにエラーが返される状況を作ろうとする開発者もいます。私の考えでは、このやりかたは実用的ではなく、多くの場合、あまりにも面倒です。失敗をシミュレートするテスト固有のモック実装を利用するほうが、通常ははるかに良い選択です。

モックを検討するべき4つの理由

上記のように、モック化されたコラボレーターが望ましいことが明白なケースに加えて、疑似的なコラボレーターが良い選択であるかもしれない微妙な状況があります。テストプロセスが以下のいずれかの問題を抱えている場合は、テスト対象コードの隔離を推進する必要があることを示しています。

1. テストを繰り返し実行できない

変化する依存関係がある場合、安定したテストを実装するのは困難です。そのような場合、テスト対象コードやテストコードを変更していないのにテスト結果が変わることになります 。システムコールへの依存や、テストの中から制御できない外部信号への依存が、そういった流動性の原因になる場合があります。典型的な例はシステムクロックです。テストシナリオが特定の時点で応答する必要がある場合、テスト対象コードへの間接的な入力を完全に制御できるモック化されたコラボレーターがなければ、自動化は困難です。

2. テスト環境を初期化するのが難しい

テスト環境の初期化は非常に複雑になる場合があります。実際のコラボレーターをシミュレートしてテスト対象コードに信頼できる入力を提供することは、不可能ではないにせよ、難しい作業です。

Stubbing mocking for more efficient unit testing with C C++test_3

コンポーネントはしばしば相互に関連しており、特定のモジュールを初期化しようとすると、結局はシステムの半分ほども初期化するはめになるかもしれません。実際のコラボレーターを疑似実装に置き換えることで、テスト環境の初期化の複雑さが軽減されます。

3. テストのステータスを判断するのが難しい

多くの場合、テスト結果を判定するには、テストの実行後にコラボレーターの状態を確認する必要があります。実際のコラボレーターのインターフェイスにテスト後の状態を照会するための適切な手段がないために、これが不可能なことがよくあります。

Stubbing mocking for more efficient unit testing with C C++test_4

通常、実際のコラボレーターをモックに置き換えることで問題は解決します。任意のアクセス方法で疑似実装を拡張し、テスト結果を判断することができます。

4. テストが遅い

実際のコラボレーターからの応答にかなりの時間がかかる場合があります。どの時点で遅れが受け入れられなくなったり、隔離が必要になるかは必ずしも明確ではありません。テスト実行で2分の遅延は許容されるでしょうか?テストスイートをできるだけ早く、コードを変更するたびに実行できることが望ましいケースがよくあります。実際のコラボレーターとのやりとりによる遅延が大きいと、テストスイートの実行が遅くなりすぎて実用的ではありません。そのような実際のコラボレーターをモック化すると、テスト実行が桁違いに速くなり、許容レベルまでスピードを引き上げることができます。

モック化するかどうかを判断するのに役立つ質問

新しいユニットテストを作成し、実際のコラボレーターを使用するか、モックを使用するかを決定する際には、次の4つの質問を考慮してください。

  1. 実際のコラボレーターはテストの安定性にとってリスク要因になっているか?
  2. 実際のコラボレーターを初期化するのは困難か?
  3. テスト後にコラボレーターの状態を確認してテストステータスを判定することは可能か?
  4. コラボレーターの応答にはどのくらい時間がかかるか?

これらの質問にすべて答えることができるほどコラボレーターをよく理解していれば、モック化するべきかどうかを判断するのは簡単です。そうでない場合は、まず実際のコラボレーターを使ってテスト作成を始めて、進捗にしたがってこれら4つの質問に答えを出すようにするとよいでしょう。実際、これはほとんどのテスト駆動開発(TDD)実践者が日常的に適用するアプローチです。つまり、テストケースを注意深く管理し、安定するまで慎重に検討する必要があるということです。

ほとんどの場合、ユニットテストが複雑になる要因は、テスト対象ユニットの依存関係です。依存コンポーネントをモック化することが明らかに望ましいケースもあれば、より微妙な状況もあります。場合によっては、明確な線引きはなく、依存関係がテスト環境に及ぼすリスクと不確実性に依存するでしょう。

(この記事は、開発元Parasoft社 Blog 「7 Times to Isolate a Unit under Test: C and C++ Mocking for Efficient Unit Testing」2018年7月26日の翻訳記事です。)

Top