2010-11-17 12 views
0

J'utilise Adobe Alchemy dans un projet utilisant UnitTest++. Les tests unitaires sont exécutés dans le cadre du processus de construction.Alchemy et UnitTest ++ fonctionnent ensemble

Il s'avère que UnitTest ++ dépend d'une fonctionnalité de C++ isn't implemented in Alchemy, à savoir l'instanciation des classes statiques et/ou des fonctions d'appel pour initialiser les variables globales. La meilleure chose à propos de UnitTest ++ est que vous n'avez pas à vous souvenir d'ajouter vos tests à la liste des tests à exécuter. Cela arrive automatiquement en utilisant de la magie pour créer des classes de cas de test et les ajouter à une liste globale de tests. Donc ceci:

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

devient ceci:

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

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

où le constructeur de ListAdder ajoute testMyTestInstance à la liste globale des tests. Le problème est qu'à cause du bogue d'Alchemy, le constructeur ListAdder ne s'exécute jamais, donc la liste des tests est toujours vide.

Pour prouver que le constructeur est ListAdder ne s'appelle, vous pouvez l'instrument à planter quand il est appelé:

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

Cela va planter lors de la compilation en mode natif, mais ne sera pas si compilé avec l'alchimie.

Une façon moins radicale de le voir est d'ajouter juste un printf:

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

Lors de la compilation en mode natif, vous verrez « ListAdder ... » pour chaque test, mais lorsqu'il est compilé sous Alchemy il a gagné N'imprime rien.

Ma question est: comment puis-je modifier UnitTest ++ pour que les tests puissent être exécutés? Les solutions de contournement described here ne semblent pas s'appliquer.

Répondre

0

Il a fallu faire quelque chose, mais je l'ai compris. L'astuce est que initialiseur statique fonctions fonctionnent, par exemple,

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

aussi longtemps qu'ils ne l'appellent pas de constructeurs ou d'utiliser de nouveaux/malloc ou de l'utilisation printf. (Il m'a fallu un certain temps avant de réaliser que Gunslinger47 avait raison sur le fait que printfs vissât les choses.)

Le fait que les fonctions d'initialisation statiques fonctionnent est juste suffisant pour que UnitTest ++ fonctionne. Ce que nous faisons est d'utiliser une variante de la solution de contournement « Pointeurs » décrit here:

  • Au lieu d'être affecté de manière statique, chaque classe de test a une fonction allocateur
  • Un pointeur sur chaque fonction allocateur est ajouté à une liste de pointeurs de fonction
  • En principe, cette liste de pointeurs de fonction est ensuite itérée et chaque fonction appelée.

Les petits détails sont ci-dessous:

(1) En TestMacros.h, modifiez la macro TEST_EX d'utiliser une fonction d'initialisation statique plutôt qu'un constructeur:

#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) Changement TEST_FIXTURE_EX d'une manière similaire à TEST_EX. Je vais vous épargner la verbosité.

(3) Au fond de TestList.cpp, ajoutez les fonctions que les TEST_EX/macros TEST_FIXTURE_EX appel:

#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](); 
    } 
} 

et bien sûr ajouter leurs prototypes à TestList.h:

typedef void (*test_creator_func_t)(); 
test_creator_func_t addTestCreator(test_creator_func_t fp); 
void initializeAllTests(); 

(4) Enfin, dans votre coureur de test unitaire, vous devez appeler initializeAllTests:

UnitTest::initializeAllTests(); 
return UnitTest::RunAllTests(); 

Mais ce s pas tout! Il y a quelques autres petits morceaux qui doivent être fait avant que ça va marcher:

(1) Assurez-vous que UNITTEST_USE_CUSTOM_STREAMS est défini dans 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 

La raison est que si elle ne défini, MemoryOutStream.h va #include <sstream>, ce qui va briser l'initialisation statique (je suppose qu'il fait une sorte de constructeur global ou quelque chose).

(2) Dans SignalTranslator.h, assurez-vous que la macro UNITTEST_THROW_SIGNALS est un noop. Je le fais en injectant un -D__ALCHEMY__ dans mon construit et vérifier pour elle:

#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 

Si cela ne se fait pas, l'appel sigsetjmp échouera lors de l'exécution.