少し手間がかかりましたが、私はそれを理解しました。秘訣は、静的初期化関数が機能することです。たとえば、
int someFunc() {
return 42;
}
int someVal= someFunc();
コンストラクターを呼び出したり、new/malloc を使用したり、printf を使用したりしない限り。(ガンスリンガー47がprintfsが物事を台無しにしていることについて正しかったことに気付くのに少し時間がかかりました。)
静的初期化関数が機能するという事実は、UnitTest++ を機能させるのに十分です。ここで説明する「ポインター」回避策のバリエーションを使用します。
- 静的に割り当てられる代わりに、各テスト クラスにはアロケーター関数があります。
- 各アロケーター関数へのポインターが関数ポインターのリストに追加されます
- メインでは、この関数ポインタのリストが繰り返され、各関数が呼び出されます。
ざらざらした詳細は以下のとおりです。
(1) TestMacros.h で、コンストラクターではなく静的初期化関数を使用するように TEST_EX マクロを変更します。
#define TEST_EX(Name, List) \
class Test##Name : public UnitTest::Test \
{ \
public: \
Test##Name() : Test(#Name, UnitTestSuite::GetSuiteName(), __FILE__, __LINE__) {} \
private: \
virtual void RunImpl() const; \
}; \
\
void create_test##Name##Instance() { \
Test##Name *test##Name##Instance= new Test##Name(); \
UnitTest::ListAdder adder##Name (List(), test##Name##Instance); \
} \
\
UnitTest::test_creator_func_t fp_create_test##Name##Instance= \
UnitTest::addTestCreator(create_test##Name##Instance); \
\
void Test##Name::RunImpl() const
#define TEST(Name) TEST_EX(Name, UnitTest::Test::GetTestList)
(2) TEST_FIXTURE_EX を TEST_EX と同様に変更します。冗長性は割愛します。
(3) TestList.cpp の最後に、TEST_EX/TEST_FIXTURE_EX マクロが呼び出す関数を追加します。
#if !defined(MAX_TEST_CREATORS)
#define MAX_TEST_CREATORS 1024
#endif
const size_t max_test_creators= MAX_TEST_CREATORS;
size_t num_test_creators= 0;
// This list unfortunately must be static-- if we were to
// dynamically allocate it, then alchemy would break.
// If it winds up not being big enough, then just inject
// a bigger definition for MAX_TEST_CREATORS
test_creator_func_t test_creator_list[max_test_creators]= {NULL};
test_creator_func_t addTestCreator(test_creator_func_t fp) {
int idx= num_test_creators;
num_test_creators++;
if (num_test_creators > max_test_creators) {
throw "test creator overflow";
}
test_creator_list[idx]= fp;
return fp;
}
void initializeAllTests() {
for (size_t idx= 0; idx < num_test_creators; idx++) {
test_creator_list[idx]();
}
}
もちろん、プロトタイプを TestList.h に追加します。
typedef void (*test_creator_func_t)();
test_creator_func_t addTestCreator(test_creator_func_t fp);
void initializeAllTests();
(4) 最後に、単体テスト ランナーで、initializeAllTests を呼び出す必要があります。
UnitTest::initializeAllTests();
return UnitTest::RunAllTests();
しかし、それだけではありません!それが機能する前に行う必要がある他のいくつかのヒントがあります。
(1) Config.h で UNITTEST_USE_CUSTOM_STREAMS が定義されていることを確認します。
// by default, MemoryOutStream is implemented in terms of std::ostringstream, which can be expensive.
// uncomment this line to use the custom MemoryOutStream (no deps on std::ostringstream).
#define UNITTEST_USE_CUSTOM_STREAMS
この理由は、定義されていない場合、MemoryOutStream.h が#include <sstream>
になり、静的初期化が中断されるためです (ある種のグローバル コンストラクターまたは何かを実行すると思われます)。
(2) SignalTranslator.h で、UNITTEST_THROW_SIGNALS マクロが noop であることを確認します。これを行うには-D__ALCHEMY__
、ビルドに a を挿入してチェックします。
#if defined(__ALCHEMY__)
#define UNITTEST_THROW_SIGNALS
#else
#define UNITTEST_THROW_SIGNALS \
UnitTest::SignalTranslator sig; \
if (UNITTEST_EXTENSION sigsetjmp(*UnitTest::SignalTranslator::s_jumpTarget, 1) != 0) \
throw ("Unhandled system exception");
#endif
これを行わないと、実行時に sigsetjmp 呼び出しが失敗します。