SSブログ

Google TestでDeathテストを使ってみる [Google Test]

Google TestにはDeathテストというのがある.
詳細は例によって,ドキュメントを参照してほしいんだけど,要はコード中のアサーションが正しく動作しているかどうかを確認するためのテストで,しかもアサーションが失敗してプログラムが処理を中断し終了するかどうかをテストするものだ.
ちなみにアサーションというのは日本語だと「表明」とかって訳されていて,そこには実行時に絶対に満たされているべき条件を記載してやる.つまり,条件に書かれてる内容が満たされてることを期待してますよ,と表明しているわけだ.なお,その条件が満たされないと以後の処理は続けられない(続けると回復できない重大な問題が発生ししてしまう)場合に使う.その場合ってのは,つまり致命的なエラーが起きたってことだ.

では,試してみよう.
例によってディレクトリ構成.
gtest_advanced_death_proj/
├── CMakeLists.txt
├── assertion_func.cpp
├── assertion_func.h
├── compiler_settings.cmake
├── main.cpp
└── test
    └── advanced_unittest.cpp


compiler_settings.cmakeは以前から変更が無いので省略して,それ以外は以下のようになる.

CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
project(gtest_advanced_test)

include (compiler_settings.cmake)

enable_testing()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
message(STATUS GTEST_DIR=$ENV{GTEST_DIR})

include_directories($ENV{GTEST_DIR}/include)
link_directories($ENV{GTEST_DIR}/lib)

cxx_executable(assert_main "" main.cpp assertion_func.cpp)
cxx_test(gtest_advanced_test "" assertion_func.cpp test/advanced_unittest.cpp)


assertion_func.cpp
#include <cassert>
#include "assertion_func.h"

void Func(bool is_true)
{
    assert(is_true);
}


assertion_func.h
#ifndef ASSERTION_FUNC_H
#define ASSERTION_FUNC_H

void Func(bool is_true);

#endif // ASSERTION_FUNC_H


main.cpp
#include <iostream>
#include "assertion_func.h"

int main()
{
    bool test = false;
    Func(test);

    return 0;
}


test/advanced_unittest.cpp
#include <gtest/gtest.h>
#include <cassert>
#include "assertion_func.h"

TEST(MyDeathTest, FuncDeath)
{
    ASSERT_DEATH({
            bool test = false;
            Func(test);
            }, "");
}


では,ざっくり説明.
とりあえず,ビルドして,assertion_mainを実行してみると,以下のような表示が出てプログラムは終了する.
gtest_advanced_death_proj/assertion_func.cpp:6: void Func(bool): Assertion `is_true' failed.

これはassertion_func.cpp:6にis_trueが「true」であることを期待してますよと表明しておいたにもかかわらず,実際は「false」だったため,アサーションが失敗しました,ということだ.
で,これをテストするために,Google Testでは例えばASSERT_DEATHというマクロを用意してくれてる.書式は,
ASSERT_DEATH(statement, regex);
となっていて,statementにはプログラムが「クラッシュ」する文を書く.ちなみにここで言うクラッシュとは,ドキュメントによれば「プロセスが exit() または _exit() を0ではない引数で呼び出した場合. あるいは,シグナルによって殺された場合」を指す.だから正常終了した場合なんかは当てはまらない.で,ここには式だけじゃなくて例にあるように文も書ける.
あと,regexには標準エラー出力とマッチする正規表現を書ける.

アサーション自体はとても重要だ.だからアサーションが正しく機能するかをテストすることもとても重要だ.
Deathテストを自分で作るとすると多分とてもめんどくさいはず.それをGoogle Testが用意してくれてるのはとても助かる.しかも簡単に使える.
ぜひお試しあれ.
タグ:Deathテスト
nice!(0)  トラックバック(0) 
共通テーマ:日記・雑感

Google Testの上級ガイドにある「その他のアサーション」を試してみる [Google Test]

Google Testには本当にいろいろなアサーションが用意されている.
詳細は,「上級ガイド — Google Test ドキュメント日本語訳」に記載されてるので,そっちを参照してもらうとして,とりあえずその上級ガイドにある,「その他のアサーション」についていくつか試してみた.

で,詳細を説明しようと思ったけど,まぁ上級ガイドに書かれてることを試そうなんて人は,上記のドキュメントを読めば内容は理解できると思うので,とりあえず試したコードとメモ程度の内容だけ示しておくにとどめとく.ってまぁ説明するのが面倒なのと,実際のところ説明って言っても結局ドキュメントに書かれてることそのままになっちゃうからなんだけど.

というわけで,まずはディレクトリ構成.まぁいつもと同じなんだけど...
gtest_advanced_proj/
├── CMakeLists.txt
├── compiler_settings.cmake
└── test
    └── advanced_unittest.cpp


compiler_settings.cmakeは以前から変更が無いので省略して,それ以外のファイルを示すと,

CMakeLists.txt.
cmake_minimum_required(VERSION 2.6)
project(gtest_advanced_test)

include (compiler_settings.cmake)

enable_testing()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
message(STATUS GTEST_DIR=$ENV{GTEST_DIR})

include_directories($ENV{GTEST_DIR}/include)
link_directories($ENV{GTEST_DIR}/lib)

cxx_test(gtest_advanced_test "" test/advanced_unittest.cpp)


test/advanced_unittest.cpp
#include <gtest/gtest.h>

TEST(AdvancedTest, Succeed)
{
    SUCCEED();
}

TEST(AdvancedTest, Fail)
{
    FAIL() << "FAIL() is always fail.";   // 常に失敗
}

bool do_something(int x, int y)
{
    return x == y;
}

TEST(AdvancedTest, Predicate)
{
    int a = 3;
    int b = 5;
    EXPECT_PRED2(do_something, a, b);
    EXPECT_TRUE(do_something(a, b));
}

namespace testing {
AssertionResult AssertionSuccess();
AssertionResult AssertionFailure();
}

::testing::AssertionResult IsEven(int n)
{
    if ((n % 2) == 0) {
        return ::testing::AssertionSuccess();
    } else {
        return ::testing::AssertionFailure() << n << " is odd";
    }
}

bool IsEvenSimple(int n)
{
    return (n % 2) == 0;
}

int add(int x, int y)
{
    return x + y;
}

TEST(AdvancedTest, AssertionResult)
{
    EXPECT_TRUE(IsEven(add(3, 2)));
    EXPECT_TRUE(IsEvenSimple(add(3, 2)));
}

::testing::AssertionResult AssertDoSomething(const char* m_expr,
                                             const char* n_expr,
                                             int m,
                                             int n)
{
    if (do_something(m, n)) {
        return ::testing::AssertionSuccess();
    } else {
        return ::testing::AssertionFailure()
            << m_expr << " and " << n_expr << " ( " << m << " and " << n << ")"
            << " are not same.";
    }
}

TEST(AdvancedTest, PredicateFormat)
{
    int a = 5;
    int b = 8;
    EXPECT_PRED_FORMAT2(AssertDoSomething, a, b);
}

TEST(AdvancedTest, Float)
{
    float x = 0.1;
    float y = 0.01;
    EXPECT_EQ(x, y * 10) << "using EXPECT_EQ()";
    EXPECT_FLOAT_EQ(x, y * 10) << "using EXPECT_FLOAT_EQ()";
}

template <typename T> class Foo {
    public:
        void Bar() {
            ::testing::StaticAssertTypeEq<int, T>();
        }
};

void Test1()
{
    // 以下は::testing::StaticAssertTypeEqによりコンパイルエラーを発生させてくれる
    // コンパイルできなくなり,他のテストを試せなくなるのでコメントアウト
    /*
     * Foo<bool> foo;
     * foo.Bar();
     */
}


で,内容だけど,前半は主にテスト結果をいかに分かりやすくするかって感じのいろいろな機能で,エラーのときの表示を指定する方法なんかが書かれてる.これらは分かりやすいテストを書くには必要だし,あると便利なんだろうけど,まぁ面倒であまり使わない気もする...
あとは浮動小数点のテストと型の一致テストがちょろっと書かれてる.こっちはそこそこ使うかも.

んー,手抜きだな,今回は特に... まぁ,お試しあれ.
タグ:gtest
nice!(0)  トラックバック(0) 
共通テーマ:日記・雑感

Google MockのExpectationをいろいろ使ってみる [Google Test]

前回,Google Mockを使ってみた.
で,今回はさらにいろいろなExpectationを使ってみる.
という訳で,Google Mock ドキュメント日本語訳の超入門編に出てくるExpectationをいろいろ試してみる.

ファイル構成は前回と同じ.というか前回のファイルに追記してる.
変更したのは,painter.h,painter.cpp,test/mock_unittest.cppの3つだけだ.まずはこれを示そう.

painter.h
#ifndef PAINTER_H
#define PAINTER_H

#include "turtle.h"

class Painter {
public:
    Painter(Turtle* turtle)
        : turtle_(turtle) {}
    ~Painter() {}

    bool DrawCircle(int x, int y, int r);
    int GetPositionX();
    void Move(int steps);

private:
    Turtle* turtle_;
};

#endif // PAINTER_H


painter.cpp
#include "painter.h"

bool Painter::DrawCircle(int x, int y, int r)
{
    (void)x;
    (void)y;
    (void)r;

    turtle_->PenDown();
    return true;
}

int Painter::GetPositionX()
{
    return turtle_->GetX();
}

void Painter::Move(int steps)
{
    turtle_->Forward(steps * 10);
}


test/mock_unittest.cpp
#include "gtest/gtest.h"
#include "gmock/gmock.h"

#include "painter.h"
#include "mock_turtle.h"

TEST(PainterTest, CanDrawSomething)
{
    MockTurtle turtle;
    EXPECT_CALL(turtle, PenDown())
        .Times(::testing::AtLeast(1));
  
    Painter painter(&turtle);

    EXPECT_TRUE(painter.DrawCircle(0, 0, 10));
}

TEST(PointerTest, ReturnOfGetX)
{
    MockTurtle turtle;
    EXPECT_CALL(turtle, GetX())
        .Times(5)
        .WillOnce(::testing::Return(100))
        .WillOnce(::testing::Return(150))
        .WillRepeatedly(::testing::Return(200));
  
    Painter painter(&turtle);

    EXPECT_EQ(100, painter.GetPositionX());
    EXPECT_EQ(150, painter.GetPositionX());
    EXPECT_EQ(200, painter.GetPositionX());
    EXPECT_EQ(200, painter.GetPositionX());
    EXPECT_EQ(200, painter.GetPositionX());
}

TEST(PointerTest, CheckForwardArgs)
{
    MockTurtle turtle;
    EXPECT_CALL(turtle, Forward(100));
  
    Painter painter(&turtle);
    painter.Move(10);
}

TEST(PointerTest, CheckOnlyForwardCall)
{
    MockTurtle turtle;
    EXPECT_CALL(turtle, Forward(::testing::_));
  
    Painter painter(&turtle);
    painter.Move(3);
}

TEST(PointerTest, CheckForwardArgsGe)
{
    MockTurtle turtle;
    EXPECT_CALL(turtle, Forward(::testing::Ge(200)));
  
    Painter painter(&turtle);
    painter.Move(20);

    EXPECT_CALL(turtle, Forward(::testing::Ge(200)));
    painter.Move(30);
}

TEST(PointerTest, CardinalityCheck)
{
    MockTurtle turtle;
    EXPECT_CALL(turtle, GetX())
        .WillOnce(::testing::Return(100))
        .WillOnce(::testing::Return(150));
  
    Painter painter(&turtle);
    painter.GetPositionX();
    painter.GetPositionX();
}

TEST(PainterTest, MultipleExpectation)
{
    MockTurtle turtle;
    EXPECT_CALL(turtle, Forward(::testing::_));
    EXPECT_CALL(turtle, Forward(10))
        .Times(2);
  
    Painter painter(&turtle);
    painter.Move(1);
    painter.Move(3);
    painter.Move(1);
}

TEST(PainterTest, NoSequence)
{
    MockTurtle turtle;
    EXPECT_CALL(turtle, Forward(::testing::_));
    EXPECT_CALL(turtle, PenDown());
    EXPECT_CALL(turtle, GetX());
    
    Painter painter(&turtle);

    painter.GetPositionX();         // GetX()呼び出し
    painter.Move(3);                // Forward()呼び出し
    painter.DrawCircle(0, 0, 10);   // PenDown()呼び出し
}

TEST(PainterTest, SequenceRequired)
{
    MockTurtle turtle;

    {
        ::testing::InSequence dummy;

        EXPECT_CALL(turtle, Forward(::testing::_));
        EXPECT_CALL(turtle, PenDown());
        EXPECT_CALL(turtle, GetX());
    }
    
    Painter painter(&turtle);

    painter.Move(3);                // Forward()呼び出し
    painter.DrawCircle(0, 0, 10);   // PenDown()呼び出し
    painter.GetPositionX();         // GetX()呼び出し
}

TEST(PainterTest, SaturationNoSequence)
{
    MockTurtle turtle;

    for (int i = 3; i > 0; i--) {
        EXPECT_CALL(turtle, GetX())
            .WillOnce(::testing::Return(10 * i))
            .RetiresOnSaturation();
    }

    Painter painter(&turtle);
    EXPECT_EQ(10, painter.GetPositionX());
    EXPECT_EQ(20, painter.GetPositionX());
    EXPECT_EQ(30, painter.GetPositionX());
}

TEST(PainterTest, SaturationInSequence)
{
    MockTurtle turtle;

    ::testing::InSequence s;    // これがないとEXPECT_CALLを逆順にセットする必要がある

    for (int i = 1; i <= 3; i++) {
        EXPECT_CALL(turtle, GetX())
            .WillOnce(::testing::Return(10 * i))
            .RetiresOnSaturation();
    }

    Painter painter(&turtle);
    EXPECT_EQ(10, painter.GetPositionX());
    EXPECT_EQ(20, painter.GetPositionX());
    EXPECT_EQ(30, painter.GetPositionX());
}


前回と同様,コードとテストの詳細は前述のGoogle Mock ドキュメント日本語訳の超入門編をみてもらうとして,ポイントだけ説明しておく.
まず,今回,テスト用にPainterクラスにGetPositionX()とMove()を追加してる.まぁこの動作にはあまり意味は無くて,単にテスト例を示すためだけの処理になってる.

で,テストの内容だが,CanDrawSomethingは前回示したので置いておいて,それ以外について説明しておく.

まずReturnOfGetX.
これはEXPECT_CALLでGetX()の戻り値を設定してる.Mockに100, 150, 以後200を返すように設定する場合の例だ.WillOnceが1回だけ,WillRepeatedlyは繰り返し,設定した値を返すようになる.

次にCheckForwardArgsとCheckOnlyForwardCall.
テストするときに,引数チェックまでするか,単にメソッド呼び出しだけチェックしたいか,の2つのパターンがあると思う.で,引数チェックするならEXPECT_CALLで引数指定してやればいいし,呼び出しだけでいいなら,::testing::_というのがが使える.これにしておくと,引数は何でもいいから呼び出しだけをチェックできるようになる(引数の一致はチェックされなくなる).

CheckForwardArgsGe.
引数チェックに条件(たとえば「100以上であること」とか)を設定したければ,::testing::_の代わりに::testing::Ge(値)が使える.他にもいろいろ条件は選べる.これらは::testing::_も含め,matcherと呼ばれるようだ.

CardinalityCheck.
EXPECT_CALLには呼び出し回数としてTimes()を書けるけど,書かない場合でもGoogle Mockがそれなりに推測してうまくやってくれる.例えば,WillOnce()が2回書かれてれば,呼び出し回数は2回と推測してくれる.

MultipleExpectation.
複数のEXPECT_CALLを書く場合.
こいつはちょっと分かりにくいかもしれない.
まず複数のEXPECT_CALLが書かれてる場合だと,Google Mockは書かれてる逆の順番で一致を探索する仕様になってる.なので,今回の例だと,引数10が先に探索され,次に引数不問の呼び出しが探索される.逆の順序で探索してEXPECT_CALLと一致する呼び出しが見つかればOKだけど,そうじゃないとテストは失敗する.
例えば,今回の例だと,引数10の呼び出しが先に探索され,一致しなければ引数不問の呼び出しが探索される.で,今回のMove(3)は,引数10の呼び出しとは一致しないが引数不問の呼び出しとは一致するのでOKとなる.
注意が必要なのは,一致探索は逆順ということと,逆順で最初に一致判断できるものが採用されるという点だ.たとえば今回の例で,EXPECT_CALLの記載順序を変えて,引数不問の呼び出しを後に書くと,このテストは失敗するようになる.なぜなら引数不問呼び出しを後に書くとMove(1)が引数不問と一致していると判断されてしまい,次のMove(3)と一致するものがなくなってしまうからだ.たとえEXPECT_CALLに引数10の呼び出し,すなわちMove(1)と完全一致するEXPECT_CALLが書かれていても,その呼び出しとは判断されない.あくまで,逆順で探索し最初に一致判断できる引数不問のEXPECT_CALLが採用されてしまう.
ちょっと分かりにくい気がするが,まぁそういうことだ.
まぁ,この場合は呼び出しの順番は関係なくてとにかく引数10の呼び出しが2回あることと,それ以外が1回だけあることをテストしたい場合,つまり順番は関係ない場合向けということになる.

NoSequenceとSequenceRequired.
テストするときに,呼び出し順序まで指定したい場合もある.で,その場合の例.
::testing::InSequenceを書くと,EXPECT_CALLを書いた順番で呼び出さないとエラーになる.::testing::InSequenceが無ければ,MultipleExpectationのテストのように,逆順から探索される.順番はどうでもよくてメソッドの呼び出しがされているかだけチェックしたければ::testing::InSequenceはいらないけど,呼び出し順序,すなわちシーケンスを守らなければならないのであれば::testing::InSequenceを書いてやる.

SaturationNoSequenceとSaturationInSequence.
これも最初はちょっと分かりにくいかも.
基本は,呼び出しが一致するEXPECT_CALLがあった場合に,消えたりしないということだ.
んー,表現が分かりにくいな.ドキュメントによれば,「呼び出し回数が上限に達してもアクティブであり続ける」とある.
例えば,SaturationNoSequenceの例で言えば,もしRetiresOnSaturation()の記載がなければ,
EXPECT_CALL(turtle, GetX()).WillOnce(::testing::Return(30));
EXPECT_CALL(turtle, GetX()).WillOnce(::testing::Return(20));
EXPECT_CALL(turtle, GetX()).WillOnce(::testing::Return(10));
と書かれているのと等価で,前述の通り,EXPECT_CALLは逆順から探索され,最初に一致したものが有効になる.この場合だと,GetX()呼び出しをすると,最初に
EXPECT_CALL(turtle, GetX()).WillOnce(::testing::Return(10));
が一致するのでこれが有効となる.で,問題は,一回一致した
EXPECT_CALL(turtle, GetX()).WillOnce(::testing::Return(10));
はずっと有効で,消えたりしないということだ.再度GetX()を呼び出すと,やはり
EXPECT_CALL(turtle, GetX()).WillOnce(::testing::Return(10));
が一致することになるが,WiiOnce()といっているので,2回目の呼び出しはテストとしてエラーとなる.
で,これを避けたければ,RetiresOnSaturation()を書いてやればいい.これなら指定した呼び出し回数に一致したら消える(破棄される)ようになる.
で,さらに::testing::InSequenceも書いてやると,EXPECT_CALLの順番通り,指定回数通りの呼び出しチェックができるようになる.
このへんは,テスト内容(何をテストしたいか)によって使い分けるといいだろう.

では,お試しあれ.
タグ:Google Mock
nice!(0)  トラックバック(0) 
共通テーマ:日記・雑感

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。