C/C++単体テストツールCutterにハマる

周囲で「プログラムのテストを綺麗にやろう」という風潮が高まっているので,次のフレームワークを試してみました.
はじめに: Cutterリファレンスマニュアル - Cutter

CにもC++にも対応しているということなので,簡単なサンプルを作りました.

まず,ヘッダファイルとソースファイル.

#ifndef __CALC_H
#define __CALC_H
#include <iostream>
using namespace std;

class Calc
{
public:
  Calc();
  ~Calc();
  int add(int a, int b);
}
#endif


#include <Calc.h>

Calc::Calc()
{
  cout<<"Calc constructor"<<endl;
}
Calc::~Calc()
{
  cout<<"Calc destructor"<<endl;
}
int Calc::add(int a, int b)
{
  return(a+b);
}

このサンプルを,共有ライブラリ化します.

> g++ -o lib/Calc.o -c lib/Calc.cc -I./lib
> ar crv lib/libCalc.so lib/Calc.o
a - lib/Calc.o

ライブラリを呼び出すメインルーチンを作成し,ライブラリとリンクさせて動作確認します.

#include <Calc.h>

int main()
{
  Calc *calc = new Calc();
  cout<<calc->add(1,2)<<endl;
  delete calc;
  return(0);
}
> g++ -o src/main src/main.cc -Llib -lCalc -Ilib
> ./src/main
Calc constructor
3
Calc destructor

確かに,コンストラクタが呼ばれて,足し算して,デストラクタが呼ばれました.
ただし,いちいちメインルーチンを作って目視で動作確認をするのが大変なので,Cutter用のテストプログラムを作ります.

#include <cppcutter.h>
#include <Calc.h>

void test_Calc()
{
  Calc *calc = new Calc();
  cppcut_assert_equal(3, calc->add(1,2));
  delete calc;
}

Cutterの仕様に従って,このテストプログラムも共有ライブラリ化します.
そしてCutterを通して実行しようとしても...実行されません...

> g++ -o test/test_Calc.so test/test_Calc.cc -I/usr/local/include/cutter -Llib -lCalc -Ilib -L/usr/local/lib -lcppcutter -shared
> ls test/
test_Calc.cc    test_Calc.so

> cutter test/
Finished in 0.000144 seconds (total: 0.000000 seconds)

0 test(s), 0 assertion(s), 0 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
0% passed

無視されます.
ここで3日くらい悩みました...

結局分かったことは,CじゃなくてC++の場合は,namespaceでテストルーチンを囲むと出来ました.

#include <cppcutter.h>
#include <Calc.h>

namespace testCalc
{
  void test_Calc()
  {
    Calc *calc = new Calc();
    cppcut_assert_equal(3, calc->add(1,2));
    delete calc;
  }
}
> g++ -o test/test_Calc.so test/test_Calc.cc -I/usr/local/include/cutter -Llib -lCalc -Ilib -L/usr/local/lib -lcppcutter -shared
> cutter test/
Calc constructor
Calc destructor
.

Finished in 0.002072 seconds (total: 0.000615 seconds)

1 test(s), 1 assertion(s), 0 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
100% passed

1+2=3になったので,全てのテストにパスしたと出ました.

さらっと終わってしまうと,本当にテストされているのか心配になるのが人情です.
そこで,テストが失敗するであろう例を作ってみます.

#include <Calc.h>

Calc::Calc()
{
  cout<<"Calc constructor"<<endl;
}
Calc::~Calc()
{
  cout<<"Calc destructor"<<endl;
}
int Calc::add(int a, int b)
{
  return(a+b+1);
}

わざとバグを混入です.足し算の結果がちゃんと出るのではなく,足し算の結果に1だけさらに足された結果が出ます.

実行確認+Cutter確認してみますと...

> g++ -o lib/Calc.o -c lib/Calc.cc -I./lib
> ar crv lib/libCalc.so lib/Calc.o
r - lib/Calc.o
> g++ -o src/main src/main.cc -Llib -lCalc -Ilib
> ./src/main
Calc constructor
4
Calc destructor

>  g++ -o test/test_Calc.so test/test_Calc.cc -I/usr/local/include/cutter -Llib -lCalc -Ilib -L/usr/local/lib -lcppcutter -shared
> cutter test/
Calc constructor
F

1) Failure: testCacl::test_Calc
<calc->add(1,2) == 4>
expected: <3>
  actual: <4>
test/test_Calc.cc:9: void testCacl::test_Calc()(): cppcut_assert_equal(3, calc->add(1,2), )

Finished in 0.003860 seconds (total: 0.001037 seconds)

1 test(s), 0 assertion(s), 1 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
0% passed

ちゃんとエラーを検知してくれました.
「1+2=3を期待しているけど,4になってる」って言ってくれてます.