はじめに
単体テストを効率的に行うため、テスト対象となる言語に応じてさまざまなフレームワーク「xUnit」がリリースされています。例えばJavaならJUnit、.NETならNUnit、CならCUnit、C++ならCppUnitあたりがそれぞれの代表格といったところでしょうか。
マイクロソフトのオンラインマガジン「MSDNマガジン2008年2月号」で、Windows環境に特化したC/C++対応の単体テストフレームワーク「WinUnit」が紹介されています。実行環境がWindowsに限定されてはいるものの、それを補って余りある使い勝手の良さを実現しています。
CUnit/CppUnitの問題点
CUnitによる単体テストの例を示します。
#include <CUnit.h> #include "Car.h" #include <stdio.h> Car c; int car_setup(void) { c = car_new(50,2); return c != NULL ? 0 : 1; } int car_teardown(void) { car_delete(c); return 0; } void test_car_init(void) { CU_ASSERT_EQUAL(car_distance(c), 0); CU_ASSERT_EQUAL(car_remain(c), 0); } void test_car_refuel(void) { car_refuel(c, 20); CU_ASSERT_EQUAL(car_remain(c), 20); car_refuel(c, 20); CU_ASSERT_EQUAL(car_remain(c), 40); car_refuel(c, 20); CU_ASSERT_EQUAL(car_remain(c), 50); } /* * test-suitの構築と実行 */ static CU_TestInfo test_car[] = { { "初期化", test_car_init }, { "給油", test_car_refuel }, CU_TEST_INFO_NULL, }; static CU_SuiteInfo suites[] = { { "Carのテスト", car_setup, car_teardown, test_car }, CU_SUITE_INFO_NULL, }; #include <Basic.h> int main() { CU_initialize_registry(); CU_register_suites(suites); CU_basic_set_mode(CU_BRM_VERBOSE); CU_basic_run_tests(); CU_cleanup_registry(); return 0; }
このコード、中ほどの/* --- */
以降の部分はテストそのものではありません。いくつものテスト関数をまとめたスイートを組み上げ、スイート内のテスト関数を順に呼び出してはその結果を収集する(ライブラリ提供の)実行ルーチンを起動しています。テストの追加/削除あるいはテスト関数名の変更に伴ってスイート組み上げ部の修正が必要となります。そのためスイートの修正漏れがあると、コンパイル/リンクエラーとなったり、追加したテストが実行されません。
かたやこちらは同じテストをNUnit(C++/CLI)で書いたもの。スイートの組み上げやテスト実行部がありません。
#include "Car.h" using namespace System; using namespace NUnit::Framework; using namespace NUnit::Framework::SyntaxHelpers; namespace TestByNUnit { [TestFixture] public ref class CarTest { Car c; public: [SetUp] void SetUp() { c = car_new(50,2); } [TearDown] void TearDown() { car_delete(c); } [Test] void init() { Assert::That(car_distance(c), Is::EqualTo(0)); Assert::That(car_remain(c), Is::EqualTo(0)); } [Test] void refuel() { car_refuel(c, 20); Assert::That(car_remain(c), Is::EqualTo(20)); car_refuel(c, 20); Assert::That(car_remain(c), Is::EqualTo(40)); car_refuel(c, 20); Assert::That(car_remain(c), Is::EqualTo(50)); } }; }
Javaや.NETには生成されたバイトコードやアセンブリからクラス名、メソッド名、引数などを抽出し、インスタンスの生成とメソッドの呼び出しが可能です。JUnit、NUnitは「リフレクション」と呼ばれるこの機能を使ってテストを抽出/実行できるのですが、ネイティブコードを生成するC/C++はリフレクションを持ち合わせていないので、テストスイートの組み立ておよびテストの実行をソースコード内に置いておかなければなりません。
本稿で紹介するWinUnitはテストコードからDLLを生成することで(簡易版の)リフレクションを実現しています。DLLは他のEXEやDLLから呼び出せるよう、関数名がその内部に埋め込まれています。WinUnitのテスト実行アプリケーション「WinUnit.exe」は、実行時に指定されたDLL内からテスト関数を抽出し呼び出してくれるので、テストコード中のスイート組み上げやテスト実行部(すなわちmain
)を必要としないのです。
Windowsという限られた環境に限定されるものの、CUnit、CppUnitに比べ使い心地/お手軽さが大きく向上しています。