我在使用Adobe Alchemy的项目中使用UnitTest++。单元测试作为构建过程的一部分运行。
事实证明,UnitTest ++依赖于C ++的一个特性isn't implemented in Alchemy,即实例化静态类和/或调用函数来初始化全局变量。
UnitTest ++的优点在于您不必记住将测试添加到要运行的测试列表中。它会自动使用一些宏魔法来创建测试用例类并将它们添加到全局测试列表中。所以这个:
TEST(MyTest) {
CHECK(doSomething());
}
成为这个:
class TestMyTest : public UnitTest::Test {
...
} testMyTestInstance;
UnitTest::ListAdder adderMyTest(UnitTest::Test::GetTestList(), &testMyTestInstance);
ListAdder
的构造函数将testMyTestInstance
添加到全局测试列表中。
问题在于,由于Alchemy错误,ListAdder
构造函数从不运行,因此测试列表始终为空。
为了证明永远不会调用ListAdder
构造函数,可以在调用它时将其设置为崩溃:
ListAdder::ListAdder(TestList& list, Test* test) {
int *p= (int*)INT_MAX; // NULL won't crash alchemy (!)
*p= 0; // boom
list.Add(test);
}
当本地编译时会崩溃,但在使用Alchemy编译时不会崩溃。
一种不那么激烈的方式来看它就是添加一个printf:
ListAdder::ListAdder(TestList& list, Test* test) {
printf("ListAdder %s \n", test->m_details.testName);
list.Add(test);
}
当本地编译时,你会看到每个测试的“ListAdder ...”,但是在Alchemy下编译时它不会打印任何东西。
我的问题是:如何修改UnitTest ++以便测试运行?解决方法described here似乎不适用。
答案 0 :(得分:0)
花了一些时间,但我明白了。诀窍是静态初始化器函数将起作用,例如,
int someFunc() {
return 42;
}
int someVal= someFunc();
只要他们不调用任何构造函数或使用new / malloc或使用printf。 (我花了一段时间才意识到Gunslinger47关于printfs的事情是对的。)
静态初始化函数的工作原理足以让我们使UnitTest ++正常工作。我们所做的是使用here所描述的“指针”解决方法的变体:
细节如下:
(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_EX类似的方式更改TEST_FIXTURE_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__
并检查它来执行此操作:
#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调用将在运行时失败。