SSブログ

Google Mockを使ってみる [Google Test]

いままで,Google Testを使ってきたが,さらにGoogle Mockも使ってみる.

Google Mockとは,まぁ日本語ドキュメントがあるので参照してください.って書いてしまうとさすがに寂しいのでもうちょっと書くというか引用すると,Google Mockは「モッククラスを作成して使用するためのライブラリ」ということになる.
で,モックというのは,「Expectation を利用して事前にプログラムされたオブジェクト」だ.呼び出されたら何を返すかとか,何を引数として呼び出されるのかとか,そもそも何回呼び出されるのかとかをあらかじめ指定しておけるオブジェクトだ.なので,テストをするときに「本物」の代わりに使うと何かと便利になる.

ではまず,Google Mockを使えるようにしてみる.Google Testを使えるようにしたときと同様に,まずは適当なディレクトリを作成し,Google Mockをダウンロード,展開してやる.
cd ~/
mkdir googlemock
cd googlemock/
wget http://googlemock.googlecode.com/files/gmock-1.6.0.zip
unzip gmock-1.6.0.zip

そしたらmakeする.
このとき,ubuntuの環境だとエラーが出てmakeできない.なので,1カ所変更する.
変更対象のファイルは,~/googlemock/gmock-1.6.0/make/Makefileだ.このファイルの最後の部分,-lpthreadのところを変更してやる.
変更前
gmock_test : gmock_test.o gmock_main.a
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -lpthread $^ -o $@
変更後
gmock_test : gmock_test.o gmock_main.a
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) $^ -lpthread -o $@

変更したらmakeして,テストする.
cd gmock-1.6.0/make/
make
./gmock_test

テストでエラーが出なければOKだ.

そしたらライブラリはコピーしておく.
cd ~/googlemock/gmock-1.6.0/
mkdir lib
cp make/gmock_main.a lib/libgmock_main.a


あと,Google Mockのパスを設定しておく.~/.bashrcとかに,
export GMOCK_DIR=~/googlemock/gmock-1.6.0

としておけばよい.

これでひとまず準備は完了.
そしたら,Google Mock ドキュメント日本語訳の超入門編に出てくるMock Turtlesの例で試してみる.

いつもと同じように適当なディレクトリにturtles_mock_projディレクトリを作って,ファイル構成は以下のようにしてやる.
turtles_mock_proj/
├── CMakeLists.txt
├── compiler_settings.cmake
├── mock_turtle.h
├── painter.cpp
├── painter.h
├── test
│   └── mock_unittest.cpp
└── turtle.h


それぞれのファイルは以下のようになる.ちょっと長くなるが全部示そう.

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

include (compiler_settings.cmake)

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

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

cxx_gmock_test(mock_test "" painter.cpp  test/mock_unittest.cpp)

compiler_settings.cmake
# compiler settings
find_package(Threads)

set(cxx_base_flags "${cxx_base_flags} -Wall -Wshadow")
set(cxx_base_flags "${cxx_base_flags} -Wextra")
set(cxx_base_flags "${cxx_base_flags} -Werror")

function(cxx_executable_with_flags name cxx_flags libs)
  add_executable(${name} ${ARGN})
  if (cxx_flags)
    set_target_properties(${name}
      PROPERTIES
      COMPILE_FLAGS "${cxx_flags}")
  endif()
  foreach (lib "${libs}")
    target_link_libraries(${name} ${lib})
  endforeach()
endfunction()

function(cxx_executable name libs)
  cxx_executable_with_flags(${name} "${cxx_base_flags}" "${libs}" ${ARGN})
endfunction()

function(cxx_test_with_flags name cxx_flags libs)
  cxx_executable_with_flags(${name} "${cxx_flags}" "${libs}" ${ARGN})
  target_link_libraries(${name} gtest;gtest_main)
  target_link_libraries(${name} ${CMAKE_THREAD_LIBS_INIT})
  add_test(${name} ${name})
endfunction()

function(cxx_test name libs)
  cxx_test_with_flags("${name}" "${cxx_base_flags}" "${libs}" ${ARGN})
endfunction()

function(cxx_gmock_test name libs)
  cxx_test_with_flags("${name}" "${cxx_base_flags}" "${libs};gmock_main" ${ARGN})
endfunction()

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);

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;
}

turtle.h
#ifndef TURTLE_H
#define TURTLE_H

class Turtle {
public:
    Turtle() {}
    virtual ~Turtle() {}
    virtual void PenUp() = 0;
    virtual void PenDown() = 0;
    virtual void Forward(int distance) = 0;
    virtual void Turn(int degrees) = 0;
    virtual void GoTo(int x, int y) = 0;
    virtual int GetX() const = 0;
    virtual int GetY() const = 0;
};

#endif // TURTLE_H

mock_turtle.h
#ifndef MOCK_TURTLE_H
#define MOCK_TURTLE_H

#include "gmock/gmock.h"
#include "turtle.h"

class MockTurtle : public Turtle {
public:
    MOCK_METHOD0(PenUp, void());
    MOCK_METHOD0(PenDown, void());
    MOCK_METHOD1(Forward, void(int distance));
    MOCK_METHOD1(Turn, void(int degrees));
    MOCK_METHOD2(GoTo, void(int x, int y));
    MOCK_CONST_METHOD0(GetX, int());
    MOCK_CONST_METHOD0(GetY, int());
};

#endif // MOCK_TURTLE_H

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));
}


そしたらいつも通りビルドしてやればテストできる.
コードとテストの詳細は前述のGoogle Mock ドキュメント日本語訳の超入門編をみてもらうとして,ポイントだけ説明しておく.
今回の例だと,CanDrawSomethingのテストで,PenDown()が1回だけ呼ばれることをモックを使ってテストしていて,実際,DrawCircle()でPenDown()を呼び出しているので,テストは成功する.
試しに,DrawCircle()でのPenDown()の呼び出しをコメントアウトしたりするとテストが失敗するようになるはずだ.

今回はとりあえずメソッド呼び出しの回数テストだけしか試してないけど,Google Mockの雰囲気は分かると思う.
Google Mockは他にもいろいろできるんだけど,その辺についてはまた次回.
タグ:Google Mock
nice!(1)  トラックバック(0) 
共通テーマ:日記・雑感

Google Testでテストフィクスチャを使ってみる [Google Test]

Google Testを今回も試してみる.
で,今回は,テストフィクスチャを使ってみる.
テストフィクスチャってのは,Wikipediaを引用すると,「テストを実行、成功させるために必要な状態や前提条件の集合を、フィクスチャと呼ぶ。これらはテストコンテキストとも呼ばれる。開発者はテストの実行前にテストに適した状態を整え、テスト実行後に元の状態を復元することが望ましい。」とある.

要するに,これを使うと,テストの事前準備と事後処理をさせることができるようになる.

で,Google Testでの使い方なんだけど,まずはディレクトリ構成.
fixture_proj/
├── CMakeLists.txt
├── compiler_settings.cmake
└── test
    └── fixture_unittest.cpp


次にファイルについて.
compiler_settings.cmakeは前回と同じ内容で,CMakeLists.txtはプロジェクト名の変更と,テストファイル名がtest/fixture_unittest.cppに変わったことに伴う変更だけ.

で,今回のテストフィクスチャの使い方はfixture_unittest.cppに書かれてるわけだが,最初に言っておくと,今回のテストはあくまでテストフィクスチャの使い方の説明のためだけであって,テストそのものとかは正直あんまり意味は無い.

ではfixture_unittest.cppの中身.
#include <gtest/gtest.h>

class FixtureTest : public ::testing::Test {
protected:
    virtual void SetUp() {
        printf("TestFixture SetUp called\n");
        p = new int[10];
        for (int i = 0; i < 10; i++) {
            p[i] = i;
        }
    }

    virtual void TearDown() {
        printf("TestFixture TearDown called\n");
        delete [] p;
    }

    int *p;
};

TEST_F(FixtureTest, CheckIndex0Value)
{
    EXPECT_EQ(0, p[0]);
}

TEST_F(FixtureTest, UpdateValue)
{
    p[0] = 100;
    p[5] = 500;

    EXPECT_EQ(100, p[0]);
    EXPECT_EQ(500, p[5]);
}

TEST_F(FixtureTest, CheckAllValue)
{
    for (int i = 0; i < 10; i++) {
        EXPECT_EQ(i, p[i]);
    }
}


で,これをビルドしてテストを実行してみると分かるが,1つ1つのテスト(今回の例だとCheckIndex0Value,UpdateValue,CheckAllValue)が実行されるたびに,SetUp()とTearDown()が実行される.
ビルドディレクトリで,
make test ARGS=-V

とすると,SetUp()とTearDown()にあるprintf()がテストごとに実行されているのが分かるはずだ.
また,この例で試してみたところ,CheckAllValueのテストが実行されてからCheckAllValueのテストが実行されるようだが,SetUp()で毎回配列の初期化がされるので,UpdateValueのテストで配列の値の変更をしてもCheckAllValueのテストを実行するときには再度配列の初期化がされているのでテストは成功する.

というわけで,1つ1つのテストに対して前提条件があって,常にその条件を満たすようにしたい場合には,このテストフィクスチャの仕組みがうまく機能するということだ.

今回の例はあんまり参考にならないかもしれないが,実際にいろいろなテストを書いてると,こういった前提条件を整えるというのはどうしても必要になってくる.
これがフレームワークとして用意されてるのはとても助かる.

便利なのでぜひ使ってみてくださいな.
nice!(0)  トラックバック(0) 
共通テーマ:日記・雑感

CMakeでコンパイルオプションを設定してみる [Google Test]

以前,Google Testを使ってみた.このときはお試しだったので,とりあえずコンパイルできてテストできればよかったんだけど,実際に使い続けるとなるとコンパイルオプションをちゃんと設定したい.

コンパイラが教えてくれるエラーやワーニングは,(ときどき余計なお世話だと思うこともあるけど)ちゃんとしたコードを書くためにはとても役に立つと思う.
少なくとも,世間一般でよく言われるコンパイルオプションは設定しておかないと...

というわけで,CMakeでやるとしたらどうするかを調べてみた.
で,お手軽には,CMakeの変数「CMAKE_CXX_FLAGS」にコンパイルオプションを設定してやればいいようだ.
ただ,もうちょっと柔軟にコンパイルオプションを設定するんだと,CMakeの「set_target_properties」コマンドを使うといいらしい.これだと,ビルドするターゲットごとにコンパイルオプションを設定するとかってこともできるようになる.例えば,リリースするバイナリをビルドするときとテストのときでコンパイルオプションを変えるとかもできるようになる.

で,参考にしたのはGoogle Testのライブラリの設定だ.
Google Testをダウンロードしたディレクトリの中に,CMakeLists.txtと,cmake/internal_utils.cmakeというファイルがあって,それを参考にしてる.というかここから必要な部分だけ抜き出してる.

では,具体的に,以前作ったadd_projに対してどうなるかだが,細かい説明は抜きにしてとりあえず結果を示すと以下のようになる.

まず,ディレクトリ構成
add_proj/
├── CMakeLists.txt
├── add.cpp
├── add.h
├── compiler_settings.cmake <--- このファイルを追加
├── main.cpp
└── test
    └── add_unittest.cpp


で,今回変更したファイルは,CMakeLists.txtと,追加したcompiler_settings.cmakeだけ.
まず,CMakeLists.txt
cmake_minimum_required(VERSION 2.6)
project(add)

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(add_main "" main.cpp add.cpp)
cxx_test(add "" add.cpp test/add_unittest.cpp)

次にcompiler_settings.cmake
# compiler settings
find_package(Threads)

set(cxx_base_flags "${cxx_base_flags} -Wall -Wshadow")
set(cxx_base_flags "${cxx_base_flags} -Wextra")
set(cxx_base_flags "${cxx_base_flags} -Werror")

function(cxx_executable_with_flags name cxx_flags libs)
  add_executable(${name} ${ARGN})
  if (cxx_flags)
    set_target_properties(${name}
      PROPERTIES
      COMPILE_FLAGS "${cxx_flags}")
  endif()
  foreach (lib "${libs}")
    target_link_libraries(${name} ${lib})
  endforeach()
endfunction()

function(cxx_executable name libs)
  cxx_executable_with_flags(${name} "${cxx_base_flags}" "${libs}" ${ARGN})
endfunction()

function(cxx_test_with_flags name cxx_flags libs)
  cxx_executable_with_flags(${name} "${cxx_flags}" "${libs}" ${ARGN})
  target_link_libraries(${name} gtest;gtest_main)
  target_link_libraries(${name} ${CMAKE_THREAD_LIBS_INIT})
  add_test(${name} ${name})
endfunction()

function(cxx_test name libs)
  cxx_test_with_flags("${name}" "${cxx_base_flags}" "${libs}" ${ARGN})
endfunction()


で,内容をざっくり書くと...
compiler_settings.cmakeで必要なコンパイルオプションの設定とCMakeのfunctionを定義してる.で,CMakeLists.txtからは定義したfunctionを呼び出すようにしてる.
ちなみに今回設定しているコンパイルオプションは,-Wall -Wshadow -Wextra -Werrorの4つだけ.他にも大事なオプションはいっぱいあるんだけどとりあえず今回はこれだけ.で,これの意味は,まぁGoogle先生に聞いてください.

なお,実際にちゃんとコンパイルオプションが設定されているか確認するには,ビルドディレクトリで,
make VERBOSE=1

としてやると,詳細が表示されるようになる.

今回は説明がだいぶざっくりになってるんだけど... 気が向いたらもう少し詳しい説明を書きます...

では,お試しあれ.

nice!(0)  トラックバック(0) 
共通テーマ:日記・雑感

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