C++のUnitTestライブラリ、nfiftestを作った
Not Fancy, Intellisence Friendly Test、略してnfiftestというテストライブラリを作りました。 いまさらなんでyet anotherなUnit Testのライブラリ作るの?という話があると思うのでその辺の話も。
URL
https://github.com/karino2/nfiftest
githubのサンプル見るのが一番わかり易いと思う。
開発動機
まず、ヘッダオンリーな奴がほしかった。
ヘッダオンリーなUnit Testのライブラリはいっぱいあるけれど、 皆マクロをすごい使う。これがVSCodeで開発しててすごく都合が悪かった。 とにかくインテリセンスが効かない。 マクロで少し便利で見た目を良くするよりも、多少冗長でもインテリセンスが効いてほしい。 これが開発のモチベーション。
boostのutとかはマクロ無しを謳っているがC++17。そのほかマクロ少ない路線は皆C++17以上で、 ちょっと今の段階では自分らのプロジェクトには早すぎる。 という事でC++11で動く、マクロをあまり使わない、けどその分不便で冗長で野暮ったいUnit Testのライブラリがほしかった。
nfiftestの目指したもの
- ヘッダオンリー
- C++11で動く
- なるべくマクロを使わないもの
- VSCodeのコード補完等のanalyzeやデバッグ実行があまり不便じゃないもの
- Catch2のSECTIONみたいな事が出来るもの
- 小さくて必要に応じていじるような奴
Catch2的なSECTIONの話
C++のUnit Testは、最近だとCatch2が多いと思う。gtestとかboostのtestに比べてヘッダオンリーで使えるのと、SECTIONが使えるのが大きい。 SECTIONは従来のsetUpやtearDownの改善版。
SECTIONはネストさせる事が出来て、以下みたいなのがあると、
SECITON("sec1") {
cout << "deb1";
SECTION("sec2") {
cout << "deb2";
}
SECTIN("sec3") {
cout << "deb3";
}
}
SECTION("sec4") {
cout << "deb4";
}
とあると以下のように実行される。
- まずdeb1を出力して終わる
- 次にdeb1を出力しdeb2を出力して終わる
- 次にdeb1を出力しdeb3を出力して終わる
- 次にdeb4を出力して終わる
2と3ではdeb1の部分が共有されているが、テストとしては独立して実行される。 これが共通のセットアップをスタックに置いてへろへろ書いていくのに都合が良くて、 スタックでいろいろやりたいC++的には非常に便利に使える。
だがこのSECTIONはマクロが要るので、nfiftestでは以下のようになっている。
if(SECITON("sec1")) {SG g;
cout << "deb1";
if(SECTION("sec2")) {SG g;
cout << "deb2";
}
if(SECTIN("sec3")) {SG g;
cout << "deb3";
}
}
if(SECTION("sec4")) {SG g;
cout << "deb4";
}
ようするに、以下のようにセクションを定義している訳だ。
if(SECTION(セクション名)) { SG g;
if文とブロックの終わりを判定する為のSG (SectionGuard)を置かないといけない。かっこ悪いがステップ実行は素直に進められるしインテリセンスも混乱しない。 まったくDRYでは無いが、DRYより大切な事があるのです。
lambdaでテストを書く
自動登録のマクロが嫌だったので、テストケースはラムダにした。 こんな感じ。
vector<TestPair> test_cases = {
{"1つ目のテスト", []{
// ここにテストを書く
}},
{"2つ目のテスト", []{
// ここに2つ目のテストを書く
}},
...
};
少しコード解析に負荷が掛かってる感じはあるが、書きやすさ的には自動登録は無しでいいかな、と思った。
使った感想とか今後の事とか
30セクションくらいテストを書いてみた感想としては、非常にいい感じに使えている。 VSCodeでのインテリセンスDBの更新も十分実用的な速さで動くし、ステップ実行も快適。SGのデストラクタに入っていってしまうのは残念だが、これは不可避だろう。 登録の所がマクロになってるだけでびっくりするほどVSCodeが混乱していたんだなぁ、という事を痛感した。 nfiftestではVSCodeが普通に動いて素晴らしい。
これまでのやる気ない自作ASSERT_EQだけでなんとなく書きためていたUnit TestをSECTIONを使ってだいぶひとまとめに出来たし、テストも読みやすくなった。 C++のようにセットアップが複雑になりがちな言語では、やはりSECTIONで書いていくのが王道に思う。 使ってみるとxUnitよりも明らかに優れていて、C++の世界も進んでいるなぁ、と感心した。
REQUIRE(Unit Testでいう所のASSERT相当)だけマクロ使っていて、これは仕方ないかなぁ、と思う。__FILE__
と__LINE__
は欲しいので。
このREQUIREのマクロはインテリセンスDBの更新をだいぶ遅くするので、自分は.cppで再定義している(この辺の事はgithubのTIPSに書いてある)。同じファイルだと段違いに早い。
だからこの手のマクロが簡単に再定義出来る必要もあると思っている。既存のテストライブラリはこのマクロがすごい高機能でめっさ多段に定義されているのだよねぇ…
現状は一つの.cppファイルしか対応していない(ヘッダの中にばーんとContext g_context;
という行があある)。
includeする前になにか定義してinclude、みたいなのを許せば簡単なのだが、実用上は各.cppごとにRunTestを呼んで、その関数をmainで呼べば十分だから無理に機能を増やす必要は無いかなぁ、と思っている。
(追記:あとで対応した。README.md参照)
あまりコマンドライン引数の対応とか頑張る気はなくて、特定のテストだけ実行したいとかは適当にifdefで呼ぶ方がいいかな、と思っている。
複数ファイル対応は必要に迫られたらやるかも。ただ他の機能はあまり入れる気は起こらないかなぁ。特にREQUIRE以外のマクロを増やす気はあまり無い。 またこの中で変なマクロにハマるケースに対応する気もあまり起こらない。書く側が気をつけてシンプルに保つ方がいいでしょう。
それにしてもヘッダオンリーでC++11で動いてインテリセンスに優しい、って条件のテストライブラリが一個も無いというのは驚いた。 世の中こんなにC++のテストライブラリが溢れているのに、誰もインテリセンスで困ってないの?まぢで!?