C++の単体テストでGoogle C++ Testing Frameworkを使ってみたのでメモを残します。
Table of Contents
1 Google C++ Testing Framework
Google C++ Testing FrameworkとはC++のテスティングフレームワークで、 Windows, OsX, Linux等のマルチプラットフォームで動作します。
1.1 ソースコードの取得
Subversionから取得します。
$ svn checkout http://googletest.googlecode.com/svn/trunk/ googletest-read-only
リリース版のアーカイブもあるので、そちらを利用しても良いでしょう。
$ wget https://googletest.googlecode.com/files/gtest-1.7.0.zip $ unzip gtest-1.7.0.zip
以降ではgtest-1.7.0.zipを使用します。
1.2 libgtest.aとlibgtest_main.a
Google C++ Testing Frameworkを紹介しているサイトではlibgtest.aと libgtest_main.aの作成方法を紹介しております。
$ cd gtest-1.7.0 $ mkdir build $ cd build $ cmake ../ $ make $ ls CMakeCache.txt CMakeFiles Makefile cmake_install.cmake libgtest.a libgtest_main.a
libgtest.aとlibgtest_main.aはcmakeとmakeを実行した環境用のバイナリで、 マルチプラットフォーム対応の静的ライブラリではありません。 そこで静的ライブラリではなく、ソースコードそのものを利用する方法を紹 介します。
1.2.1 libgtest.a
テスティングフレームワークの各種処理を含んだライブラリです。
1.2.2 libgtest_main.a
ユーザが実装したソースコードを呼び出すmain関数を含んだライブラリです。
1.3 gtest-all.ccとgtest.hの作成
fuse_gtest_files.pyを用いることで、srcディレクトリ配下のソースコード をまとめたgtest-all.ccとincludeディレクトリ配下のインクルードファイ ルをまとめたgtest.hが作成されます。
$ cd gtest-1.7.0 $ ./scripts/fuse_gtest_files.py out $ tree out/ out/ `-- gtest |-- gtest-all.cc `-- gtest.h 1 directory, 2 files
ここで作成されたgtest-all.ccとgtest.hはsrc/gtest-all.ccと include/gtest.hとは異なるもので、src/gtest-all.cc等がincludeディレク ティブで参照している別のソースコードを展開したものがとなります。
ここで作成したgtest.hをインクルードし、ここで作成したgtest-all.ccを リンクすることでGoogle C++ Testing Frameworkを利用できます。 加えて、src/gtest_main.ccを利用することで、テスト呼び出すmain関数の 記述も簡略化できます。
$ cat hello.cpp #include <iostream> #include <gtest/gtest.h> TEST(hello, print) { std::cout << "Hello, World" << std::endl; } $ g++ -I./out hello.cpp out/gtest/gtest-all.cc src/gtest_main.cc $ ./a.out Running main() from gtest_main.cc [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from hello [ RUN ] hello.print Hello, World [ OK ] hello.print (0 ms) [----------] 1 test from hello (0 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (1 ms total) [ PASSED ] 1 test.
以上の通り、fuse_gtest_files.pyで作成したgtest-all.ccとgtest.h、元の ソースツリーに含まれるgtest_main.ccを各プラットフォームに持っていく ことで、マルチプラットフォーム対応のテストプログラムを作成できます。
1.4 Makefileの例
Google C++ Testing Frameworkのソースコードを以下の構成で置きます。
$ tree . |-- Makefile |-- gtest | |-- gtest-all.cc | |-- gtest.h | `-- gtest_main.cc `-- hello.cpp 1 directory, 5 files
Makefileは以下の通り、gtestディレクトリ配下へインクルードパスの設定、 gtest-all.ccとgtest_main.ccをtpバイナリ生成用のコードに追加、 Makefileと同じ階層の全てのcppファイルをtpバイナリに含むようにします (今回は標準出力にHello, Worldと出力するhello.cppのみ)。
SRCS := $(wildcard *.cpp) CXXFLAGS += -I. -lpthread GTEST_SRC := gtest/gtest-all.cc gtest/gtest_main.cc all: $(CXX) $(GTEST_SRC) $(SRCS) -o tp $(CXXFLAGS) clean: -@$(RM) -f tp
実行例は以下の通りです。
$ make c++ gtest/gtest-all.cc gtest/gtest_main.cc hello.cpp -o tp -I. -lpthread $ ./tp Running main() from gtest_main.cc [==========] Running 1 test from 1 test case. [----------] Global test environment set-up. [----------] 1 test from hello [ RUN ] hello.print Hello, World [ OK ] hello.print (0 ms) [----------] 1 test from hello (0 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test case ran. (0 ms total) [ PASSED ] 1 test.
2 テストプログラムの実装
2.1 最も単純な使い方
2.1.1 TESTマクロ
TESTマクロで実行するテストを定義します。
TEST(TestCaseName, TestName1) { /** Do test */ } TEST(TestCaseName, TestName2) { /** Do test */ }
例えば、クラスをテストする場合には、TestCaseNameにクラス名を、 TestNameにメソッドの正常系のテスト、メソッドの異常系のテスト等を使用 すれば良いと思います。
ここで注意すべき点は、TESTマクロで定義されたテストの実行順序は保証さ れない点です。テスト実行時に生成した乱数で順序がかわります。 あるテストが成功した後で実行されるべきテストがある場合は、ひとつのテ ストとしてまとめなくてはなりません。
2.1.2 EXPECT_TRUE / EXPECT_FALSE
doSomethingという名前のメソッドの戻り値がtrueであることを期待する場 合、EXPECT_TRUEマクロを使用します。もしdoSomethingメソッドの戻り値が falseの場合にはテストは失敗し、falseになった旨が結果に出力されます。 EXPECT_FALSEはfalseであることを期待値とするマクロです。
TEST(TestCaseName, TestName) { EXPECT_TRUE(doSomething()); }
2.1.3 EXPECT_EQ / EXPECT_NE
doSomethingという名前のメソッドの戻り値が1であることを期待する場合、 EXPECT_EQマクロを使用します。EXPECT_TRUE(doSomething() == 1)との違い は、テストが失敗した場合にdoSomethingメソッドの戻り値が何であったか をEXPECT_EQでは結果に表示されます。EXPECT_NEは値が等しくないことを期 待値とするマクロです。
TEST(TestCaseName, TestName) { EXPECT_EQ(doSomething(), 1); }
2.1.4 EXPECT_STREQ / EXPECT_STRNE
文字列比較用のマクロです。EXPECT_EQ / EXPECT_NEと同様です。
2.1.5 その他
Google C++ Testing Frameworkのマニュアルを参照して下さい。 条件が成立しない箇所でテストを停止するASSERT_XXXや、外部から与えられ たパラメータをテストで使用する方法等が記載されてます。
2.2 複数のテストでリソースを共有
リソースの共有はTEST_Fマクロで実現できます。
2.2.1 testing::TestクラスとTEST_Fマクロ
gtest.hで定義されているtesting::Testクラスを継承したクラスを定義し、 TEST_Fマクロでクラスに関連したテストを定義します。
class MyClass : public testing::Test { /** Define share resource */ }; TEST_F(MyClass, TestName) { /** Do test */ };
2.2.2 リソースはprotectedフィールドに定義
TEST_Fで定義したテストではtesting::Testを継承したクラスのprivateフィー ルドにアクセスできない為(TEST_Fマクロはさらにクラスを継承するクラス を定義するマクロになっている?)、TEST_Fで定義したテストで使用するリ ソース用の変数やメソッドはprotectedフィールドやpublicフィールドに定 義する必要があります。
2.2.3 グローバル変数とローカル変数
複数のTEST_Fで定義するテスト群で共有する変数はstaticな変数に、各テス トで独自に使用する変数はローカルな変数で定義します。
class MyClass : public testing::Test { protected: static int gValue; int mValue; }; int MyClass::gValue; TEST_F(MyClass, TestName1) { mValue = 1; /** Local value for TestName1 */ gValue = 1; /** Global value for MyClass */ }; TEST_F(MyClass, TestName) { mValue = 2; /** Local value for TestName2 */ gValue = 2; /** Global value for MyClass */ };
2.2.4 SetUpメソッドとTearDownメソッド
TEST_Fで定義した各テストの実行前にSetUpメソッドが呼ばれ、実行後に TearDownメソッドが呼ばれます。 これらはクラスのコンストラクタやデストラクタでも代替え可能かもしれま せん。 TEST_Fで定義したテスト毎に以下が実行されます。
1. クラスのコンストラクタが呼ばれる。 2. SetUpメソッドが呼ばれる。 3. TEST_Fで定義したテストが実行される。 3. TearDownメソッドが呼ばれる。 4. クラスのデストラクタが呼ばれる。
2.2.5 SetUpTestCaseメソッドとTearDownTestCaseメソッド
クラスのグローバルなメソッドです。TEST_Fで定義したテスト群が呼ばれる 前の最初にSetUpTestCaseメソッドが1度だけ呼ばれ、テスト群が呼ばれた後 の最後にTearDownTestCaseメソッドが1度だけ呼ばれます。 テスト群で共有する変数の初期化処理と終了処理を実行するのに用いること ができます。
以下の通りにクラスとテストを定義します。
#include <iostream> #include <gtest/gtest.h> class ShareResource : public testing::Test { protected: static int gValue; int mValue; static void SetUpTestCase() { std::cout << __PRETTY_FUNCTION__ << std::endl; } static void TearDownTestCase() { std::cout << __PRETTY_FUNCTION__ << std::endl; } void SetUp() { std::cout << __PRETTY_FUNCTION__ << std::endl; } void TearDown() { std::cout << __PRETTY_FUNCTION__ << std::endl; } public: ShareResource() : mValue(1) { std::cout << __PRETTY_FUNCTION__ << std::endl; } ~ShareResource() { std::cout << __PRETTY_FUNCTION__ << std::endl; } }; int ShareResource::gValue = 1; TEST_F(ShareResource, printValue) { std::cout << "mValue = " << mValue << std::endl; std::cout << "gValue = " << gValue << std::endl; } TEST_F(ShareResource, addOneAndPrintValue) { mValue += 1; gValue += 1; std::cout << "mValue = " << mValue << std::endl; std::cout << "gValue = " << gValue << std::endl; } TEST_F(ShareResource, addTwoAndPrintValue) { mValue += 2; gValue += 2; std::cout << "mValue = " << mValue << std::endl; std::cout << "gValue = " << gValue << std::endl; }
以下の実行結果が得られました。ただし、各種テストの実行順序は Google C++ Testing Frameworkで保証されておりません。
$ ./tp Running main() from gtest_main.cc [==========] Running 4 tests from 2 test cases. [----------] Global test environment set-up. [----------] 3 tests from ShareResource static void ShareResource::SetUpTestCase() [ RUN ] ShareResource.printValue ShareResource::ShareResource() virtual void ShareResource::SetUp() mValue = 1 gValue = 1 virtual void ShareResource::TearDown() virtual ShareResource::~ShareResource() [ OK ] ShareResource.printValue (0 ms) [ RUN ] ShareResource.addOneAndPrintValue ShareResource::ShareResource() virtual void ShareResource::SetUp() mValue = 2 gValue = 2 virtual void ShareResource::TearDown() virtual ShareResource::~ShareResource() [ OK ] ShareResource.addOneAndPrintValue (0 ms) [ RUN ] ShareResource.addTwoAndPrintValue ShareResource::ShareResource() virtual void ShareResource::SetUp() mValue = 3 gValue = 4 virtual void ShareResource::TearDown() virtual ShareResource::~ShareResource() [ OK ] ShareResource.addTwoAndPrintValue (0 ms) static void ShareResource::TearDownTestCase() [----------] 3 tests from ShareResource (0 ms total) [----------] 1 test from hello [ RUN ] hello.print Hello, World [ OK ] hello.print (0 ms) [----------] 1 test from hello (0 ms total) [----------] Global test environment tear-down [==========] 4 tests from 2 test cases ran. (23 ms total) [ PASSED ] 4 tests.