2014年6月2日月曜日

C++ Testing Framework の Catch を使ってみた - その2(Reporter)

第一回から大分時間が経ちましたが、第二回です。
今回は予定を変更して、先に Reporter 関係を見ていこうと思います。

デフォルト Reporter
Catch ではコマンドライン引数で Reporter の選択ができます。
-r, --reporterconsole
xml
junit
compact

よく使う Reporter は予め用意されていますので、十分だとは思いますが、
これら Reporter はユーザーが独自に定義することもできます。
今回はそこを解説します。

カスタム Reporter
Reporter は IReporter もしくは IStreamingReporter を継承して作ります。
どちらもいくつかの純粋仮想関数を持っているので、それぞれ必要な実装を定義します。
catch_legacy_reporter_adapter.h
catch_interfaces_reporter.h
IReporter と IStreamingReporter の使い分けについては調べきれていないので割愛します。

まずは、コンソールに出力するところからやりたいと思うので、IStreamingReporter を使った場合から説明します。

IStreamingReporter
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
using namespace Catch;

class MyStreamingReporter : public StreamingReporterBase
{
public:
    MyStreamingReporter(ReporterConfig const& _config) : StreamingReporterBase(_config) {}

    static std::string getDescription() {
        return "MyStreamingReporter";
    }

    virtual ReporterPreferences getPreferences() const {
        ReporterPreferences prefs;
        prefs.shouldRedirectStdOut = false;
        return prefs;
    }

    virtual void assertionStarting(AssertionInfo const&) {
        stream << "ASSERTION START" << ::std::endl;
    }
    virtual bool assertionEnded(AssertionStats const& _assertionStats) {
        stream << "ASSERTION END" << ::std::endl;
        return true;
    }

    virtual void sectionStarting(SectionInfo const& _sectionInfo) {
        stream << "SECTION START: " << _sectionInfo.name << ::std::endl;
        StreamingReporterBase::sectionStarting(_sectionInfo);
    }
    virtual void sectionEnded(SectionStats const& _sectionStats) {
        stream << "SECTION END: " << _sectionStats.sectionInfo.name << ::std::endl;
        StreamingReporterBase::sectionEnded(_sectionStats);
    }
};

REGISTER_REPORTER("myreporter", MyStreamingReporter);

TEST_CASE("Test", "[sample]")
{
    int a=0;
    int b=1;
    SECTION("A")
    {
        REQUIRE(a == 0);
    }
    SECTION("B")
    {
        REQUIRE(b == 0);
    }
}

IStreamingReporter を継承した StreamingReporterBase が用意されているので、そちらを利用しました。
ポイントは3つで、
1つ目は、 Reporter のコンストラクタは ReporterConfig を受け取ること。
2つ目は、getDescription static 関数を定義すること。
最後に、REGISTER_REPORTER マクロで Reporter の登録をすることです。

登録した Reporter はコマンドライン引数で名前を指定することで使用されます。
>catch_sample -r myreporter
SECTION START: Test
SECTION START: A
ASSERTION END
SECTION END: A
SECTION END: Test
SECTION START: Test
SECTION START: B
ASSERTION END
SECTION END: B
SECTION END: Test
SECTION START: Test
SECTION END: Test

コンソールへの出力は、StreamingReporterBase クラスメンバの stream に対してするだけなので非常に簡単です。
また、出力をカスタマイズするだけなら ConsoleReporter を継承して書くと楽だと思います。

IReporter
続いて、IReporter を継承した場合です。
#define CATCH_CONFIG_MAIN
#include <catch.hpp>
using namespace Catch;

class MyReporter : public SharedImpl<IReporter>
{
    std::ostream& stream;
public:
    MyReporter(ReporterConfig const& _config) : stream(_config.stream()) {}

    static std::string getDescription() {
        return "MyStreamingReporter";
    }

    virtual bool shouldRedirectStdout() const
    {
        return true;
    }

    virtual void StartTesting() {}
    virtual void EndTesting(Totals const& totals) {}
    virtual void StartGroup(std::string const& groupName) {}
    virtual void EndGroup(std::string const& groupName, Totals const& totals) {}
    virtual void StartTestCase(TestCaseInfo const& testInfo) {}
    virtual void EndTestCase(TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr) {}
    virtual void StartSection(std::string const& sectionName, std::string const& description)
    {
        stream << "SECTION START: " << sectionName << ::std::endl;
    }
    virtual void EndSection(std::string const& sectionName, Counts const& assertions)
    {
        stream << "SECTION END: " << sectionName << ::std::endl;
    }
    virtual void NoAssertionsInSection(std::string const& sectionName) {}
    virtual void NoAssertionsInTestCase(std::string const& testName) {}
    virtual void Aborted() {}
    virtual void Result(AssertionResult const& result) {}
};

REGISTER_LEGACY_REPORTER("myreporter", MyReporter);

TEST_CASE("Test", "[sample]")
{
    int a=0;
    int b=1;
    SECTION("A")
    {
        REQUIRE(a == 0);
    }
    SECTION("B")
    {
        REQUIRE(b == 0);
    }
}

IStreamingReporter の場合とほとんど同じです。
異なる点は、REGISTER_LEGACY_REPORTER で登録することと、仮想関数の種類・引数が違うくらいです。
既存の Reporter では、XmlReporter が IReporter を継承して作られていますが、使い分けについてはよくわかってません。
使いやすい方を使えばいいと思います。

一応出力結果も載せておきます。
>catch_sample -r myreporter
SECTION START: Test
SECTION START: A
SECTION END: A
SECTION END: Test
SECTION START: Test
SECTION START: B
SECTION END: B
SECTION END: Test
SECTION START: Test
SECTION END: Test

まとめ
Catch でも GoogleTest のようなイベントリスナーが書けます。
登録したら勝手に、コマンドライン引数で呼び分けられるようになるのも嬉しいですね。

では、次回は(いつになるかわからんが) BDD-style か test-fixture について書こうと思います。

0 件のコメント:

コメントを投稿