2. テスト駆動開発 (TDD)

2.1. ワークフロー

TDDのワークフローは [Gre00] で詳細に説明されているので、そちらを参照のこと。

2.2. テスティングフレームワーク

[Gre00] ではテスティングフレームワーク [1]Unity および CppUTest が紹介されているが、特に実機でユニットテストを 実行したいという要求がない限り、本ドキュメントは GoogleTest [2] の使用を推奨する。以下、GoogleTest の使用を推奨する理由を述べる。

アサーション (Assertion)

GoogleTest では、 JUnit 由来の、アサーション ASSERT_THAT() を使用することができる。 ASSERT_THAT() は、以下に示す通り比較的自然言語に近い形で表記することができる。 ここに示す以外にも、豊富なマッチャーが用意されているので、 適宜 本家の情報 を参照のこと。

#include "gtest/gtest.h"
#include "gmock/gmock.h"
...

using ::testing::Eq;
using ::testing::Gt;

TEST(ClassA, Func_returns_0)
{
    ClassA classA;
    ASSERT_THAT(classA.Func(), Eq(0)); // classA.Func() == 0
}

TEST(ClassB, Func_returns_greater_than_5)
{
    ClassB classB;
    ASSERT_THAT(classB.Func(), Gt(5)); // classB.Func() > 5
}
値をパラメータ化したテスト (Value Parameterized Tests)

境界値テストを実施する場合、以下のアプローチを取ることが一般的である。

  1. パラメータのみ変えたテスト関数を複数用意する
  2. ひとつのテスト関数に複数のテストをまとめる

しかしながら、これらのようなアプローチをとった場合、1点目はテストコードの冗長化を招き、 2点目はテスト関数の意図が不明確になるデメリットがある。 これに対し、GoogleTest には「値をパラメータ化したテスト」と呼ばれる機能があり、ひとつの テスト関数で複数のテストを実行することができる。 まず、 [Gre00] で紹介されている LedDriver クラスのテストコード (CppUTest版) を示す。

TEST(LedDriver, OutOfBoundsTurnOffDoesNoHarm)
{
    LedDriver_TurnAllOn();

    LedDriver_TurnOff(-1);
    LedDriver_TurnOff(0);
    LedDriver_TurnOff(17);
    LedDriver_TurnOff(3141);

    LONGS_EQUAL(0xffff, virtualLeds);
}

上記のテスト関数は、範囲 (1~16) 外のIDを与えた場合はLEDの状態が変更されないことを テストするものであるが、4回の LedDriver_TurnOff() の呼び出し中に、LEDが意図しない 状態となっていないことを保証することができない。 次に、上記関数を GoogleTest に移植したテストコードを示す。

#include "gtest/gtest.h"
#include "gmock/gmock.h"
...

using ::testing::Eq;

class LedDriverTest : public ::testing::Test {
protected:
    ...
};

class all_leds_turn_off : public LedDriverTest {};

class arg_pattern_test_when_all_leds_turn_off :
    public all_leds_turn_off,
    public ::testing::WithParamInterface<int> {};

TEST_P(arg_pattern_test_when_all_leds_turn_off, TurnOn_does_no_harm)
{
    LedDriver_TurnAllOn();

    const int n = GetParam();

    // RuntimeErrorモックに対し、期待する振る舞いを指定
    // 本説明では不要であるため省略

    LedDriver_TurnOff(n);

    ASSERT_THAT(virtualLeds_, Eq(0xffffu));
}

INSTANTIATE_TEST_CASE_P(
    aPatternInstance,
    arg_pattern_test_when_all_leds_turn_off,
    ::testing::Values(-1, 0u, 17u, 3141u));

記述の詳細は本家ドキュメントに譲るが、GoogleTest 版では、上記のようにテスト関数を 実装することで、1パラメータごとにテストを実行することができ、LEDが意図しない状態に なっていないことを確実に保証することができる。 その他、組み合わせテストのパラメータ生成に非常に有効な Combine() というパラメータ 生成関数が用意されている。

GoogleMock
[Gre00] ではテストダブルを実装するための手法が複数紹介されているが、GoogleTest の拡張 フレームワーク GoogleMock を使用すれば同等以上のテストが実施できる。

ここまでに紹介した GoogleTest の利点をより読者に感じていただくため、 [Gre00]サイト で配布されているコードを GoogleTest に 移植した。 GitHub で公開している ので、ぜひ一読いただきたい。

2.3. ユニットテストコード

以下を参考に、プロジェクトごとにルールを定めることを推奨する。

2.3.1. コーディングルール

原則 Google C++ Style Guide を基本とする。 ただし、C++11/14で追加された機能に対する制限はしないものとする。

また、命名規則は一部変更する。

メソッド (メンバ関数)
  • public、protected
    パスカルケース
  • private
    キャメルケース
フィールド (メンバ変数)
  • public
    プレフィックス「k」でパスカルケース、最後にアンダーバー
    ※定数(const、constexpr)以外の宣言は禁止する。
  • protected
    パスカルケースで最後にアンダーバー
  • private
    キャメルケースで最後にアンダーバー

2.3.2. 構造

_images/structure-of-ut-code.png

ユニットテスト構造の例

脚注

[1][Gre00] ではテストハーネスと表現されている。
[2]本ドキュメントでは、GoogleMock も含めて GoogleTest と呼ぶこととする。