Google Testで「値をパラメータ化したテスト」を試してみるの続きを書いてみる [Google Test]
前回,Google Testで「値をパラメータ化したテスト」を試してみたが,その説明が途中で終わっていた.
ずいぶん間があいてしまったが,続きを書いてみる.
で,前回の続きだが,テストするときにパラメータとして入力値と期待値のペアを複数用意しておいて,それを使ってテストを実行したい場合がよくあると思う.
今回の例で言えば,奇数ならfalseで偶数ならtrueになるわけだが,テストのパラメータとして,入力値としていくつかの奇数と偶数と,その期待値であるbool値を具体的な値として用意しておく.
例えば,
で,そんな場合には,tupleが使える環境ならtupleで,もし使えないなら適当な構造体を定義してやって,テストパラメータとして与えてやればいい.
今回のケースなら,intとboolの要素を持つようにしておいて,それぞれ入力値と期待値として使う.
::testing::Values()を使って,tupleを列挙してもいいし,あらかじめ配列にしておいて::testing::ValuesIn()でもいい.TEST_P内での使い方は,コードを見てみれば分かると思う.
これだと,テストパラメータとして入力値と期待値をコード中に列挙してやれるので,テスト対象のコードの挙動が分かりやすくなると思う.
最後に,::testing::Combine()について書いておく.
テストするときに,多くのパラメータの組み合わせに対してテストをしておきたい場合がある.
たとえば今回の例で,mul()に対して,少なくとも九九の計算が正しいことをテストしておきたいとする.そうすると,2つの入力値をそれぞれ1〜9まで変化させてテストする必要があり,組み合わせとしては81通りが必要になる.
こんな場合に,::testing::Combine()が使える.これの引数にテストしたい2つの入力値(今回の場合なら1〜9を2つ)を書いておくと,::testing::Combine()が全組み合わせ,すなわち81通りに展開してくれる.これが無ければ自分でループ文を書くとかをしなければならないので,とても楽ちん.
では,お試しあれ.
ずいぶん間があいてしまったが,続きを書いてみる.
で,前回の続きだが,テストするときにパラメータとして入力値と期待値のペアを複数用意しておいて,それを使ってテストを実行したい場合がよくあると思う.
今回の例で言えば,奇数ならfalseで偶数ならtrueになるわけだが,テストのパラメータとして,入力値としていくつかの奇数と偶数と,その期待値であるbool値を具体的な値として用意しておく.
例えば,
といった具合だ.--------------- input expect --------------- 0 true 1 false 2 true 3 false 4 true ---------------
で,そんな場合には,tupleが使える環境ならtupleで,もし使えないなら適当な構造体を定義してやって,テストパラメータとして与えてやればいい.
今回のケースなら,intとboolの要素を持つようにしておいて,それぞれ入力値と期待値として使う.
::testing::Values()を使って,tupleを列挙してもいいし,あらかじめ配列にしておいて::testing::ValuesIn()でもいい.TEST_P内での使い方は,コードを見てみれば分かると思う.
これだと,テストパラメータとして入力値と期待値をコード中に列挙してやれるので,テスト対象のコードの挙動が分かりやすくなると思う.
最後に,::testing::Combine()について書いておく.
テストするときに,多くのパラメータの組み合わせに対してテストをしておきたい場合がある.
たとえば今回の例で,mul()に対して,少なくとも九九の計算が正しいことをテストしておきたいとする.そうすると,2つの入力値をそれぞれ1〜9まで変化させてテストする必要があり,組み合わせとしては81通りが必要になる.
こんな場合に,::testing::Combine()が使える.これの引数にテストしたい2つの入力値(今回の場合なら1〜9を2つ)を書いておくと,::testing::Combine()が全組み合わせ,すなわち81通りに展開してくれる.これが無ければ自分でループ文を書くとかをしなければならないので,とても楽ちん.
では,お試しあれ.
Google Testで「値をパラメータ化したテスト」を試してみる [Google Test]
Google Testでは値をいろいろと変えてテストをしたいときのために,「値をパラメータ化したテスト」というのが用意されている.詳細は,いつもの通りドキュメントを参照してほしいんだけど,要は値を変えてテストするときに自分でいちいち値ごとにテストをコピーしたりしなくても,たとえば値の組み合わせを用意してやれば,Google Testがそれにあわせて値を変えながらテストしてくれるという仕組みがあるということだ.
では,具体的にやってみよう.
例によってディレクトリ構成.
compiler_settings.cmakeは以前から変更が無いので省略して,それ以外は以下のようになる.
CMakeLists.txt
test/advanced_unittest.cpp
CMakeLists.txtは特に変わったことはしていないので,test/advanced_unittest.cppだけ説明しよう.
では,上の方から順番に...
まず,IsEven()というのがテスト対象で,これについて引数をいくつか値を変えてテストしたいとする.例えば,0以上10未満かつ2刻みの値を使ってテストしたいとする.
で,値をパラメータ化したテストなので,フィクスチャクラスとして,::testing::TestWithParam<T>から派生させたParamTestIntクラスを定義してやる必要がある.今はint型の値をパラメータ化したテストをしたいのでTはintだ.
そしたらTEST_Pを使って,テストを定義してやる.普段TEST_Fでやってるように定義すればいい.で,ここでGetParam()ってのを呼び出すと,テストパラメータを呼び出せるようになる.
じゃあテストパラメータってどう定義するのかってことなんだけど,INSTANTIATE_TEST_CASE_Pってのを使う.引数は,順に,インスタンス化するときの名前,フィクスチャクラス名,そしてテストパラメータだ.上記の例で言えば,TestDataIntRangeという名前でテストをインスタンス化し,そのときのテストフィクスチャクラスはParamTestIntで,パラメータは,::testing::Range(0, 10, 2)だ.
ここで,::testing::Range(0, 10, 2)ってのはGoogle Testが用意してくれてる便利なやつで,0以上10未満step2を意味する.つまり,0,2,4,6,8がテストパラメータとなる.で,TEST_P内のGetParam()ではこの値が取得できるという訳だ.
要するに,ここまでの記述で,IsEven()に対して引数が0,2,4,6,8の場合のテストができるということになる.
楽ちん.
なお,パラメータは::testing::Range()以外にもいろいろあって,たとえば::testing::Values(0, 2, 4, 6, 8)というのも使える.こっちはまさに0,2,4,6,8をパラメータとして使うって言う場合の書き方だ.他にもいくつかあるけど,それはまずはドキュメントを参照してくださいな.
ちょっと長くなってきたので,まだ最初しか説明してないけど,とりあえず今回はここまで.続きはまた次回...
では,具体的にやってみよう.
例によってディレクトリ構成.
gtest_advanced_param_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> #include <tr1/tuple> #include <iostream> bool IsEven(int n) { return (n % 2) == 0; } class ParamTestInt : public ::testing::TestWithParam<int> {}; TEST_P(ParamTestInt, IsEvenTest) { EXPECT_TRUE(IsEven(GetParam())); } INSTANTIATE_TEST_CASE_P( TestDataIntRange, ParamTestInt, ::testing::Range(0, 10, 2)); INSTANTIATE_TEST_CASE_P( TestDataIntValues, ParamTestInt, ::testing::Values(0, 2, 4, 6, 8)); class ParamTestTuple : public ::testing::TestWithParam< std::tr1::tuple<int, bool> > {}; TEST_P(ParamTestTuple, IsEvenTest) { const int n = std::tr1::get<0>(GetParam()); const bool expected = std::tr1::get<1>(GetParam()); EXPECT_EQ(expected, IsEven(n)); } INSTANTIATE_TEST_CASE_P( TestDataTuple, ParamTestTuple, ::testing::Values( std::tr1::make_tuple( 1, false), std::tr1::make_tuple( 2, true), std::tr1::make_tuple( 3, false), std::tr1::make_tuple( 0, true), std::tr1::make_tuple( 100, true) ) ); std::tr1::tuple<int, bool> TestDataArray[] = { std::tr1::make_tuple(11, false), std::tr1::make_tuple(12, true), }; INSTANTIATE_TEST_CASE_P( TestDataTuple2, ParamTestTuple, ::testing::ValuesIn(TestDataArray) ); struct TestData { int n; bool expected; } test_data[] = { { 21, false, }, { 22, true, }, }; class ParamTestStruct : public ::testing::TestWithParam<TestData> {}; TEST_P(ParamTestStruct, IsEvenTest) { const int n = GetParam().n; const bool expected = GetParam().expected; EXPECT_EQ(expected, IsEven(n)); } INSTANTIATE_TEST_CASE_P( TestDataSturct, ParamTestStruct, ::testing::ValuesIn(test_data) ); int mul(int x, int y) { return x * y; } class ParamTestCombine : public ::testing::TestWithParam< ::std::tr1::tuple<int, int> > {}; TEST_P(ParamTestCombine, MulTest) { int x = std::tr1::get<0>(GetParam()); int y = std::tr1::get<1>(GetParam()); std::cout << x << " * " << y << " = " << mul(x, y) << std::endl; EXPECT_EQ(x * y, mul(x, y)); } INSTANTIATE_TEST_CASE_P( TestDataCombine, ParamTestCombine, ::testing::Combine(::testing::Range(1,10), ::testing::Range(1,10)) );
CMakeLists.txtは特に変わったことはしていないので,test/advanced_unittest.cppだけ説明しよう.
では,上の方から順番に...
まず,IsEven()というのがテスト対象で,これについて引数をいくつか値を変えてテストしたいとする.例えば,0以上10未満かつ2刻みの値を使ってテストしたいとする.
で,値をパラメータ化したテストなので,フィクスチャクラスとして,::testing::TestWithParam<T>から派生させたParamTestIntクラスを定義してやる必要がある.今はint型の値をパラメータ化したテストをしたいのでTはintだ.
そしたらTEST_Pを使って,テストを定義してやる.普段TEST_Fでやってるように定義すればいい.で,ここでGetParam()ってのを呼び出すと,テストパラメータを呼び出せるようになる.
じゃあテストパラメータってどう定義するのかってことなんだけど,INSTANTIATE_TEST_CASE_Pってのを使う.引数は,順に,インスタンス化するときの名前,フィクスチャクラス名,そしてテストパラメータだ.上記の例で言えば,TestDataIntRangeという名前でテストをインスタンス化し,そのときのテストフィクスチャクラスはParamTestIntで,パラメータは,::testing::Range(0, 10, 2)だ.
ここで,::testing::Range(0, 10, 2)ってのはGoogle Testが用意してくれてる便利なやつで,0以上10未満step2を意味する.つまり,0,2,4,6,8がテストパラメータとなる.で,TEST_P内のGetParam()ではこの値が取得できるという訳だ.
要するに,ここまでの記述で,IsEven()に対して引数が0,2,4,6,8の場合のテストができるということになる.
楽ちん.
なお,パラメータは::testing::Range()以外にもいろいろあって,たとえば::testing::Values(0, 2, 4, 6, 8)というのも使える.こっちはまさに0,2,4,6,8をパラメータとして使うって言う場合の書き方だ.他にもいくつかあるけど,それはまずはドキュメントを参照してくださいな.
ちょっと長くなってきたので,まだ最初しか説明してないけど,とりあえず今回はここまで.続きはまた次回...
Google TestでDeathテストを使ってみる [Google Test]
Google TestにはDeathテストというのがある.
詳細は例によって,ドキュメントを参照してほしいんだけど,要はコード中のアサーションが正しく動作しているかどうかを確認するためのテストで,しかもアサーションが失敗してプログラムが処理を中断し終了するかどうかをテストするものだ.
ちなみにアサーションというのは日本語だと「表明」とかって訳されていて,そこには実行時に絶対に満たされているべき条件を記載してやる.つまり,条件に書かれてる内容が満たされてることを期待してますよ,と表明しているわけだ.なお,その条件が満たされないと以後の処理は続けられない(続けると回復できない重大な問題が発生ししてしまう)場合に使う.その場合ってのは,つまり致命的なエラーが起きたってことだ.
では,試してみよう.
例によってディレクトリ構成.
compiler_settings.cmakeは以前から変更が無いので省略して,それ以外は以下のようになる.
CMakeLists.txt
assertion_func.cpp
assertion_func.h
main.cpp
test/advanced_unittest.cpp
では,ざっくり説明.
とりあえず,ビルドして,assertion_mainを実行してみると,以下のような表示が出てプログラムは終了する.
これはassertion_func.cpp:6にis_trueが「true」であることを期待してますよと表明しておいたにもかかわらず,実際は「false」だったため,アサーションが失敗しました,ということだ.
で,これをテストするために,Google Testでは例えばASSERT_DEATHというマクロを用意してくれてる.書式は,
あと,regexには標準エラー出力とマッチする正規表現を書ける.
アサーション自体はとても重要だ.だからアサーションが正しく機能するかをテストすることもとても重要だ.
Deathテストを自分で作るとすると多分とてもめんどくさいはず.それをGoogle Testが用意してくれてるのはとても助かる.しかも簡単に使える.
ぜひお試しあれ.
詳細は例によって,ドキュメントを参照してほしいんだけど,要はコード中のアサーションが正しく動作しているかどうかを確認するためのテストで,しかもアサーションが失敗してプログラムが処理を中断し終了するかどうかをテストするものだ.
ちなみにアサーションというのは日本語だと「表明」とかって訳されていて,そこには実行時に絶対に満たされているべき条件を記載してやる.つまり,条件に書かれてる内容が満たされてることを期待してますよ,と表明しているわけだ.なお,その条件が満たされないと以後の処理は続けられない(続けると回復できない重大な問題が発生ししてしまう)場合に使う.その場合ってのは,つまり致命的なエラーが起きたってことだ.
では,試してみよう.
例によってディレクトリ構成.
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というマクロを用意してくれてる.書式は,
となっていて,statementにはプログラムが「クラッシュ」する文を書く.ちなみにここで言うクラッシュとは,ドキュメントによれば「プロセスが exit() または _exit() を0ではない引数で呼び出した場合. あるいは,シグナルによって殺された場合」を指す.だから正常終了した場合なんかは当てはまらない.で,ここには式だけじゃなくて例にあるように文も書ける.ASSERT_DEATH(statement, regex);
あと,regexには標準エラー出力とマッチする正規表現を書ける.
アサーション自体はとても重要だ.だからアサーションが正しく機能するかをテストすることもとても重要だ.
Deathテストを自分で作るとすると多分とてもめんどくさいはず.それをGoogle Testが用意してくれてるのはとても助かる.しかも簡単に使える.
ぜひお試しあれ.
タグ:Deathテスト
Google Testの上級ガイドにある「その他のアサーション」を試してみる [Google Test]
Google Testには本当にいろいろなアサーションが用意されている.
詳細は,「上級ガイド — Google Test ドキュメント日本語訳」に記載されてるので,そっちを参照してもらうとして,とりあえずその上級ガイドにある,「その他のアサーション」についていくつか試してみた.
で,詳細を説明しようと思ったけど,まぁ上級ガイドに書かれてることを試そうなんて人は,上記のドキュメントを読めば内容は理解できると思うので,とりあえず試したコードとメモ程度の内容だけ示しておくにとどめとく.ってまぁ説明するのが面倒なのと,実際のところ説明って言っても結局ドキュメントに書かれてることそのままになっちゃうからなんだけど.
というわけで,まずはディレクトリ構成.まぁいつもと同じなんだけど...
compiler_settings.cmakeは以前から変更が無いので省略して,それ以外のファイルを示すと,
CMakeLists.txt.
test/advanced_unittest.cpp
で,内容だけど,前半は主にテスト結果をいかに分かりやすくするかって感じのいろいろな機能で,エラーのときの表示を指定する方法なんかが書かれてる.これらは分かりやすいテストを書くには必要だし,あると便利なんだろうけど,まぁ面倒であまり使わない気もする...
あとは浮動小数点のテストと型の一致テストがちょろっと書かれてる.こっちはそこそこ使うかも.
んー,手抜きだな,今回は特に... まぁ,お試しあれ.
詳細は,「上級ガイド — 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
Google MockのExpectationをいろいろ使ってみる [Google Test]
前回,Google Mockを使ってみた.
で,今回はさらにいろいろなExpectationを使ってみる.
という訳で,Google Mock ドキュメント日本語訳の超入門編に出てくるExpectationをいろいろ試してみる.
ファイル構成は前回と同じ.というか前回のファイルに追記してる.
変更したのは,painter.h,painter.cpp,test/mock_unittest.cppの3つだけだ.まずはこれを示そう.
painter.h
painter.cpp
test/mock_unittest.cpp
前回と同様,コードとテストの詳細は前述の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()の記載がなければ,
が一致するのでこれが有効となる.で,問題は,一回一致した
はずっと有効で,消えたりしないということだ.再度GetX()を呼び出すと,やはり
が一致することになるが,WiiOnce()といっているので,2回目の呼び出しはテストとしてエラーとなる.
で,これを避けたければ,RetiresOnSaturation()を書いてやればいい.これなら指定した呼び出し回数に一致したら消える(破棄される)ようになる.
で,さらに::testing::InSequenceも書いてやると,EXPECT_CALLの順番通り,指定回数通りの呼び出しチェックができるようになる.
このへんは,テスト内容(何をテストしたいか)によって使い分けるといいだろう.
では,お試しあれ.
で,今回はさらにいろいろな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は逆順から探索され,最初に一致したものが有効になる.この場合だと,GetX()呼び出しをすると,最初に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(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
Google Mockを使ってみる [Google Test]
いままで,Google Testを使ってきたが,さらにGoogle Mockも使ってみる.
Google Mockとは,まぁ日本語ドキュメントがあるので参照してください.って書いてしまうとさすがに寂しいのでもうちょっと書くというか引用すると,Google Mockは「モッククラスを作成して使用するためのライブラリ」ということになる.
で,モックというのは,「Expectation を利用して事前にプログラムされたオブジェクト」だ.呼び出されたら何を返すかとか,何を引数として呼び出されるのかとか,そもそも何回呼び出されるのかとかをあらかじめ指定しておけるオブジェクトだ.なので,テストをするときに「本物」の代わりに使うと何かと便利になる.
ではまず,Google Mockを使えるようにしてみる.Google Testを使えるようにしたときと同様に,まずは適当なディレクトリを作成し,Google Mockをダウンロード,展開してやる.
そしたらmakeする.
このとき,ubuntuの環境だとエラーが出てmakeできない.なので,1カ所変更する.
変更対象のファイルは,~/googlemock/gmock-1.6.0/make/Makefileだ.このファイルの最後の部分,-lpthreadのところを変更してやる.
変更前
変更したらmakeして,テストする.
テストでエラーが出なければOKだ.
そしたらライブラリはコピーしておく.
あと,Google Mockのパスを設定しておく.~/.bashrcとかに,
としておけばよい.
これでひとまず準備は完了.
そしたら,Google Mock ドキュメント日本語訳の超入門編に出てくるMock Turtlesの例で試してみる.
いつもと同じように適当なディレクトリにturtles_mock_projディレクトリを作って,ファイル構成は以下のようにしてやる.
それぞれのファイルは以下のようになる.ちょっと長くなるが全部示そう.
CMakeLists.txt
compiler_settings.cmake
painter.h
painter.cpp
turtle.h
mock_turtle.h
test/mock_unittest.cpp
そしたらいつも通りビルドしてやればテストできる.
コードとテストの詳細は前述のGoogle Mock ドキュメント日本語訳の超入門編をみてもらうとして,ポイントだけ説明しておく.
今回の例だと,CanDrawSomethingのテストで,PenDown()が1回だけ呼ばれることをモックを使ってテストしていて,実際,DrawCircle()でPenDown()を呼び出しているので,テストは成功する.
試しに,DrawCircle()でのPenDown()の呼び出しをコメントアウトしたりするとテストが失敗するようになるはずだ.
今回はとりあえずメソッド呼び出しの回数テストだけしか試してないけど,Google Mockの雰囲気は分かると思う.
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
Google Testでテストフィクスチャを使ってみる [Google Test]
Google Testを今回も試してみる.
で,今回は,テストフィクスチャを使ってみる.
テストフィクスチャってのは,Wikipediaを引用すると,「テストを実行、成功させるために必要な状態や前提条件の集合を、フィクスチャと呼ぶ。これらはテストコンテキストとも呼ばれる。開発者はテストの実行前にテストに適した状態を整え、テスト実行後に元の状態を復元することが望ましい。」とある.
要するに,これを使うと,テストの事前準備と事後処理をさせることができるようになる.
で,Google Testでの使い方なんだけど,まずはディレクトリ構成.
次にファイルについて.
compiler_settings.cmakeは前回と同じ内容で,CMakeLists.txtはプロジェクト名の変更と,テストファイル名がtest/fixture_unittest.cppに変わったことに伴う変更だけ.
で,今回のテストフィクスチャの使い方はfixture_unittest.cppに書かれてるわけだが,最初に言っておくと,今回のテストはあくまでテストフィクスチャの使い方の説明のためだけであって,テストそのものとかは正直あんまり意味は無い.
ではfixture_unittest.cppの中身.
で,これをビルドしてテストを実行してみると分かるが,1つ1つのテスト(今回の例だとCheckIndex0Value,UpdateValue,CheckAllValue)が実行されるたびに,SetUp()とTearDown()が実行される.
ビルドディレクトリで,
とすると,SetUp()とTearDown()にあるprintf()がテストごとに実行されているのが分かるはずだ.
また,この例で試してみたところ,CheckAllValueのテストが実行されてからCheckAllValueのテストが実行されるようだが,SetUp()で毎回配列の初期化がされるので,UpdateValueのテストで配列の値の変更をしてもCheckAllValueのテストを実行するときには再度配列の初期化がされているのでテストは成功する.
というわけで,1つ1つのテストに対して前提条件があって,常にその条件を満たすようにしたい場合には,このテストフィクスチャの仕組みがうまく機能するということだ.
今回の例はあんまり参考にならないかもしれないが,実際にいろいろなテストを書いてると,こういった前提条件を整えるというのはどうしても必要になってくる.
これがフレームワークとして用意されてるのはとても助かる.
便利なのでぜひ使ってみてくださいな.
で,今回は,テストフィクスチャを使ってみる.
テストフィクスチャってのは,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つのテストに対して前提条件があって,常にその条件を満たすようにしたい場合には,このテストフィクスチャの仕組みがうまく機能するということだ.
今回の例はあんまり参考にならないかもしれないが,実際にいろいろなテストを書いてると,こういった前提条件を整えるというのはどうしても必要になってくる.
これがフレームワークとして用意されてるのはとても助かる.
便利なのでぜひ使ってみてくださいな.
タグ:テストフィクスチャ
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に対してどうなるかだが,細かい説明は抜きにしてとりあえず結果を示すと以下のようになる.
まず,ディレクトリ構成
で,今回変更したファイルは,CMakeLists.txtと,追加したcompiler_settings.cmakeだけ.
まず,CMakeLists.txt
次にcompiler_settings.cmake
で,内容をざっくり書くと...
compiler_settings.cmakeで必要なコンパイルオプションの設定とCMakeのfunctionを定義してる.で,CMakeLists.txtからは定義したfunctionを呼び出すようにしてる.
ちなみに今回設定しているコンパイルオプションは,-Wall -Wshadow -Wextra -Werrorの4つだけ.他にも大事なオプションはいっぱいあるんだけどとりあえず今回はこれだけ.で,これの意味は,まぁGoogle先生に聞いてください.
なお,実際にちゃんとコンパイルオプションが設定されているか確認するには,ビルドディレクトリで,
としてやると,詳細が表示されるようになる.
今回は説明がだいぶざっくりになってるんだけど... 気が向いたらもう少し詳しい説明を書きます...
では,お試しあれ.
コンパイラが教えてくれるエラーやワーニングは,(ときどき余計なお世話だと思うこともあるけど)ちゃんとしたコードを書くためにはとても役に立つと思う.
少なくとも,世間一般でよく言われるコンパイルオプションは設定しておかないと...
というわけで,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
としてやると,詳細が表示されるようになる.
今回は説明がだいぶざっくりになってるんだけど... 気が向いたらもう少し詳しい説明を書きます...
では,お試しあれ.
Google Testのアサーションをいろいろ使ってみる [Google Test]
前回に引き続き,Google Testを試してみる.
Google Testにはいろいろなアサーションが用意されてるので,それを試してみる.
前回と同様に適当なディレクトリにassert_projというディレクトリを作って,ファイル構成は以下のようにしてやる.なお,今回はアサーションのテストだけしかしないので,作成するファイルはCMakeLists.txtとassert_unittest.cppだけだ.
CMakeLists.txt
assert_unittest.cpp
じゃあビルド.前回と同様,assert_buildディレクトリを作ってビルド.
これで,assert_testバイナリが生成される.
実行は,
でOK.ARGS=-Vにすると詳細が表示されるんだけど,テストに失敗したときだけ詳細が表示されればいいんだったら,ARGS=--output-on-failureにするといい.ま,テスト成功のときはシンプルな表示で失敗のときにいろいろ表示されるほうが使いやすいかも.
アサーションの説明は,Google Test ドキュメント日本語訳の入門ガイドにあるアサーションあたりを読むと分かると思う.
ま,それだけだと寂しいので,テストの内容をいくつか,ざっくり書いとく.
TEST(TestAssert, Message)
アサーションに失敗したときにメッセージを出力するもの.flagがfalseのときに失敗して,「test failed at index 1」というメッセージが追加で表示される.成功のときはメッセージは表示されない.
TEST(TestAssert, Class)
値の比較をするアサーションは,組み込み型ならそのまま使える.もしユーザ定義型でも使いたい場合は,比較演算子を用意してやれば使える.今回の場合だと,EXPECT_EQとEXPECT_NEを使うので,==演算子と!=演算子を用意してやればよい.
TEST(TestAssert, CStr)
C言語文字列を比較したいときは,EXPECT_EQではなく,EXPECT_STREQを使う.EXPECT_EQだとポインタの比較になってしまい文字列そのものの比較にならない.
とりあえず今回はここまで.
お試しあれ.
Google Testにはいろいろなアサーションが用意されてるので,それを試してみる.
前回と同様に適当なディレクトリにassert_projというディレクトリを作って,ファイル構成は以下のようにしてやる.なお,今回はアサーションのテストだけしかしないので,作成するファイルはCMakeLists.txtとassert_unittest.cppだけだ.
assert_proj/ ├── CMakeLists.txt └── test └── assert_unittest.cpp
CMakeLists.txt
cmake_minimum_required(VERSION 2.6) project(assert_test) enable_testing() find_package(Threads) 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) add_executable(assert_test test/assert_unittest.cpp) target_link_libraries(assert_test gtest gtest_main) target_link_libraries(assert_test ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME assert_test COMMAND assert_test)
assert_unittest.cpp
#include <gtest/gtest.h> // message output test TEST(TestAssert, Message) { bool flag[2] = {true, false}; for (int i = 0; i < 2; i++) { EXPECT_TRUE(flag[i]) << "test failed at index " << i; } } // bool test TEST(TestAssert, AssertTrue) { EXPECT_TRUE(true); ASSERT_TRUE(true); } TEST(TestAssert, AssertFalse) { EXPECT_FALSE(false); ASSERT_FALSE(false); } // value test TEST(TestAssert, Value) { int expected = 2; int actual = 2; EXPECT_EQ(expected, actual); // expected == actual int val1, val2; val1 = val2 = 3; EXPECT_NE(val1 , val2+1); // val1 != val2 EXPECT_LE(val1 , val2 ); // val1 <= val2 EXPECT_GE(val1 , val2 ); // val1 >= val2 EXPECT_LT(val1 , val2+1); // val1 < val2 EXPECT_LE(val1 , val2+1); // val1 <= val2 EXPECT_GT(val1+1, val2 ); // val1 > val2 EXPECT_GE(val1+1, val2 ); // val1 >= val2 } // class test class Point { private: int x_; int y_; public: Point(int x, int y) : x_(x), y_(y) {} bool operator==(const Point& obj) const { return (x_ == obj.x_) && (y_ == obj.y_); } bool operator!=(const Point& obj) const { return !(*this == obj); } }; TEST(TestAssert, Class) { Point a(0, 0); Point b(0, 1); EXPECT_EQ(a, a); EXPECT_NE(a, b); } // c string test TEST(TestAssert, CStr) { char str1[] = "aaa"; char str2[] = "aaa"; EXPECT_NE(str1, str2); EXPECT_STREQ(str1, str2); // "aaa" vs "aaa" strcpy(str2, "Aaa"); EXPECT_STRNE(str1, str2); // "aaa" vs "Aaa" EXPECT_STRCASEEQ(str1, str2); // "aaa" vs "Aaa" strcpy(str2, "bAA"); EXPECT_STRCASENE(str1, str2); // "aaa" vs "bAA" }
じゃあビルド.前回と同様,assert_buildディレクトリを作ってビルド.
mkdir assert_build cd assert_build/ cmake ../assert_proj/ make
これで,assert_testバイナリが生成される.
実行は,
make test ARGS=--output-on-failure
でOK.ARGS=-Vにすると詳細が表示されるんだけど,テストに失敗したときだけ詳細が表示されればいいんだったら,ARGS=--output-on-failureにするといい.ま,テスト成功のときはシンプルな表示で失敗のときにいろいろ表示されるほうが使いやすいかも.
アサーションの説明は,Google Test ドキュメント日本語訳の入門ガイドにあるアサーションあたりを読むと分かると思う.
ま,それだけだと寂しいので,テストの内容をいくつか,ざっくり書いとく.
TEST(TestAssert, Message)
アサーションに失敗したときにメッセージを出力するもの.flagがfalseのときに失敗して,「test failed at index 1」というメッセージが追加で表示される.成功のときはメッセージは表示されない.
TEST(TestAssert, Class)
値の比較をするアサーションは,組み込み型ならそのまま使える.もしユーザ定義型でも使いたい場合は,比較演算子を用意してやれば使える.今回の場合だと,EXPECT_EQとEXPECT_NEを使うので,==演算子と!=演算子を用意してやればよい.
TEST(TestAssert, CStr)
C言語文字列を比較したいときは,EXPECT_EQではなく,EXPECT_STREQを使う.EXPECT_EQだとポインタの比較になってしまい文字列そのものの比較にならない.
とりあえず今回はここまで.
お試しあれ.
簡単なコードでGoogle Testを使ってみる [Google Test]
前回,Google Testを使えるようにしたので,今回は,ごく簡単なコードに対してGoogle Testを使ってみることにする.
というわけで,Google Testの使い方について検索するとよく出てきて定番っぽい,2つの変数を加算するadd関数について試してみる.
なるべく,実際使う構成に近い方がイメージしやすいと思うので(といいつつも,単にadd関数だけなんて作ること無いと思うのだけど...)適当なディレクトリに,add_projというディレクトリを作ってやって,ファイル構成は以下のようにしてやる.あと,cmakeを使ってビルドしてみる.
それぞれのファイルの内容は以下のようになる.
CMakeLists.txt(CMake用の設定ファイル)
ちなみに,GTEST_DIRは,前回Google Testをダウンロード,展開したディレクトリだ.
うちの場合だと,~/googletest/gtest-1.6.0/になる.なので,
add.cpp(add関数の定義)
add.h(add関数の宣言)
main.cpp(add関数を使用するmainの定義)
test/add_unittest.cpp(add関数のテスト定義)
では,ビルドしてみる.
ビルドは,ソースファイルがあるディレクトリとは別のディレクトリにする(これをout-of-source buildとよぶらしい).同じディレクトリでもいいのだが,CMakeはかなり中間ファイルを生成してごちゃごちゃするので,別ディレクトリにするのがおすすめだ.
今回は,add_projと同一階層にadd_buildディレクトリを生成して,以下のようにしてやる.
で,これによって,2つのバイナリが生成される.
add_mainとaddだ.
add_mainのほうは,単にadd関数によって2と3を加算した結果を表示するだけのプログラムで,addのほうがadd関数のテストになる.今回は1+2の結果が3になることをテストしている.
テストの実行は,./addでもいいし,make testでもいい.
ただし,make testだと,結果は詳細は表示されない../addと同等の表示をしたければ,make test ARGS=-Vとしてやればいい.
なお,今回はadd_mainというadd関数を使ったプログラムも作ったが,単にadd関数のテストだけをやりたければ,main.cppはいらないし,add_mainの生成もしなくていい.
Google Testは,今回の値の一致テスト以外にもいろんなことができる.それについてはまた別の機会に...
では,お試しあれ.
というわけで,Google Testの使い方について検索するとよく出てきて定番っぽい,2つの変数を加算するadd関数について試してみる.
なるべく,実際使う構成に近い方がイメージしやすいと思うので(といいつつも,単にadd関数だけなんて作ること無いと思うのだけど...)適当なディレクトリに,add_projというディレクトリを作ってやって,ファイル構成は以下のようにしてやる.あと,cmakeを使ってビルドしてみる.
add_proj/ ├── CMakeLists.txt ├── add.cpp ├── add.h ├── main.cpp └── test └── add_unittest.cpp
それぞれのファイルの内容は以下のようになる.
CMakeLists.txt(CMake用の設定ファイル)
cmake_minimum_required(VERSION 2.6) project(add) enable_testing() find_package(Threads) 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) add_executable(add_main main.cpp add.cpp) add_executable(add add.cpp test/add_unittest.cpp) target_link_libraries(add gtest gtest_main) target_link_libraries(add ${CMAKE_THREAD_LIBS_INIT}) add_test(NAME add COMMAND add)
ちなみに,GTEST_DIRは,前回Google Testをダウンロード,展開したディレクトリだ.
うちの場合だと,~/googletest/gtest-1.6.0/になる.なので,
としてやる.実際には,.bashrcにでも記載してやるといい.export GTEST_DIR=~/googletest/gtest-1.6.0
add.cpp(add関数の定義)
#include "add.h" int add(int x, int y) { return x + y; }
add.h(add関数の宣言)
#ifndef ADD_H #define ADD_H int add(int x, int y); #endif // ADD_H
main.cpp(add関数を使用するmainの定義)
#include <iostream> #include "add.h" int main() { int x = 2; int y = 3; std::cout << x << " + " << y << " = " << add(x, y) << std::endl; return 0; }
test/add_unittest.cpp(add関数のテスト定義)
#include <gtest/gtest.h> #include "add.h" TEST(TestAdd, add1) { ASSERT_EQ(3, add(1,2)); }
では,ビルドしてみる.
ビルドは,ソースファイルがあるディレクトリとは別のディレクトリにする(これをout-of-source buildとよぶらしい).同じディレクトリでもいいのだが,CMakeはかなり中間ファイルを生成してごちゃごちゃするので,別ディレクトリにするのがおすすめだ.
今回は,add_projと同一階層にadd_buildディレクトリを生成して,以下のようにしてやる.
mkdir add_build cd add_build/ cmake ../add_proj/ make
で,これによって,2つのバイナリが生成される.
add_mainとaddだ.
add_mainのほうは,単にadd関数によって2と3を加算した結果を表示するだけのプログラムで,addのほうがadd関数のテストになる.今回は1+2の結果が3になることをテストしている.
テストの実行は,./addでもいいし,make testでもいい.
ただし,make testだと,結果は詳細は表示されない../addと同等の表示をしたければ,make test ARGS=-Vとしてやればいい.
なお,今回はadd_mainというadd関数を使ったプログラムも作ったが,単にadd関数のテストだけをやりたければ,main.cppはいらないし,add_mainの生成もしなくていい.
Google Testは,今回の値の一致テスト以外にもいろんなことができる.それについてはまた別の機会に...
では,お試しあれ.
タグ:Google Test CMAKE