C++11テンプレートメタプログラミング-テストTeardown

5446 ワード

fixture内部でsetupを定義できる以上、同じfixtureのすべてのテスト例に共通する後始末作業を処理するためにteardownを対称的に定義することも当然望ましい.
たとえば、次のようなテスト例の説明を望んでいます.
FIXTURE(TestTearDown)
{
    TEARDOWN()
    {
        ASSERT_EQ(__int(0), expected);
    }

    TEST("test 1")
    {
        using expected = __int(0);
    };

    TEST("test 2")
    {
        using expected = __sub(__int(1), __int(1));
    };
};

上記TEARDOWNの定義は、一般にfixtureのすべての試験例の前にあるが、実行は各試験例の後にある.どのようにTEARDOWNを実現しますか?前にsetupを実装した経験を借りて、TEARDOWNでクラスを定義する必要があるようです.各テスト・インスタンスはそれを継承し、それ自体を渡すことができます.
そこで私たちはCRTP(Curiously Recurring Template Pattern)モードを考えました.その使い方は以下の通りです.
template
struct TlpTestTeardown
{
    //  TestCase 
};

struct TlpTestCase : TlpTestTearDown
{
    // ...
};

CRTPでは、まず親テンプレートを定義し、子がこのテンプレートを継承するときに自身をテンプレートパラメータとして親テンプレートに渡すことができ、親テンプレートでは子タイプを使用することができます.
問題はteardown機能を完了した親テンプレートに対して、各テスト例としてのサブクラスタイプをどのように使用するかです.最も直接的な考え方は、TlpTestTeardownをTestから継承することであり、各テスト例の内容がTlpTestTeardownに直接表示される.
template
struct TlpTestTeardown : TestCase
{
    // ...
};

struct TlpTestCase : TlpTestTearDown
{
    // ...
};

残念なことに、上記のコードはコンパイルできません.理由はTlpTestTeardownTlpTestCaseを親とするため、コンパイラがこの文にコンパイルする際にはTlpTestCaseの完全な定義が必要であり、このときTlpTestCaseは定義を開始する.
これは、TlpTestTearDownがTlpTestCaseを継承した場合、TlpTestTearDown時点でTlpTestCaseの完全な定義を見なければならないことを警告しています.では、TlpTestTearDownをTlpTestCase定義の後ろに移動できますか.
struct TlpTestCase
{
    // ...
};

struct : TlpTestTearDown{};

これはコンパイルできますが、これからはすべてのテスト例にstruct : TlpTestTearDown{}を付けなければなりません.これは、TESTを隠すために専用の対応する終了マクロをマクロstruct : TlpTestTearDown{}に実装する必要があることを意味する.これにより、後でテスト例を定義して、一般的なカッコを使用するのではなく、次のようにします.
TEST_BEGIN("test description")
    //  
TEST_END() //  “}; struct : TlpTestTearDown{};”

これは嫌だ!定義テストの例はこのように頻繁であるため、私たちはやはり前の簡潔な定義方式を維持したいと思っています.struct TlpTestCase : TlpTestTearDownの書き方に戻ると、このような組合せ関係の定義をカッコの前に置いて処理する必要があるようです.では、他に方法はありませんか.TlpTestTearDownのメンバーメソッドでTlpTestCaseタイプを使用すると、TlpTestCaseの完全定義に対する依存時間を遅らせることができることを思い出す.そこでTlpTestTearDownの実装を以下のように修正した.
template  struct TlpTestTeardown
{
    TlpTestTeardown()
    {
        struct Teardown : TestCase
        {
            //  teardown , TestCase !
        };
    }
};

上記のコードでは、TlpTestTeardownテンプレートのコンストラクション関数で、テンプレートのパラメータTestCaseを継承する一時クラスTeardownを定義しました.この一時クラスではteardownの具体的な内容を定義することができ、TestCaseクラスの内部定義にアクセスすることができます.現在、この一時クラス定義はTlpTestTeardownのメンバー関数にあるため、struct TlpTestCase : TlpTestTearDown時刻はTlpTestCaseの完全な定義を必要とせず、コンパイラはTlpTestTearDownのコンストラクション関数のインスタンス化を現在のcppファイルの最後に配置し、このときTlpTestCaseの完全な定義が見られるようになった.
上記の技術を使用すると、TESTマクロ定義は以下のように変更される.
#define TEST(name) struct UNIQUE_NAME(tlp_test_) :TlpTestSetup, TlpTestTearDown

そこで、テスト用例を定義する使い方は、以前と同様に、よく知られているカッコを使用することができます.しかしteardownの定義は少し統一されていません.
#define TEARDOWN_BEGIN()                            \
template  struct TlpTestTeardown \
{                                                   \
    TlpTestTeardown()                               \
    {                                               \
        struct Teardown : TestCase                  \
        {

#define TEARDOWN_END()  }; } };

teardownの定義ではsetupやtestcaseのようにカッコを使用するのではなく、TEARDOWN_BEGIN()TEARDOWN_END()が使用されることがわかります.スタイルは違いますが、これはもう私が考えることができる最善の方法です.
setupの定義をteardownと統一するために、setupの定義にも対称的なマクロ定義SETUP_BEGIN(),SETUP_END()を提供した.fixtureでsetupとteardownが同時に現れる必要がある場合、setupはteardownと一致するスタイルを使用して定義できます.その他の場合、setupはカッコのスタイルを使用します.
TlpTestTeardownコンストラクタでTeardownクラスが継承するTestCaseはテンプレートパラメータから入力されるため、TeardownではTestCaseの定義を直接参照することはできず、TestCase::接頭辞を付けなければならないという問題もある.この目的のために、指定されたteardown内の変数がTestCaseから参照されていることを示すより意味のあるマクロ__test_refer()を実現した.
#define __test_refer(...)       typename TestCase::__VA_ARGS__

最終的には、次のように完全なfixtureを定義できます.
FIXTURE(TestWithSetUpAndTearDown)
{
    SETUP_BEGIN()
        using Expected = __int(2);
    SETUP_END()

    TEARDOWN_BEGIN()
        ASSERT_EQ(__test_refer(Result), Expected);
    TEARDOWN_END()

    TEST("test1")
    {
        using Result = __add(__int(1), __int(1));
    };

    TEST("test2")
    {
        using Result = __div(__int(4), __int(2));
    };
};

teardownを導入するとフレームワーク全体が複雑になり、スタイルも一致しない傾向にあることを認めざるを得ない.しかし、可変性のないメタプログラミングでは、テストコンテキストをクリーンアップすることはもともと意味がないため、fixtureでteardownを定義する機会は多くありません.重要なことに,teardownの導入により,従来の定義testcaseの簡潔性を損なうことはなかった.またteardownのないfixtureではsetupは従来のようにカッコを使用することができます.だからほとんどの場合、受け入れることができます!
しかし、私はもっと良い実现方法があると信じていますが、本人の能力に限られて、ここまでしかできません!もっと良い実現方法を知っていれば、教えてください.
テストレポート
C++11テンプレートメタプログラミング-ディレクトリを返します