2014年5月27日火曜日

Google Mock

使っております。これのおかげでテストを書くのがなかなか楽しくなってます。
どういうことか適当なコードスニペットを使って説明しますと(コンパイルは通りません、あしからず・・・)
enum EnergyBlastType //気功波のタイプ
{
    EnergyDiskRazor, //気円斬
    Kamehameha, //かめはめ派
    TriBeam //気功砲
};

class IZwarrior
{
  public:
    virtual EnergyBlastType ShootEnergyBlast() const = 0;
    virtual ~IZwarrior() {};
};

ドラゴンボールのZ戦士を表すインターフェースがあるとして、ナッパにZ戦士の技を受けてもらいます。

std::string Nappa::React(IZwarrior const & zwarrior) const
{
    EnergyBlastType blastType = zwarrior.ShootEnergyBlast();
    switch (blastType)
    {
      case EnergyDiskRazor:
        return "くだらん技だ!";

      case Kamehameha:
        return "オレの最高の技だぞ・・・・・・!!!押しのけやがった・・・!!";

      default:
        assert(blastType == TriBeam);
        return "おどかしやがって・・・!";
    }   
}

switch 文全体のコードカバレッジを満たすために、それぞれのブランチをテストします。テストコードから本物のZ戦士に不要な依存関係を作らないため、あるいはテスト環境で本物のZ戦士を用意するのが困難ため、テスト環境下では偽Z戦士に登場してもらいます。昔SFCソフトのドラゴンボールZ超サイヤ伝説でZ戦士に化けるコピーマンという敵がいましたが、まさにそれです。

Google Mock 使用前

class KuririnTestDouble : public IZwarrior
{
  public:
    virtual EnergyBlastType ShootEnergyBlast() const
    {
       return EnergyDiskRazor;
    }
};

class GokuTestDouble : public IZwarrior
{
  public:
    virtual EnergyBlastType ShootEnergyBlast() const
    {
       return Kamehameha;
    }
};

class TenShinhanTestDouble : public IZwarrior
{
  public:
    virtual EnergyBlastType ShootEnergyBlast() const
    {
       return TriBeam;
    }
};


#include <gtest/gtest.h>

TEST(NappaTest, ReactsToEnergyBlast) // Google Test
{
    Nappa nappa;
    
    KuririnTestDouble kuririn;
    EXPECT_STREQ("くだらん技だ!", nappa.React(kuririn));

    GokuTestDouble goku;
    EXPECT_STREQ("オレの最高の技だぞ・・・・・・!!!押しのけやがった・・・!!", nappa.React(goku));

    TenShinhanTestDouble tenshinhan;
    EXPECT_STREQ("おどかしやがって・・・!", nappa.React(tenshinhan));
}


クリリンのコピーマン、悟空のコピーマン、天津飯のコピーマンに登場してもらいましたが、どのコピーマンも一種類の気功波しか打てないため、ナッパの全ての反応を見るために三体のコピーマンを用意しないといけない。もしも実世界のソフトウェア製品のコードのテストでこのようなアプローチをとることになった場合、コピーマンのようなテストのセットアップのためだけの test double の量が膨れ上がります。

Google Mock 使用後

#include <gtest/gtest.h>
#include <gmock/gmock.h>

class GmockZwarrior : public IZwarrior
{
  public:
    MOCK_CONST_METHOD0(ShootEnergyBlast, EnergyBlastType());
};

TEST(NappaTest, ReactsToEnergyBlast) // Google Test
{
    Nappa nappa;
    GmockZwarrior mockZwarrior;
  
    EXPECT_CALL(mockZwarrior, ShootEnergyBlast).
        WillOnce(Return(EnergyDiskRazor));

    EXPECT_STREQ("くだらん技だ!", nappa.React(mockZwarrior));


    EXPECT_CALL(mockZwarrior, ShootEnergyBlast).
        WillOnce(Return(Kamehameha));

    EXPECT_STREQ("オレの最高の技だぞ・・・・・・!!!押しのけやがった・・・!!", nappa.React(mockZwarrior));

    
    EXPECT_CALL(mockZwarrior, ShootEnergyBlast).
        WillOnce(Return(TriBeam));

    EXPECT_STREQ("おどかしやがって・・・!", nappa.React(mockZwarrior));
}

Google Mock のルールに従ってコピーマンを一体作れば、テスト内でEXPECT_CALL を使ってなんとそのコピーマンの動きをコントロールできてしまいます。これでクリリンのコピーマン、悟空のコピーマン、天津飯のコピーマンを用意する必要がなくなりました。

このように Google Mock を使えばテストの前段階に必要な test double の量を軽減することができるのですが、それ以上に Google Mock を使ってみて分かった最大の利点は

テストコードの理解度が向上する

という点です。Google Mock 使用前は、わざわざクリリンのコピーマン、悟空のコピーマン、天津飯のコピーマンの振る舞いが定義されている場所まで行ってその動作を確認しなければならなかったのに対して、Google Mock 使用後は、テストの関数の中で EXPECT_CALL を見れば、その場でコピーマンの動きが把握できます。あくまで主観ですがテストコードは局所的であるべきです。その箇所だけを読んでテストの内容を理解できるというのが理想的です。Google Mock は便利だなあと感じるのはそういう理由からきているのだと思います。

便利な日常アプリだけじゃなく、開発者にも素晴らしいフレームワークを提供してくれる Google 先生はやはりさすがです。