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

Natural Software

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

CppUnit-x ではじめる、簡単ユニットテスト−その3 テストを自動化してみる

プログラム C++ テスト

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

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

写経シリーズ 第3回。
さて、毎度毎度 suite 関数やら main 関数やら書くのはめんどくさいし、テストが増えたら管理が大変になって止めてしまうのがオチなので自動化してみる。

自動生成されたテストクラス

自動生成の対象はテストクラスに suite 部分を付加したクラスとTestRunner を付加した main 関数の2つ。
以下のようなファイルが生成される。

Test.cpp
#include <cppunit/TestCase.h>
#include <cppunit/TestSuite.h>
#include <cppunit/TestRunner.h>
#include <cppunit/TestCaller.h>
USING_NAMESPACE_CPPUNIT
using namespace std;

#include "SampleTest.h"
class tmpSampleTest : public SampleTest
{
public:
	tmpSampleTest( const char* name ) : SampleTest( name ) {}
	static Test* suite()
	{
		TestSuite* suite = new TestSuite( "SampleTest");
		suite->addTest( new TEST_CALLER( SampleTest, testCalc));
		suite->addTest( new TEST_CALLER( SampleTest, testMem));
		suite->addTest( new TEST_CALLER( SampleTest, testSolve));
		return suite;
	}
};

int main( int argc, char* argv[] )
{
	TestRunner runner;
	runner.addTest( "SampleTest", tmpSampleTest::suite() );
	return runner.run( argc, argv );
}

指針

テストクラスはこんな構成にする。


CppUnit-x の TestCase クラスかは派生した SampleTest クラスを自分で作成する。
作成する内容は void testXXXX() というテスト関数とコンストラクタのみ。


そこから tmpSampleTest を派生し、suite 関数を付加する。
tempSampleTest と runner を含む main 関数は自動生成する。

自動化作成手順

テストコード作成

テストコードは void testXXXX() というテストケースとコンストラクタのみのクラスとする。

SampleTest.h
class SampleTest : public TestCase
{
public:

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

    // 実行したいテストメソッド
    void testCalc(){}
    void testMem(){}
    void testSolve(){}
};
TestSuite 自動生成コード

後述する TestRunnerFactory.rb からテストコードを渡され、テストケースを検索しテストスイートを作成する。

TestSuiteFactory.rb
class TestSuiteFactory
  
    def initialize( classname )
      @classname = classname
      @testMethods = []
    end
    
    def makeAddSuite
      "\trunner.addTest( \"" + @classname + "\", tmp" + @classname + "::suite() );\n";
    end
    
    def makeSuiteMethod( file )
      file.puts "#include \"" + @classname + ".h\""
      file.puts "class tmp" + @classname + " : public " + @classname
      file.puts "{"
      file.puts "public:"
      file.puts "\ttmp" + @classname + "( const char* name ) : " + @classname + "( name ) {}\n"
      file.puts "\tstatic Test* suite()"
      file.puts "\t{"
      file.puts "\t\tTestSuite* suite = new TestSuite( \"" + @classname + "\");\n"
      @testMethods.each{ |method|
        file.puts "\t\tsuite->addTest( new TEST_CALLER( " + @classname + ", " + method + "));"
      }
      file.puts "\t\treturn suite;"
      file.puts "\t}"
      file.puts "};"
      file.puts ""
    end
    
    def parse
      lex = /.void (test.*)\(\).*/
      File.readlines( @classname + ".h" ).each do |f|
        if lex =~ f then
          @testMethods.push( $1 )
        end
      end
    end
  end
TestRunner 自動生成コード

実行フォルダを検索し、テスト対象ファイルを TestRunnerFactory.rb に渡すことでテストスイートを作成する。
すべてのテストスイートが作成されたら、main 関数にてテストランナーを作成する。

TestRunnerFactory.rb
require "TestSuiteFactory.rb"

class TestRunnerFactory
  
  def initialize
    @tests = []
  end
  
  def getTestList
    dir = Dir.open( Dir.pwd )
    reg = /(.*Test)\.h/
    dir.each { |file|
      if reg =~ file then
        @tests.push TestSuiteFactory.new( $1 )
      end
    }
    
    dir.close
  end
  
  def makeRunner
    tester = File.new( "Tester.cpp", "w" )
    tester.puts "#include <cppunit/TestCase.h>"
    tester.puts "#include <cppunit/TestSuite.h>"
    tester.puts "#include <cppunit/TestRunner.h>"
    tester.puts "#include <cppunit/TestCaller.h>"
    tester.puts "USING_NAMESPACE_CPPUNIT"
    tester.puts "using namespace std;"
    tester.puts ""
    
    @tests.each { |aClass|
      aClass.parse
      aClass.makeSuiteMethod( tester )
    }
    
    tester.puts "int main( int argc, char* argv[] )"
    tester.puts "{"
    tester.puts "\tTestRunner runner;"
    
    @tests.each { |aClass|
      tester.puts aClass.makeAddSuite
    }
    
    tester.puts "\treturn runner.run( argc, argv );"
    tester.puts "}"
    tester.close
  end
end
  
factory = TestRunnerFactory.new
factory.getTestList
factory.makeRunner
自動実行 bat ファイル

今回は Windows + VC の環境なので、TestRunnerFactory.rb で TestSuite と TestRunner を作成後、cl にてコンパイルし、そのまま生成された実行ファイルを実行する。

ruby TestRunnerFactory.rb
cl -GX -ID:\_work\etc\cppunit-x-20020331 Tester.cpp cppunit.lib
Tester.exe
pause
まとめ

ここまでで、テストケースさえ書くことが出来れば bat ファイルを実行するだけでテスト実行まで自動でやってくれる。
今考えられる積み残しとしてはこんなとこかな。

  • コンパイルの部分を make ファイルかなんかに 出来た:)
  • テストでエラーが発生した場合の処理を考える
  • ソースを Subversion から持ってくる


うーん、一から順番にやっていくとカンタンだ。
本当にこんないいものをいままで使っていなかったことを後悔するよ(;´Д`)