2011-04-25 gtest/gmock1.6.0をmake installする

gtestとgmockが1.6.0からmake installできなくなってしまいました。今回は開発者からしたら不本意でしょうが、make installを有効化する方法を説明します。
その前に
とりあえず背景から。先日リリースされたgmockの1.6.0をインストールしようとしたところ、make installが失敗するという現象にぶち当たりました。READMEやその他にもインストール方法は一切記述されていません(リンク方法は書いてありますが、記述に問題があります)。気になって調べたところ、開発者が意図的にmake installを無効化したそうです。
無効化した理由も調べてみました。gtestはコンパイルオプション次第でABIが大きく変わります。gtest/gmockはC++で書かれているので、ヘッダにもコードが多く記述されています。そのため、gtestを使う側でも、gtestインストール時に設定した一部のコンパイルオプションを再度明示的に設定してコンパイルしなければ、libgtestとうまくリンクできなくなります。当然、gmockを使う場合も、gtestとコンパイルオプションを合わせないといけません。この辺をよくわかってない人達が、適当にインストールしたgtestをリンクしようとして問題を起こしまくっており、その問い合わせが結構来るようになったので問題視してmake installを無効化したようです。
make install方法
と言うわけで、事情を知った上で自己責任でmake installする分にはまったく問題がないでしょう。というかできないと不便なんですよ。確かに開発は普通にできますが面倒です。
まず(なぜかzipでしか配布されなくなってしまった)gtest1.6.0.zipを解凍します。その中にMakefile.amというのがあるのですが、そいつを編集します。編集する前に、Makefile.amに書き込み権限がないのでchmodしておきましょう。Makefile.amを開くと、下の方に
# Disables 'make install' as installing a compiled version of Google
# Test can lead to undefined behavior due to violation of the
# One-Definition Rule.
install-exec-local:
echo "'make install' is dangerous and not supported. Instead, see README for how to integrate Google Test into your build system."
false
install-data-local:
echo "'make install' is dangerous and not supported. Instead, see README for how to integrate Google Test into your build system."
false
こんなのが書いてあります。これを全部消します。その次に、Makefile.amが置いてあるディレクトリでautoreconfを実行します。後は普通にconfigure、make、make installを実行するだけです。だいぶ大雑把な説明ですが、分からないところは適時ググってください。gmockも似たような手順でインストールできます。
念のため、このページの手順で問題が起こっても絶対にMLで問い合わせないでくださいねー。そういうのを防止するためにmake installが無効化されているので。自己責任です。
2011-01-03 google-gtest: value parameterized testのCombineを使う

テスト用のフレームワークを使ってユニットテストより粒度の荒いテスト(コンポーネントテストなど)を書こうとすると、1つのテストケースが複数のパラメタに依存してきます。例えばDecoratorパターンで、DecoratorAとDecoratorBをつなげてテストすることを考えましょう。DecoratorAが3つオプションを持ち、DecoratorBが2つオプションを持つとすると、AとBをつなげた場合は合計6通りのオプションの組み合わせに対してテストを記述する必要が出てきます。このような場合は、考え得るすべての組み合わせをテストするのが理想的ですが、手でそれらをすべて書くのは困難です。
上記の問題に対処するために、gtestの機能の1つであるvalue parameterized testのCombineというジェネレータを使って、複数のパラメタの組み合わせに対するテストを効率的に記述する方法を紹介します。
ジェネレータ
まずvalue parameterized testのジェネレータについて説明します。ジェネレータとは名前の通りテストで使用する値を生成する「なにか」です。
INSTANTIATE_TEST_CASE_Pにこのジェネレータを渡すことで、複数の値に対するテストを効率的に記述できるのがvalue parameterized testの特徴です。Value parameterized testに関する詳細はドキュメントを見てください。しょぼくても日本語で読みたいなら何回か前にこのブログで紹介したものがあるので見てみてください。
Combineの説明に入る前に、簡単に既存のジェネレータを紹介しておきます。gtestでは以下のジェネレータを提供しています。
- Range(begin, end[, step])
- 特定の区間の値を生成(stepはデフォルトで1)
- Values(v1, v2, ..., vn)
- 任意の個数の値を生成
- ValuesIn(container) or ValuesIn(begin, end)
- Bool
- true,falseを生成
- Combine
- 今回の目玉
例えば
Values("a", "b", "c")
とすると、テストケースに"a", "b", "c"がそれぞれ渡ってきます。
const set<string>& ValidNames() {
static set<string> names;
if (!names.empty()) return names;
names.insert("Enoch");
// 他の71通りの名前を追加
return names;
}
ValuesIn(ValidNames())
このようにすると、72通りの呼び方すべてをテストすることが可能になります。
Combine
Combineは複数のジェネレータから得られた値の直積をテストの入力として使うためのジェネレータです。
Combineは以下のようにして使います。
testing::Combine(g1, g2. g3. ..., gn)
gにはジェネレータを渡します。Combineから得られる値は、std::tr1::tupleとして渡されます。なのでtupleが使えない環境では機能を利用することはできません。ちなみに、tupleはgcc4.1.2でも使えたのでCent君でも安心です。
話を戻します。ジェネレータの値は、テストコードからはGetParam関数経由で取得します。Combineを使った場合、GetParamはtupleを返すので、次のようにしてそれぞれのジェネレータの値を取得することができます。
std::tr1::get<0>(GetParam()); // ジェネレータ1の値 std::tr1::get<1>(GetParam()); // ジェネレータ2の値 ...
Combineを使う例として、以下のAnd関数のテストを書いてみましょう。
bool And(bool a, bool b) { return !(!a || !b); }
引数のa,bに対して(true, true),(true, false),(false, true),(false, false)の4通りの組み合わせを入力してテストする必要があります。このテストはvalue parameterized testを使って以下のように記述できます。
typedef std::tr1::tuple<bool, bool> Parameters;
class AndTest : public testing::TestWithParam<Parameters> {
};
INSTANTIATE_TEST_CASE_P(AndTestInstance,
AndTest,
testing::Combine(testing::Bool(), testing::Bool()));
TEST_P(AndTest, test) {
bool a = std::tr1::get<0>(GetParam());
bool b = std::tr1::get<1>(GetParam());
ASSERT_EQ(a && b, And(a, b));
}
このテストケースの場合、CombineのジェネレータとしてBoolを2つ渡しています。Boolは集合{true, false}を生成します。なのでBoolとBoolの直積を作ることで、必要な組み合わせをすべて生成することができました。
Combine Combine
Combine自体もジェネレータなので、CombineをCombineすることもできます。
bool AndOr(bool a, bool b, bool c) { return !(!a || !b) || c; }
typedef std::tr1::tuple<std::tr1::tuple<bool, bool>, bool> Parameters;
class AndOrTest : public testing::TestWithParam<Parameters> {
};
INSTANTIATE_TEST_CASE_P(AndOrTestInstance,
AndOrTest,
testing::Combine(testing::Combine(testing::Bool(), testing::Bool()),
testing::Bool()));
TEST_P(AndOrTest, test) {
bool a = std::tr1::get<0>(std::tr1::get<0>(GetParam()));
bool b = std::tr1::get<1>(std::tr1::get<0>(GetParam()));
bool c = std::tr1::get<1>(GetParam());
ASSERT_EQ((a && b) || c, AndOr(a, b, c));
}
さっきのテストコードを変えてみました。Combineをネストすると、受け取る側のtupleもネストする感じになります。かなり見づらいですね。でも使いどころはありそうな感じです。楽しいです。
まとめ
今回はvalue parameterized testのCombineジェネレータの使い方を紹介しました。Combineを使うと、複数のオプションの組み合わせ全通りに対するテストを比較的簡単に記述することができます。
ソフトウェアの規模が大きくなると、手で書いたテストの信頼性も徐々に落ちていくし、メンテナンスも大変になります。gtestの便利な機能を使いこなして華麗にテストを記述しましょう。正直gflagsやglogは使って後悔している面も多いのですが(Google社内で使う分には非常に便利なんだろうと思います)、gtestはまだまだ付き合って行きたい感じです。C++でテストを書くならこれですね。
へーgtest 2年くらい前に流行ったよねー2年前から使ってるわー。
2010-11-19
■ MessagePack(-RPC) for Luaをちょろちょろ改造中

スローペースで開発してます。サーセン。今週は折角の休暇だったので仕事コードにはあえて一切触れず趣味プロに走りました。あんまりプログラミングの時間をとれませんでしたが、癒されました。
MessagePack for Lua (mplua) 0.2.0
http://github.com/nobu-k/mplua
print(msgpack.unpack(msgpack.pack('hoge')))
みたいなのができるようになりました。中国の方からの要望で作成。Packer, Unpackerは従来通り使えます。
ほかはMessagePack-RPC for Luaのためのリファクタリングがメインになりました。あと$(libdir)/libmsgpack-lua.soというのも作るようになりました(lua/.../msgpack.soは今バージョンからただのシンボリックリンクになりました)。
あとは今後の予定です。
まずクリティカルなものとして、luaL_error絡みの問題があります。Luaの例外処理はlongjmpで行われます。それ自体は問題ないのですが、スタックを辿りながらローカル変数のデストラクタを呼んだりはしてくれないので、C++と相性が良くありません。簡単な対処として、lua自体のコンパイルオプションで、longjmpを例外に置き換えることもできるようです。なんですが、まあ、対応しておくに越したことはないですね・・・。仕事でもちょっと使ってみたいので対応します。
次にPackerですが、今後Stream Serializationチックな何かに対応する予定です。
p = msgpack.Packer(ここに関数を追加) p:pack(...) p:pack(...) p:flush()
のような感じになりそうです。一定のサイズがたまったら関数が呼び出される感じになるはずです。なんかグラフのシリアライズとかをされてる方がいたので、これがあると意外と便利そうです。packの度にデータくっつけたり自分でファイルに書いたりするの面倒ですしね。
クラスのシリアライゼーションはインターフェイスが難しいので悩み中。Luaから見たら全部テーブルなんですよ。ええ。
Unpackerもfeederに対応する予定です。
u = msgpack.Unpacker(ここに関数を追加) for v in u ...(文法忘れた)
普通はfeedでデシリアライズするデータを与えますが、データが足りなくなったらコンストラクタに渡した関数が呼ばれてそこから補充されます。
MessagePack-RPC for Lua(まだ危険な状態)
https://github.com/nobu-k/mprpclua
とりあえずクライアントは普通に使えるようになり、非同期呼び出しもできるようになりました。が、まだ検証不十分です。簡単なドキュメントを書きながらテストしてみます。
クライアントは後はコネクションプーリングと複数サーバへの非同期呼び出し、futureへのコールバック設定に対応できればたぶんRuby版と同じ機能がそろうのかなーと思います。
サーバはまだ設計段階です。なやましい。
終わりに
長いこと作業できてなかったんですが、その間に何件か海外の人から反応を頂きました。ありがたいです。海外というのが良いですね。MessagePackがもっと世界に広がるように頑張りたいと思います。
あと、ここ数ヶ月仕事も私生活もドタバタしてましたが、いろいろ落ち着いてきたので今後はちょこちょこ手を加えて行けると思います。別のライブラリも作ったりするよ〜。
2010-08-10 googlegtestのType Parameterized Testsも便利すぎる件

前回のValue Parameterized Testsに続き、今回はType Parameterized Testsというのを紹介したいと思います。コードがコンパイルできなかったらタイポしてるので本家の方を見てね。
Type Parameterized Testsとは
Type Parameterized Tests(勝手にTPTと略します)とは、複数の型に対するテストコードを生成するための機能です。Value Parameterized Testsの型バージョンと思えばOKです。
TEST(VectorTest, push_test)
{
vector<int> v;
vにいろいろ
}
このとき、vectorにはいろいろな型を入れてテストをしてみたいですね。そうなると今までは
TEST(VectorTest, int_push_test)
{
vector<int> v;
vにいろいろ
}
TEST(VectorTest, float_push_test)
{
vector<float> v;
同じようなチェック
}
... 他の型についても ...
みたいな感じでやる必要がありました。これは面倒ですね。TPTを使うとこのようなテストコードを簡単に書くことができます。
TPTの使い方
コードの構成は次のようになります。
template<typename T>
class HogeTest : public testing::Test {
};
TYPED_TEST_CASE_P(HogeTest);
TYPED_TEST_P(HogeTest, test_1)
{
テストコード
}
TYPED_TEST_P(HogeTest, test_2)
{
テストコード
}
...
TYPED_TEST_P(HogeTest, test_n)
{
テストコード
}
REGISTER_TYPED_TEST_CASE_P(HogeTest,
test_1, test_2, ..., test_n);
typedef testing::Types<型1, 型2, ..., 型n> HogeTypes;
INSTANTIATE_TYPED_TEST_CASE_P(Hoge, HogeTest, HogeTypes);
上から順に説明していきます。基本はTEST_Fを使うときと同じですが、TEST_Fで使っていたfixtureがテンプレートクラスになります。次に、TYPED_TEST_CASE_Pに定義したfixtureを渡します。その後、TYPED_TEST_Pで各テストを定義します。定義したテストはREGISTER_TYPED_TEST_CASE_Pに登録します。最後にINSTANTIATE_TYPED_TEST_CASE_Pにテストしたい型のリストを登録します。型のリストはtesting::Typesを使って指定します。テストしたい型が1つだけの場合は、その型を直接INSTANTIATE_TYPED_TEST_CASE_Pに渡せばOKです。
INSTANTIATE_TYPED_TEST_CASE_Pの最初の引数はテストのインスタンス名で、これは他のインスタンス名とかぶらないように適当に付ければOKです。
HogeTypesに渡した型は、TypeParamという名前で参照できます。
TYPED_TEST_P(HogeTest, test)
{
TypeParam value;
}
こうすることで、HogeTypesで指定した型すべてに対してテストが実行されます。ちなみに、REGISTER_TYPED_TEST_CASE_Pにテストを登録し忘れると、実行時に忘れていることを教えてくれます。
さっきのvectorのテストをTPTで書き直す
includeなどは省略してます。
template<typename T>
class VectorTest : public testing::Test {
};
TYPED_TEST_CASE_P(VectorTest);
TYPED_TEST_P(VectorTest, push_test)
{
vector<TypeParam> v;
テストコード
}
REGISTER_TYPED_TEST_CASE_P(VectorTest, push_test);
typedef testing::Types<int, string, double, ...> TestTypes;
INSTANTIATE_TYPED_TEST_CASE_P(VT, VectorTest, TestTypes);
見た目はこんな感じになりますね。テストコードはどう書けば良いんでしょうか。vectorに具体的な値を入れないとテストになりません。かといって、intにstringを代入したりすることはできないので、型毎に値を用意する手段が必要になります。
型毎に処理を変えるテクニック
恐らく慣れている人なら自然とこうするだろうという感じですが、
template<typename T>
struct Traits {};
template<>
struct Traits<型> {
型 value_type;
その他の補助情報
}
// 補助関数
template<typename T>
typename Traits<T>::value_type RandomValue();
// 特殊化
template<>
Traits<型>::value_type RandomValue() { ... }
こんな感じで型毎に特殊化したテンプレート補助関数を用意しておくと、テストコードを書きやすくなります。TにはもちろんTypeParamを渡します。例えばさっきのVectorTestでは、型毎に挿入する値を返す関数を用意しなければならないのですが、このようなテンプレートの補助関数を用意しておくことですべての型に対して同じテストコードを書くことができます。
上だとTraitsみたいなのを挟んでますが、そうすると言語上は同じだけど、ロジック上は異なる型のテストなどが書きづらいです。例えば個人的な話ですが、int64_tと日時型(内部的にはint64_t相当)によって処理を変えたかったのですが、testing::Typesに両方の型を渡しても、両方とも言語上は同じ型なのでほとんど意味がありませんでした。
そこで
struct Int64Type { ... };
struct DatetimeType { ... };
typedef testing::Types<Int64Type, DatetimeType> ...;
のような感じで型に相当する構造体を作り、補助テンプレート関数もInt64Type, DatetimeTypeなどに対して特殊化するという方法を採用しました。その結果かなり良い感じでテストを書くことができたので満足です。他にも熱いテクは沢山あると思うので、是非紹介してください。
fixtureを使う
TPTのドキュメントにはfixtureの使い方があまり書いてありません。テスト間の共通処理・リソースを一箇所に書けないとあまりありがたみがないのでなんとかしたいところです。なので調べてみました。幸いソースコードに結構コメントが書いてあったので何とかなりました。
コードで書いた方がわかりやすいので、下にfixtureのメンバにアクセスするためのコードを書いておきます。
template<typename T>
class HogeFixture : public testing::Test {
protected:
int normal_member;
static int static_member;
typedef int TypeDef;
};
TYPED_TEST_CASE_P(...省略...)
{
// normal_memberを参照するにはthis->が必要
this->normal_member;
// static_member
this->static_member;
TestFixture::static_member;
// typedef
typedef typename TestFixture::TypeDef YATypeDef;
}
(static)メンバにはthis経由、typedefなどにはTestFixture経由でアクセスします。typedefを使用する場合はtypenameも必要です。
メンバにアクセスするためにthisが必要なのは、TYPED_TEST_CASE_Pで生成されるテンプレートクラスが、次のようにさらにテンプレートクラスを継承しているためです。
template<typename T>
class C {
...
};
template<typename T>
class D : public C<T> {
...
};
C++の仕様上、class Dからclass Cのメンバにアクセスするためには明示的な修飾が必要になります。
おわり
無事fixtureの使い方がわかったので、後はどうにでもなりますね。MPLを駆使すればValue parameterized testのValues風のことをTPTでもできそうで夢が広がります。メタメタな感じで良いですね。テストだからコピペして良い、メンテしづらくても良いというのはもはや時代遅れです。テストのメンテナンスコストなめんな!
つーかValue Parameterized Testsと組み合わせて使いたくなりますね。できるのかな。
2010-07-21 google-gtestのValue-Parameterized Testsが便利過ぎる件

google-gtestにいつの間にかValue-Parameterized Tests(VPTと勝手に略します)という機能が追加されていました。
以前のgtestでは、実行される度に特定の値を変更しながらテストすることが面倒でした。例えば、boolのフラグを持っており、それがtrueかfalseかで挙動が変わるようなケースです。そのようなテストケースを書きたかったら、次のようにヘルパ関数を用意するか、テストをコピペして値を変更するなどの処理を書く必要がありました。
TEST(HogeTest, hogehoge) {
hogeHelper(true); // trueでテスト
hogeHelper(false); // falseでテスト
}
前者では問題が発生した時に異なるテストが同じ行番号を出力するため、問題の把握がしづらくなります。また、TESTがたくさんあった場合、それぞれにヘルパ関数を用意する必要があり、非常に面倒です。後者は言うまでもなくメンテナンスが大変です。
そこで登場するのがVPTです。この機能を使うにはTEST_Fするときと同じような感じでクラスを用意します。TEST_Fのときにはtesting::Testを継承しますが、VPTの場合はtesting::TestWithParam<型>を継承します。型にはパラメタの型を指定します。
class HogeTest : public testing::TestWithParam<bool> {
// testing::Testを継承したときと同じような感じ
};
testing::TestWithParamはtesting::Testの派生クラスであるため、HogeTestではSetUp/TearDown/SetUpTestCase/TearDownTestCaseなど、TEST_Fで使用できる関数を同じように利用できます。
次にテストケースの書き方ですが、VPTの場合はTEST_Pを使用します。TestWithParamで指定したパラメタはGetParam()で取得することができます。
TEST_P(HogeTest, hogehoge)
{
GetParam()でboolの値が帰ってくるので、hogeHelperの中身をここに書くことができる
}
GetParamで与える値は、INSTANTIATE_TEST_CASE_Pを使って指定します。
INSTANTIATE_TEST_CASE_P(HogeTestHogeInstance,
HogeTest,
testing::Values(true, false));
このようにすると、定義したすべてのTEST_Pに対してtrue,false両方の値でテストが実行されます。
値の部分にはRange, Values, ValuesIn, Combineなどを指定できるらしいです。詳しくはドキュメントを参照してください。例えばValuesInにはSTLのコンテナが渡せるので、
set<string> Parameters();
INSTANTIATE_TEST_CASE_P(AbabababaInstance,
AbabababaTest,
testing::ValuesIn(Parameters()));
のようなこともできます。手動で値を列挙するのが大変そうな場合に便利です。今回は4つの要素を持つ集合のべき集合-空集合なものに対してそれぞれテストを実行したかったので、かなり助かりました。ソースを読んでないので使い方は良く分かりませんが、Combineを使うと集合の直積を作ってくれるらしいので、それだけでもかなりいろいろなことができるんじゃないでしょうか。
ちなみに、Valuesなどに渡す値はSetUpTestCaseよりも前に評価されてしまうので、少し複雑な値を用意したいときなどは上記のようにグローバル/ファイルスコープな関数を一個はさんであげるとよさそうです。その場合、SetUpTestCaseが呼ばれる前に何度かその関数が呼び出されるようなので、値の生成に時間がかかるような場合はstatic boolなローカル変数を用意して、一度だけ値の生成を行うような実装にしておくといいかもしれません。SetUpTestCase以降は余分な呼び出しは起こりませんでした。
