Как написать хороший GMock matcher для двумерных массивов? - PullRequest
1 голос
/ 12 апреля 2020

Мне нужен был ограниченный набор операций алгебры над двумерными матрицами для моего кода C ++. Я решил реализовать это, используя std::array примерно так:

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

Как правильно написать средство сопоставления GMock для этого типа, чтобы сравнить две такие матрицы чисел типа double? Я придумал не очень умный:

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 следует использовать для улучшения этого сопоставителя? Или, может быть, кто-то может указать часть документации, которую я должен прочитать более внимательно, чтобы понять, чего мне здесь не хватает?

Ответы [ 2 ]

1 голос
/ 12 апреля 2020

Одна вещь, которую вы могли бы рассмотреть, - это явное возвращение false или true из вашего сопоставителя вместо вызова утверждений. Затем вы можете использовать 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 не печатать содержимое std::array в Value of и Expected поля обратной связи. в документах они упоминают функцию void PrintTo, но она не работает для меня.

=== РЕДАКТИРОВАТЬ ===

Если вы можете создайте класс 2D-массива вместо typedef, тогда легко подавить беспорядочный вывод gmock, предоставив перегрузку operator<<:

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

Затем вам нужно немного изменить matcher, чтобы использовать поле класса data вместо operator[] и size() напрямую. Или вы можете перегрузить их для вашего класса.

Если вам нужен комментарий @JanHackenberg, то внутри вашего сопоставителя просто установите флаг result = false вместо return (хотя я бы этого не сделал, потому что для больших массивов он не будет читаться).

0 голосов
/ 12 апреля 2020

Вы хотите получить информацию об индексах и, похоже, не хотите получать фактические значения. Как насчет:

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

...