заставить Alchemy и UnitTest ++ работать вместе - PullRequest
0 голосов
/ 17 ноября 2010

Я использую Adobe Alchemy в проекте, который использует UnitTest ++ . Модульные тесты выполняются как часть процесса сборки.

Оказывается, что UnitTest ++ зависит от функции C ++, которая не реализована в Alchemy , а именно от создания экземпляров статических классов и / или вызова функций для инициализации глобальных переменных.

Самое замечательное в UnitTest ++ заключается в том, что вам не нужно добавлять свои тесты в список тестов для запуска. Это происходит автоматически с использованием некоторой макро-магии для создания классов тестовых случаев и добавления их в глобальный список тестов. Итак, это:

TEST(MyTest) {
    CHECK(doSomething());
}

становится таким:

class TestMyTest : public UnitTest::Test {
   ...
} testMyTestInstance;

UnitTest::ListAdder adderMyTest(UnitTest::Test::GetTestList(), &testMyTestInstance);

, где конструктор для ListAdder добавляет testMyTestInstance в глобальный список тестов.

Проблема в том, что из-за ошибки Алхимии конструктор ListAdder никогда не запускается, поэтому список тестов всегда пуст.

Чтобы доказать, что конструктор ListAdder никогда не вызывается, вы можете настроить его на аварийное завершение при вызове:

ListAdder::ListAdder(TestList& list, Test* test) {
    int *p= (int*)INT_MAX;   // NULL won't crash alchemy (!)
    *p= 0;                   // boom
    list.Add(test);
}

Сбой при исходной компиляции, но не при алхимии.

Менее резкий способ увидеть это просто добавить printf:

ListAdder::ListAdder(TestList& list, Test* test) {
    printf("ListAdder %s \n", test->m_details.testName);
    list.Add(test);
}

При исходной компиляции вы увидите «ListAdder ...» для каждого теста, но при компиляции в Alchemy он ничего не напечатает.

У меня вопрос: как я могу изменить UnitTest ++, чтобы тесты запускались? Обходные пути , описанные здесь , похоже, не применяются.

1 Ответ

0 голосов
/ 01 декабря 2010

Это заняло некоторое время, но я понял это. Хитрость в том, что статический инициализатор функции будет работать, например,

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

, если они не вызывают конструкторы, не используют new / malloc и не используют printf. (Мне потребовалось некоторое время, чтобы понять, что Gunslinger47 был прав насчет того, что 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) Убедитесь, что UNITTEST_USE_CUSTOM_STREAMS определен в Config.h:

// 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 не будет выполнен во время выполнения.

...