お手軽assertで失敗時の詳細(左辺値・右辺値)を確認する


小ネタです。

TL;DR

以下のコードをヘッダ等に忍ばせておくことで、
お手軽に失敗時の詳細(左辺値・右辺値)を確認できるassertが書けます。

static inline void ASSERT_EQ_IMPL(const std::string &file, const int lineno,
                                  const std::string &func,
                                  const std::string &exp_symbol,
                                  const auto &exp,
                                  const std::string &act_symbol,
                                  const auto &act) {
  if (exp == act) {
    return;
  }
  std::cerr << file << ": " << lineno << ": " << func << ": error: check "
            << exp_symbol << " == " << act_symbol << " has failed [" << exp
            << " != " << act << "]" << std::endl;
  std::terminate();
}

#define ASSERT_EQ(exp, act)                                                    \
  do {                                                                         \
    ASSERT_EQ_IMPL(__FILE__, __LINE__, __func__, #exp, exp, #act, act);        \
  } while (0)

背景

LeetCodeのような短いプログラムのテストを書くときや、
main loopも含めたライブラリのテストを書く時は、
GoogleTest等のtesting frameworkの導入が手間に感じるときがあります。

そのような場合、私は普通のassertでテストを書くのですが、
下記のように一致しなかったことしかわからず、実際の値がどうなっていたのかがわからないため、
解析しづらいことが多いです。

そこで、失敗時の左辺値・右辺値を出力するようにしたASSERT_EQが、冒頭のものです。
後々GoogleTestに移行したときにちょっとでも楽になるようにI/FはGoogleTestに合わせています。

従来のassertを使ったテストコード

  int exp = 1;
  int act = 2;

  assert(exp == act);

従来のassertの出力

test11: test.cc:87: int main(): Assertion `exp == act' failed.

今回のASSERT_EQの使用例

以下のように、整数値でもstd::stringでも比較できます。

テストコード(整数)

  int exp = 1;
  int act = 2;
  ASSERT_EQ(exp, act);

出力(整数)

test.cc: 19: TestInt: error: check exp == act has failed [0 != 1]

テストコード(文字列)

  std::string exp = "expected string";
  std::string act = "actual string";
  ASSERT_EQ(exp, act);

出力(文字列)

test.cc: 65: TestString: error: check exp == act has failed [expected string != actual string]

仮引数の型推論(C++14)なんか使えないんだけど?

最初の例は仮引数の型推論を使っているので、C++14でコンパイルする必要があります。
テンプレートを使えばC++11でも同じようなことが書けます。

template <class T1, class T2>
static inline void ASSERT_EQ_IMPL(const std::string &file, const int lineno,
                                  const std::string &func, const std::string &exp_symbol,
                                  const T1 &exp, const std::string &act_symbol, const T2 &act) {
  if (exp == act) {
    return;
  }
  std::cerr << file << ": " << lineno << ": " << func << ": error: check "
            << exp_symbol << " == " << act_symbol << " has failed [" << exp
            << " != " << act << "]" << std::endl;
  std::terminate();
}

#define ASSERT_EQ(exp, act)                                                    \
  do {                                                                         \
    ASSERT_EQ_IMPL(__FILE__, __LINE__, __func__, #exp, exp, #act, act);        \
  } while (0)

Scoped Enumの比較に使うとエラーになるんだけど?

型チェックが厳格なのでありがたいScoped Enumですが、iostreamによる出力ができないためそのままではエラーになります。
下記のサイトで紹介してくれている実装を使うことで、Scoped Enumも比較できるようになります(田原さん、ありがとうございます)。

scoped enumをお手軽に出力できるようにする | Theolizer®

実装

テストも含めた実装はこちらに置いています。

参考