【问题标题】:How to write a good GMock matcher for two-dimensional arrays?如何为二维数组编写一个好的 GMock 匹配器?
【发布时间】:2020-04-12 13:37:20
【问题描述】:

我的 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 功能来改进这个匹配器?或者也许有人可以指出我应该更仔细阅读以了解我在这里可能遗漏的部分文档?

【问题讨论】:

    标签: c++ unit-testing googletest matcher googlemock


    【解决方案1】:

    您可能会考虑的一件事是从匹配器中显式返回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 功能,但它对我不起作用。

    === 编辑 ===

    如果您可以创建二维数组类而不是 typedef,那么通过提供operator&lt;&lt; 重载可以轻松抑制 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 类字段而不是直接使用operator[]size()。或者你可以为你的班级重载它们。

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

    【讨论】:

    • 您在第一个 *result_listener &lt;&lt; "element(" &lt;&lt; i &lt;&lt; ", " &lt;&lt; j &lt;&lt; ") mismatch "; 之后立即返回 false,但您返回时可能会跳过更多不匹配。也感谢检查尺寸-
    • @JanHackenberg 是的,我立即返回,因为如果一个元素不匹配,则意味着数组不一样,没有必要进一步检查。
    • 重点是主题创建者对失败的索引感兴趣。
    • 感谢您指出result_listener 的事情。我决定坚持使用typedef 方法。还要感谢您包括尺寸检查。结果对我来说似乎很满意,所以解决了。
    【解决方案2】:

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

    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;
    }
    
    

    【讨论】:

    • 最好使用*result_listener 对象而不是std::cout。这样,输出也将在 Googletest 可能输出的任何其他地方可见(例如 IDE 使用的 XML 文件以图形形式呈现结果)
    猜你喜欢
    • 1970-01-01
    • 2011-07-18
    • 2018-10-09
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    • 2018-05-05
    • 1970-01-01
    • 2010-12-04
    相关资源
    最近更新 更多