テスト駆動開発とかよくわからない人のための今すぐ使えるCppUnitテンプレ

時代はテスト駆動開発(TDD)らしいので有名な単体テストフレームワークCppUnitを試してみた。
これまでテスト用コードは自前で書いていたのでフレームワークのありがたさを実感するなどした。ただCppUnitは「必ず書かないといけないコード」の量が何気に多くて初見でめげそうになる気もした。なので主に自分用にCppUnitコードのテンプレをメモしておく。


まずはCppUnitを入手する。例えば

Download CppUnit - C++ port of JUnit from SourceForge.net

から入手できる。入手したらインストする。

$$ tar xzfv cppunit-1.12.1.tar.gz
$$ cd cppunit-1.12.1.tar.gz
$$ ./configure
$$ make
$$ sudo make install

でここから解説。CppUnitでテストするには簡単に言ってmain関数とテストクラスを用意すれば良い。main関数は完全なテンプレで基本的には以下のコピペを使えば良い。

#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/TestRunner.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/CompilerOutputter.h>

using namespace CPPUNIT_NS;
int main(int argc, char **argv) {
  TestResult controller;
  TestResultCollector result;
  controller.addListener(&result);

  BriefTestProgressListener progress;
  controller.addListener(&progress);

  TestRunner runner;
  runner.addTest(TestFactoryRegistry::getRegistry().makeTest());
  runner.run(controller);

  CompilerOutputter outputter(&result, stdCOut());
  outputter.write();

  return result.wasSuccessful() ? 0 : 1;
}

これをtest-main.ccとか名前を付けて保存しておく。
次にテストコード。ひとまず何もしないテンプレは以下の通り。

#include <cppunit/extensions/HelperMacros.h>

using namespace CPPUNIT_NS;
class test : public TestFixture {
  CPPUNIT_TEST_SUITE(test);
  CPPUNIT_TEST_SUITE_END();
};

CPPUNIT_TEST_SUITE_REGISTRATION(test);

これをtest-class.ccとか名前を付けて保存しておく。できたらコンパイルしてみる。

$$ g++ test-main.cc test-class.cc -lcppunit -o test
$$ ./test
OK (0)

と表示されればOK。何もしないテストができた。


うまく行ったら次はまともなテストを。例えば以下のようなクラスを考える。

class sample {
  unsigned int x_;
public:
  void set(unsigned int x) {
    if (x == 0) { throw "exception."; }
    this->x_ = x - 1;
  }
  unsigned int get() {
    return this->x_;
  }
};

これをsample.hとか名前を付けて保存しておく。このクラスはset()で数値を与えてget()で与えた数値より1小さい数値を得る。ただしset()に0を与えると例外が投げられる。という特に意味のない、どうみても説明のためだけのクラス。
このクラスに対して「set()とget()が機能しているか?」「set()に0を与えたときにきちんと例外が発生するか?」をテストしたい。
この場合、以下のようにテストクラスに手を入れる。

#include "sample.h"
#include <cppunit/extensions/HelperMacros.h>

using namespace CPPUNIT_NS;
class test : public TestFixture {
  CPPUNIT_TEST_SUITE(test);
  CPPUNIT_TEST(boundary);
  CPPUNIT_TEST(set_and_get);
  CPPUNIT_TEST_SUITE_END();

  sample *s_;
  void boundary() {
    try {
      this->s_->set(0);
      CPPUNIT_ASSERT(false);
    } catch (const char *s) {
    }
  }
  void set_and_get() {
    this->s_->set(10);
    CPPUNIT_ASSERT(this->s_->get() == 9);
  }

public:
  void setUp() {
    this->s_ = new sample();
  }
  void tearDown() {
    delete this->s_;
  }
};

CPPUNIT_TEST_SUITE_REGISTRATION(test);

CPPUNIT_TEST()で実行したいテスト関数を指定する。CPPUNIT_ASSERT()は引数がfalseになったときにテストが失敗する関数。setUp()、tearDown()はTestFixtureから継承されたメンバ関数。オーバーライドしてそれぞれテスト開始前と後に行いたい処理を記述できる。

あとはコンパイルして実行する。

$$ g++ test-main.cc test-class.cc -lcppunit -o test
$$ ./test
bit_vector_test::boundary : OK
bit_vector_test::set_and_get : OK
OK (2)

となって2つのテストが成功したことがわかる。これは便利。活用していきたい。

なおより詳しい解説はεπιστημη大先生の記事がわかりやすい。

C++アプリケーションの効率的なテスト手法(CppUnit編) − @IT

またCppUnitよりgoogletestがいいよという人は@sleepy_yoshiさんの

googletestではじめるC++の単体テスト - 睡眠不足?!

がわかりやすくて良さげ。