0

UnitTest ++を使用するプロジェクトでAdobeAlchemyを使用しています。単体テストは、ビルドプロセスの一部として実行されます。

UnitTest ++は、Alchemyに実装されていないC ++の機能、つまり静的クラスのインスタンス化や関数の呼び出しによるグローバル変数の初期化に依存していることがわかりました。

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 ++を変更するにはどうすればよいですか?ここで説明されている回避策は当てはまらないようです。

4

1 に答える 1

0

少し手間がかかりましたが、私はそれを理解しました。秘訣は、静的初期化関数が機能することです。たとえば、

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 呼び出しが失敗します。

于 2010-11-30T22:08:10.530 に答える