読者です 読者をやめる 読者になる 読者になる

Natural Software

KinectなどのDepthセンサーを中心に活動しています

CppUnit-x ではじめる、簡単ユニットテスト−その5 makefile の自動生成

プログラム C++ テスト

eXtreme Programmingテスト技法―xUnitではじめる実践XPプログラミング (OOP foundations)

eXtreme Programmingテスト技法―xUnitではじめる実践XPプログラミング (OOP foundations)

この本の写経シリーズ。


第5回は本に載ってない気がする(多分オリジナル)。


前回まででテストスイートとテストランナーを自動生成し、テストコードともどもビルド、実行することができた。


なんだけど、一つだけ不満がある。


それは「テスト対象」が増えるごとに makefile を書き換えないといけないこと。
例で言うと赤の "OBJS" の部分。

OBJS = Quiz.obj
LIBS = cppunit.lib
INC = D:\_work\etc\cppunit-x-20020331

test : $(OBJS)
ruby TestRunnerFactory.rb
cl -GX -I$(INC) Tester.cpp $(LIBS) $(OBJS)
Tester.exe


ここまで自動化しといて makefile だけ手動というのはどうかと思うので、これも自動生成できるんじゃないかと試したら。



できたよ^^;


TestRunnerFactory.rb の要領で作ってみた。

ゴール

前回の Quiz クラスのテストのほか、ここにある Counter クラスのテストも同時に行う。このとき makefile は自動で生成されるものとする。

自動生成された makefile

makefile を自動生成する Ruby のコードを書いた結果、こんな makefile が出来上がった

OBJS = Counter.obj Quiz.obj
LIBS = cppunit.lib
INC  = D:\_work\etc\cppunit-x-20020331

test : $(OBJS)
	ruby TestRunnerFactory.rb
	cl -GX -I$(INC) Tester.cpp $(LIBS) $(OBJS)
	Tester.exe

makefile を自動生成する Ruby コード

TestRunnerFactory.rb を見よう見まねで作ってみた。
その中で参考にしたサイトは以下の通り

TestMakefileFactory.rb

getCppList で cpp ファイルを検索し、ヒットしたファイル名の ".cpp" 部分を ".obj" に置換
これを makeMakefile で makefile の形にして出力している

class TestMakefileFactory

  def initialize
    @files = []
  end

  def getCppList
    dir = Dir.open( Dir.pwd )
    reg = /(.*\.cpp)/
    dir.each { |file|
      if reg =~ file then
        # .cpp を .obj に変換して格納
        @files.push file.sub( ".cpp", ".obj" )
      end
    }

    dir.close
  end

  def makeMakefile
    makefile = File.new( "makefile", "w" )

    # OBJ ファイル一覧
    makefile.print "OBJS ="
    @files.each { |file|
      makefile.print " " + file
    }
    makefile.puts ""

    makefile.puts "LIBS = cppunit.lib"
    makefile.puts "INC  = D:\\_work\\etc\\cppunit-x-20020331"
    makefile.puts ""
    makefile.puts "test : $(OBJS)"
    makefile.puts "\truby TestRunnerFactory.rb"
    makefile.puts "\tcl -GX -I$(INC) Tester.cpp $(LIBS) $(OBJS)"
    makefile.puts "\tTester.exe"

    makefile.close
  end
end
  
factory = TestMakefileFactory.new
factory.getCppList
factory.makeMakefile

実行

下記のようなバッチファイルを作成し実行する


TestMakefileFactory.rb で makefile を生成するので、nmake の前で実行する。
このとき cpp の検索で 自動生成される Tester.cpp が邪魔になるので、exe や obj もろとも "del Tester.*" で削除した。

del Tester.*
ruby TestMakefileFactory.rb
nmake
pause

結果

この結果で以下のようなファイルが出力され実行される。

makefile
OBJS = Counter.obj Quiz.obj
LIBS = cppunit.lib
INC  = D:\_work\etc\cppunit-x-20020331

test : $(OBJS)
	ruby TestRunnerFactory.rb
	cl -GX -I$(INC) Tester.cpp $(LIBS) $(OBJS)
	Tester.exe
Tester.cpp
#define CPPUNIT_COMPATIBLE
#include <cppunit/TestCase.h>
#include <cppunit/TestSuite.h>
#include <cppunit/TestRunner.h>
#include <cppunit/TestCaller.h>
USING_NAMESPACE_CPPUNIT
using namespace std;

#include "CounterTest.h"
class tmpCounterTest : public CounterTest
{
public:
	tmpCounterTest( const char* name ) : CounterTest( name ) {}
	static Test* suite()
	{
		TestSuite* suite = new TestSuite( "CounterTest");
		suite->addTest( new TEST_CALLER( CounterTest, test_init));
		suite->addTest( new TEST_CALLER( CounterTest, test_incr));
		suite->addTest( new TEST_CALLER( CounterTest, test_decr));
		suite->addTest( new TEST_CALLER( CounterTest, test_clear));
		return suite;
	}
};

#include "QuizTest.h"
class tmpQuizTest : public QuizTest
{
public:
	tmpQuizTest( const char* name ) : QuizTest( name ) {}
	static Test* suite()
	{
		TestSuite* suite = new TestSuite( "QuizTest");
		suite->addTest( new TEST_CALLER( QuizTest, testQuestion));
		suite->addTest( new TEST_CALLER( QuizTest, testAnswer));
		return suite;
	}
};

int main( int argc, char* argv[] )
{
	TestRunner runner;
	runner.addTest( "CounterTest", tmpCounterTest::suite() );
	runner.addTest( "QuizTest", tmpQuizTest::suite() );
	return runner.run( argc, argv );
}
実行結果

Counter が 4つのテスト、Quiz が2つのテストで計6つのテストがパスした。

まとめ

これで以下の3ファイルを対象フォルダにぶち込んでバッチファイルを実行すれば、勝手に makefile を生成して、ビルドして、実行してくれる。
我ながらなかなかいい出来だと思う(笑)

  • テスト対象.h
  • テスト対象.cpp
  • テストコード.h

補足

ここから拝借した Counter のテストコードは CppUnit-x 用を本家 CppUnit で動作するようにしたものなので、それをさらに CppUnit-x 用に戻す。


ついでにテストコードを cpp に書いてると CppUnit-x ヘッダをインクルードしなきゃならんのでテストコードはヘッダにまとめた。
変更した CounterTest.h を以下に示す。これにより CounterTest.cpp は使用しなくなった。

#ifndef __COUNTERTEST_H__
#define __COUNTERTEST_H__

#include "Counter.h"

class CounterTest : public TestCase
{
public:

    // いつもきまった書き方のコンストラクタ
    CounterTest( const char* name ) : TestCase( name ) {}

    void setUp() {
      // 前準備
    }

    void tearDown() {
      // 後始末
    }

    ////////////// TEST CASES //////////////////

    void test_init() {
      Counter c;
      assertEquals( 0, c.value() );
    }

    void test_incr() {
      Counter c;
      assertEquals( 0, c.value() );
      c.incr();
      assertEquals( 1, c.value() );
      c.incr();
      assertEquals( 2, c.value() );
    }

    void test_decr() {
      Counter c;
      assertEquals( 0, c.value() );
      c.decr();
      assertEquals( -1, c.value() );
      c.decr();
      assertEquals( -2, c.value() );
    }

    void test_clear() {
      Counter c;
      assertEquals( 0, c.value() );
      c.incr();
      assertEquals( 1, c.value() );
      c.clear();
      assertEquals( 0, c.value() );
    }

};

#endif