2014年10月21日火曜日

[gtest] 複数要素を持つパラメータでテストを書く

Google Test で値をパラメータとして扱う際に、パラメータに複数の要素を含めたい場合の書き方を説明します。

構造体をパラメータ化
ぱっと思いつく簡単な方法として構造体を使う方法があります。
struct Param
{
    int x;
    float y;
};

::std::vector<Param> make_param()
{
    ::std::vector<Param> v;
    for( int i=0; i < 5; ++i ) v.push_back(Param{ i, i*0.5f });
    return v;
}

class Test : public testing::TestWithParam<Param> {};
INSTANTIATE_TEST_CASE_P(A, Test, testing::ValuesIn(make_param()));

TEST_P(Test, A)
{
    Param p = GetParam();
    ::std::cout << p.x << ", " << p.y << ::std::endl;
}

ただ、これだと構造体の定義やパラメータの生成がやや面倒です。

tuple パラメータ
tuple をパラメータの型として扱う方法です。
class Test : public testing::TestWithParam< ::std::tuple<int, float> > {};
INSTANTIATE_TEST_CASE_P(A, Test, testing::Values(
    ::std::make_tuple(0, 0*0.5f)
    , ::std::make_tuple(1, 1 * 0.5f)
    , ::std::make_tuple(2, 2 * 0.5f)
    , ::std::make_tuple(3, 3 * 0.5f)
    , ::std::make_tuple(4, 4 * 0.5f)
    ));

TEST_P(Test, A)
{
    ::std::tuple<int, float> p = GetParam();
    ::std::cout << ::std::get<0>(p) << ", " << ::std::get<1>(p) << ::std::endl;
}

構造体の分だけ、すこ~しだけ簡単になった?気がします。
パラメータの構築は最初の例のように vector を返す関数で作ってもいいです。お好みでどうぞ。

iutest の場合
恒例の宣伝。
自作テスティングフレームワークである iutest では以下のようなに ::std::get を省略した書き方もできます。
class Test : public ::iutest::TestWithParam< ::std::tuple<int, float> > {};
IUTEST_INSTANTIATE_TEST_CASE_P(A, Test, ::iutest::Values(
    ::std::make_tuple(0, 0*0.5f)
    , ::std::make_tuple(1, 1 * 0.5f)
    , ::std::make_tuple(2, 2 * 0.5f)
    , ::std::make_tuple(3, 3 * 0.5f)
    , ::std::make_tuple(4, 4 * 0.5f)
    ));

IUTEST_P(Test, A)
{
    ::std::cout << GetParam<0>() << ", " << GetParam<1>() << ::std::endl;
}
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

200投稿

週1回の更新を目標にやってきましたが、この投稿で200投稿となりました。
初投稿が 2011/11/06 なので約3年での達成ということで目標以上の結果となりました。
ここまで続けてこれたのもブログを見ていただいている皆様のおかげでございます。
今後ともよろしくお願いします。m(__)m

2014年10月14日火曜日

[gtest] OR 条件のアサーション

Google Group で OR 条件のアサーションはどう書くの?というトピックがあったので、紹介します。
Google C++ Testing Framework › Logical OR of EXPECT_*

AND は簡単だけど、OR は・・・
AND 条件は簡単に書けます。
TEST(Hoge, AndTest)
{
    ASSERT_NE(0, a);
    ASSERT_NE(4, a);
}

これは、a != 0 && a != 4 を意味します。

では、a == 0 || a == 4 のような条件を考えます。
TEST(Hoge, OrTest)
{
    EXPECT_EQ(0, a);
    EXPECT_EQ(4, a);
}
当然これはダメです。これでは AND になってしまします。
例えば、下記のように書けると良いのですが、残念ながらできません。
TEST(Hoge, OrTest)
{
    EXPECT_EQ(0, a) || EXPECT_EQ(4, a);
}

最も簡単な方法
Google Group でも紹介されていた最も簡単な方法です。

TEST(Hoge, OrTest)
{
    ASSERT_TRUE( a == 0 || a == 4 );
}
[----------] 1 test from Hoge
[ RUN      ] Hoge.OrTest
main.cpp(99): error: Value of: a == 0 || a == 4
  Actual: false
Expected: true
[  FAILED  ] Hoge.OrTest (4 ms)
[----------] 1 test from Hoge (6 ms total)

ただ、これでは失敗したときに a の値がなんだったのかわかりません。
なので、以下のようにして a の値を出力するようにします。

TEST(Hoge, OrTest)
{
    ASSERT_TRUE( a == 0 || a == 4 ) << "a = " << a;
}
出力はこのようになります。
[----------] 1 test from Hoge
[ RUN      ] Hoge.OrTest
main.cpp(99): error: Value of: a == 0 || a == 4
  Actual: false
Expected: true
a = 1
[  FAILED  ] Hoge.OrTest (4 ms)
[----------] 1 test from Hoge (6 ms total)

値の出力には SCOPED_TRACE を使う方法もあります。

TEST(Hoge, OrTest)
{
    SCOPED_TRACE( ::testing::Message() << a );
    ASSERT_TRUE( a == 0 || a == 4 );
}

SCOPED_TRACE は記述したスコープの間はメッセージが追記されます。なので、複数のアサーションを記述している場合は一箇所の修正で済むので楽です。
[----------] 1 test from Hoge
[ RUN      ] Hoge.OrTest
main.cpp(99): error: Value of: a == 0 || a == 4
  Actual: false
Expected: true
Google Test trace:
main.cpp(99): 1
[  FAILED  ] Hoge.OrTest (4 ms)
[----------] 1 test from Hoge (6 ms total)

Matcher を使う方法や他の方法もあると思いますが、Google Test のみの場合ではこの方法が一番だと思います。

2014年10月6日月曜日

[Jenkins] Build-timeout Plugin の「最後のログ出力からの経過時間」が便利

Build-timeout Plugin は文字通りビルドにタイムアウトの設定ができるプラグインです。
こちらの Version 1.13 からタイムアウトの判定方法に「最後のログ出力からの経過時間」が追加されました。
これがかなりいい感じです!

これまでの判定方法には
  • Absolute
  • Elastic
  • Likely stuck
がありました。

Absolute
Absolute は、絶対時間でビルド開始からの経過時間でタイムアウトする設定です。
これは、少なくともビルド成功時にかかる時間以上の時間に設定しないといけません。このため、成功時に10時間かからジョブの場合、ジョブ開始直後に問題が発生した場合でも最低10時間は待たなければいけません。これは大変無駄なことです。

Elastic
つぎに Elastic は、直近の成功ビルドの平均時間からタイムアウト時間を算出する設定です。平均時間の 150%~400% (50刻み)を設定できます。
これは一見良さそうなのですが、実行ノードが毎回異なり、さらにスレーブのマシンスペックに差があると、実行時間にばらつきが出てしまい一番遅い環境に合わせて多めにタイムアウト値を設定しなければいけなくなります。
Absolute の時と同様にジョブの実行時間が長い場合、それだけ無駄な時間が増えます。また、実行時間が短い場合、今度はマージンが少なくなりタイムアウトしなくても大丈夫な場合でもタイムアウトしてしまう、ということがありました。

Likely stuck
最後に Likely stuck の場合は、滞留している可能性が高いならば中止する設定です。この判断は Jenkins の API が判断しています。
http://javadoc.jenkins-ci.org/hudson/model/Executor.html#isLikelyStuck()
Returns true if the current build is likely stuck.
This is a heuristics based approach, but if the build is suspiciously taking for a long time, this method returns true.
この設定は使ったことがないのですが、コードを見た感じでは推定時間がある場合はその10倍(?)、なければ24時間でタイムアウトするようです。間違っていたらすみませんm(__)m
CodeNav for Github 便利だわー)
なんにせよ、この設定も無駄に長く待ってしまう感じがします。

最後のログ出力からの経過時間
新しい判定方法の「最後のログ出力からの経過時間」では、「ログ出力が停滞して○○秒後にタイムアウトする」という設定ができます。
ビルドでもテストでも大抵の場合ログ出力はあると思います。
また、ログが出ているところまでは正常なので、そこから停滞した時間の設定はそんなに多くなくて大丈夫なはずです。よって、この判定方法は他の方法よりも無駄を少なくすることができるでしょう。(当然ログが適宜出力されることを想定してます)

ビルドが長時間停滞して困っている方は一度プラグインの導入・判定方法の見直しをしてはいかかがでしょうか?