【问题标题】:making Alchemy and UnitTest++ work together使 Alchemy 和 UnitTest++ 协同工作
【发布时间】:2011-05-11 14:43:39
【问题描述】:

我在使用UnitTest++ 的项目中使用Adobe Alchemy。单元测试作为构建过程的一部分运行。

事实证明,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 似乎不适用。

【问题讨论】:

    标签: flash alchemy unittest++


    【解决方案1】:

    花了一些时间,但我想通了。诀窍是静态初始化器 functions 将起作用,例如,

    int someFunc() {
        return 42;
    }
    int someVal= someFunc();
    

    只要它们不调用任何构造函数或使用 new/malloc 或使用 printf。 (我花了一段时间才意识到 Gunslinger47 关于 printfs 把事情搞砸的说法是正确的。)

    静态初始化函数工作的事实足以让我们让 UnitTest++ 工作。我们所做的是使用here 描述的“指针”解决方法的变体:

    • 每个测试类都有一个分配器函数,而不是静态分配
    • 将指向每个分配器函数的指针添加到函数指针列表中
    • 在 main 中,这个函数指针列表随后被迭代并调用每个函数。

    具体细节如下:

    (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 &lt;sstream&gt;,这将破坏静态初始化(我怀疑它做了某种全局构造函数之类的)。

    (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 调用将在运行时失败。

    【讨论】:

      猜你喜欢
      • 2013-09-23
      • 1970-01-01
      • 2016-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-14
      相关资源
      最近更新 更多