ヘッダ1つでgoogle testっぽくテストが書けるpicotest書いた

以下に修正BSDライセンスで公開しておきます:

nyanp/picotest · GitHub

一応VC10/VC11(Win7),gcc4.6.2(Ubuntu)で動作確認。

これは何?

C++向けのユニットテスティングフレームワークです。ちょっとしたコードに対してテストを書きたいなー、でもわざわざテストのためにGoogle Testをリンクするのも、Boost.Testを引っ張り出すのも大げさ*1…みたいな時に使えるかもしれません。ヘッダだけで使えるテスティングフレームワークは他にもあります*2が、picotestは以下のような特徴があります。

  • Google Testと同じ構文でテストが書ける
    • 学習コストが0
    • プロジェクトが大きくなってきたら、簡単にGoogle Testに切り替えることが可能
    • Google Testのようにテストケースの自動登録、フィクスチャ、operator << を持たない型のtest/print、ULPを使った浮動少数比較ができる
    • 複数翻訳単位からの利用ももちろん可能
  • コンパクト
    • ヘッダ1枚で700行程度
    • 大したこともやっていないので、気に食わない所の修正も簡単にできる、ような気がする
  • 出力が簡潔
    • ちょっとしたテストであれば、失敗した所だけ表示してほしい

サンプル

#include "picotest.h"

int Factorial(int n) {
    return n == 0 ? 1 : n * Factorial(n - 1);
}

// 0 の階乗をテスト
TEST(FactorialTest, HandlesZeroInput) {
    EXPECT_EQ(1, Factorial(0));
}

// 正の数の階乗をテスト
TEST(FactorialTest, HandlesPositiveInput) {
    EXPECT_EQ(1, Factorial(1));
    EXPECT_EQ(2, Factorial(2));
    EXPECT_EQ(6, Factorial(3));
    EXPECT_EQ(40320, Factorial(8));
}

int main(int argc, char **argv) {
    RUN_ALL_TESTS();
}

サンプルコードはgoogle testのドキュメントからほぼそのまま持ってきました。違いは(1)gtest.hの代わりにpicotest.hをインクルードしている点、(2)main関数でのtesting::InitGoogleTestの呼び出しが無い点、の2つのみです。逆にこの2点を修正すればpicotestからgoogle testに乗り換えることが可能です。

フィクスチャを使ったテストも、google testっぽく書けます。

template <typename E> // E is the element type.
class Queue {
 public:
  Queue();
  void Enqueue(const E& element);
  E* Dequeue(); // Returns NULL if the queue is empty.
  size_t size() const;
  ...
};

class QueueTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }

  // virtual void TearDown() {}

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

TEST_F(QueueTest, IsEmptyInitially) {
  EXPECT_EQ(0, q0_.size());
}

TEST_F(QueueTest, DequeueWorks) {
  int* n = q0_.Dequeue();
  EXPECT_EQ(NULL, n);

  n = q1_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(1, *n);
  EXPECT_EQ(0, q1_.size());
  delete n;

  n = q2_.Dequeue();
  ASSERT_TRUE(n != NULL);
  EXPECT_EQ(2, *n);
  EXPECT_EQ(1, q2_.size());
  delete n;
}

対応しているアサーション

マクロ
ASSERT_TRUE(cond) cond==true
ASSERT_FALSE(cond) cond==false
ASSERT_EQ(expected, actual) expected == actual
ASSERT_NE(expected, actual) expected != actual
ASSERT_LT(expected, actual) expected < actual
ASSERT_GT(expected, actual) expected > actual
ASSERT_LE(expected, actual) expected <= actual
ASSERT_GE(expected, actual) expected >= actual
ASSERT_STREQ(expected_str, actual_str) expected_str == actual_str(C文字列の比較)
ASSERT_STRNE(expected_str, actual_str) expected_str != actual_str(C文字列の比較)
ASSERT_STRCASEEQ(expected_str, actual_str) expected_str == actual_str(C文字列/大小文字を同一視)
ASSERT_STRCASENE(expected_str, actual_str) expected_str != actual_str(C文字列/大小文字を同一視)
ASSERT_FLOAT_EQ(expected, actual) expected == actual
ASSERT_DOUBLE_EQ(expected, actual) expected != actual
ASSERT_FLOAT_NE(expected, actual) expected == actual
ASSERT_DOUBLE_NE(expected, actual) expected != actual

すべてのマクロに対して、失敗するとテストケースを即座に抜けるASSERT_XXと、テストを続行するEXPECT_XXが用意されています。

できないこと

あくまで小規模コードをさくっとテストしたい場合を想定しているので、以下のような機能には対応していません。

  • モックを使ったテスト
  • google testのような出力フォーマットのカスタマイズ
  • 例外のキャッチ
  • XMLレポーティング
  • テストケースのテンプレート化
  • マルチスレッドでの利用

あと、google test「っぽく」書けるとは言え、同じ挙動を保障するようなものでは無いことは一応書いておきます。

*1:google testはfused-src/以下に巨大な.ccと.hがあり、こいつらをプロジェクトに放り込めばリンクは不要になりますが、やっぱり個人的には面倒

*2:Boost.Test(boost/test/included以下)とか、CATCH(https://github.com/philsquared/Catch)とか