如何为二维数组编写一个好的 GMock 匹配器?

How to write a good GMock matcher for two-dimensional arrays?

我的 C++ 代码需要一组有限的二维矩阵代数运算。我决定像这样使用 std::array 来实现它:

template <typename T, size_t N, size_t M>
using array_2d = std::array<std::array<T, M>, N>;

我应该如何为这种类型正确编写 GMock 匹配器,以比较两个这样的双精度矩阵? 我想出了一个不太聪明的办法:

MATCHER_P(Arrays2dDoubleEq, expected, "") {
    for (int i = 0; i < arg.size(); i++) {
        for (int j = 0; j < arg[i].size(); j++) {
            EXPECT_THAT(arg[i][j], DoubleEq(expected[i][j]));
        }
    }
    return true;
}

MATCHER_P2(Arrays2dDoubleNear, expected, max_abs_err, "") {
    for (int i = 0; i < arg.size(); i++) {
        for (int j = 0; j < arg[i].size(); j++) {
            EXPECT_THAT(arg[i][j], DoubleNear(expected[i][j], max_abs_err));
        }
    }
    return true;
}

我用的是:EXPECT_THAT(result, Arrays2dDoubleEq(expected));

这不仅看起来编码很硬,而且反馈也不好。当矩阵不匹配时,失败断言的输出是一堆不相等的双打。失败的测试输出几乎不可读,并且缺少有关矩阵索引的信息。

我觉得可以(并且应该)以更好的方式完成。我已经看过一些文档和 GMock 说明书。尽管有一些容器匹配器,但我无法使用它们同时比较两个嵌套数组。

任何人都可以指出我应该使用哪些 GMock 功能来使这个匹配器更好吗?或者也许有人可以指出我应该更仔细阅读的文档的一部分,以了解我在这里可能遗漏了什么?

您想获取有关索引的信息,但似乎不想获取实际值。怎么样:

MATCHER_P(Arrays2dDoubleEq, expected, "") {
    bool allMatched = true;
    for (int i = 0; i < arg.size(); i++) {
        for (int j = 0; j < arg[i].size(); j++) {
            if(arg[i][j] != expected[i][j]) {
                 std::cout << "Failing at indices:" << i << ";" << j << std::endl;
                 allMatched = false;
            }
        }
    }
    EXPECT_THAT(allMatched, true);
    return true;
}

您可能会考虑的一件事是显式 return falsetrue 形成匹配器而不是调用断言。然后,您可以使用 result_listener 提供有关比赛中到底出了什么问题的附加信息。您还应该在执行检查之前检查数组维度以避免未定义的行为

using testing::DoubleEq;
using testing::Value;
using testing::Not;

MATCHER_P(Arrays2dDoubleEq, expected, "") {
  if (arg.size() != expected.size())
  {
    *result_listener << "arg.size() != expected.size() ";
    *result_listener << arg.size() << " vs " << expected.size();
    return false;
  }
  for (size_t i = 0; i < arg.size(); i++) {
    if (arg[i].size() != expected[i].size())
    {
      *result_listener << "arg[i].size() != expected[i].size() i = " << i << "; ";
      *result_listener << arg[i].size() << " vs " << expected[i].size();
      return false;
    }
    for (size_t j = 0; j < arg[i].size(); j++) {
      if (!Value(arg[i][j], DoubleEq(expected[i][j])))
      {
        *result_listener << "element(" << i << ", " << j << ") mismatch ";
        *result_listener << arg[i][j] << " vs " << expected[i][j];
        return false;
      }
    }
  }
  return true;
}

TEST(xxx, yyy)
{
  array_2d<double, 2, 3> arr1 = {std::array<double, 3>({1, 2, 3}), std::array<double, 3>({4, 5, 6})};
  array_2d<double, 2, 3> arr2 = arr1;
  array_2d<double, 2, 3> arr3 = arr1;
  arr3[0][0] = 69.69;
  array_2d<double, 5, 6> arr4;
  ASSERT_THAT(arr1, Arrays2dDoubleEq(arr2));
  ASSERT_THAT(arr2, Not(Arrays2dDoubleEq(arr3)));
  ASSERT_THAT(arr2, Not(Arrays2dDoubleEq(arr4)));
}

不幸的是,我还没有弄清楚如何告诉 gmock 不要在 Value ofExpected 反馈字段中打印 std::array 的内容。在 docs 中他们提到了 void PrintTo 功能,但它对我不起作用。

=== 编辑 ===

如果您可以创建一个二维数组 class 而不是 typedef,那么通过提供 operator<< 重载可以很容易地抑制 gmock 混乱的输出:

template <typename T, size_t N, size_t M>
struct Array2D
{
  std::array<std::array<T, M>, N> data;
};

template <typename T, size_t N, size_t M>
std::ostream& operator<<(std::ostream& os, const Array2D<T, N, M>&)
{
  os << "Array2D<" << typeid(T).name() << "," << N << "," << M << ">";
  return os;
}

然后你需要稍微修改匹配器以使用 data class 字段而不是直接使用 operator[]size()。或者您可以为 class.

重载它们

如果 @JanHackenberg 的评论是你想要的,在你的匹配器中只需设置标志 result = false 而不是 return (不过我不会这样做,因为对于大数组它不会可读)。