Закрытый метод модульного тестирования в c ++ с использованием класса друга

15

Я знаю, что это спорная практика, но давайте предположим, что это лучший вариант для меня. Мне интересно, какова реальная техника, чтобы сделать это. Подход, который я вижу, заключается в следующем:

1) Сделайте класс друга из класса, метод которого я хочу проверить.

2) В другом классе создайте открытый (ые) метод (ы), который вызывает закрытый (ые) метод (ы) тестируемого класса.

3) Проверьте общедоступные методы класса друга.

Вот простой пример, иллюстрирующий вышеуказанные шаги:

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

Редактировать:

Я вижу, что в обсуждении после одного из ответов люди задаются вопросом о моей кодовой базе.

В моем классе есть методы, которые вызываются другими методами; ни один из этих методов не должен вызываться вне класса, поэтому они должны быть закрытыми. Конечно, их можно объединить в один метод, но по логике они гораздо лучше разделены. Эти методы достаточно сложны, чтобы гарантировать модульное тестирование, и из-за проблем с производительностью мне, скорее всего, придется пересмотреть эти методы, поэтому было бы неплохо провести тест, чтобы убедиться, что мой рефакторинг ничего не сломал. Я не единственный, кто работает в команде, хотя я единственный, кто работает над этим проектом, включая тесты.

Сказав выше, мой вопрос не был о том, является ли хорошей практикой написание модульных тестов для частных методов, хотя я ценю обратную связь.

Akavall
источник
5
Вот сомнительное предложение для сомнительного вопроса. Мне не нравится связь с другом, так как выпущенный код должен знать о тесте. Ответ Нира ниже - один из способов облегчить это, но я все еще не очень люблю менять класс, чтобы он соответствовал тесту. Поскольку я не часто использую наследование, я иногда просто защищаю закрытые методы, а тестовый класс наследует и выставляет по мере необходимости. Я ожидаю по крайней мере три "шипения" для этого комментария, но реальность такова, что открытый API и API тестирования могут отличаться и все же отличаться от частного API. Мех.
J Trana
4
@JTrana: Почему бы не написать это как правильный ответ?
Барт ван Инген Шенау
Ok. Это не один из тех, кем вы гордитесь, но, надеюсь, это поможет.
J Trana

Ответы:

23

Альтернативой другу (в некотором смысле), которую я часто использую, является шаблон, который я узнал как access_by. Это довольно просто:

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

Теперь предположим, что класс B участвует в тестировании A. Вы можете написать это:

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

Затем вы можете использовать эту специализацию access_by для вызова закрытых методов A. По сути, это означает, что бремя объявления дружбы помещается в заголовочный файл класса, который хочет вызывать частные методы A. Это также позволяет добавлять друзей в A без изменения источника A. Идиоматически, это также указывает тому, кто читает источник A, что A не указывает B на истинного друга в смысле расширения его интерфейса. Скорее, интерфейс A завершен, как дано, и B требуется специальный доступ к A (хороший пример - тестирование, я также использовал этот шаблон при реализации привязок форсированного Python, иногда функция, которая должна быть закрытой в C ++, удобна для выставить в слой Python для реализации).

Нир Фридман
источник
Интересуетесь допустимым вариантом использования для того friend access_by, чтобы сделать первый не-друг достаточным - будучи вложенной структурой, он имел бы доступ ко всему внутри A? например. coliru.stacked-crooked.com/a/663dd17ed2acd7a3
острый
10

Если сложно проверить, плохо написано

Если у вас есть класс с частными методами, достаточно сложными, чтобы гарантировать собственный тест, класс делает слишком много. Внутри есть другой класс, пытающийся выбраться.

Извлеките закрытые методы, которые вы хотите протестировать, в новый класс (или классы) и сделайте их общедоступными. Проверьте новые классы.

Помимо упрощения тестирования кода, этот рефакторинг облегчит понимание и сопровождение кода.

Кевин Клайн
источник
1
Я полностью согласен с этим ответом: если вы не можете полностью протестировать свои закрытые методы путем тестирования открытых методов, что-то не так, и рефакторинг частных методов для их собственного класса был бы хорошим решением.
Дэвид исполняет
4
В моей кодовой базе класс имеет очень сложный метод, который инициализирует вычислительный граф. Он вызывает несколько подфункций последовательно для достижения различных аспектов этого. Каждая подфункция довольно сложна, а общая сумма кода очень сложна. Однако подфункции не имеют смысла, если их не вызывать в этом классе и в правильной последовательности. Все, что беспокоит пользователя - это полностью инициализировать вычислительный граф; промежуточные продукты бесполезны для пользователя. Пожалуйста, я хотел бы услышать, как я должен реорганизовать это, и почему это имеет больше смысла, чем просто тестирование частных методов.
Нир Фридман,
1
@Nir: Сделайте тривиальное: извлеките класс со всеми этими методами публично и сделайте ваш существующий класс фасадом вокруг нового класса.
Кевин Клайн
Этот ответ верен, но он действительно зависит от данных, с которыми вы работаете. В моем случае мне не предоставляются фактические данные тестирования, поэтому мне нужно создать их, наблюдая данные в реальном времени, а затем «внедрить» их в мое приложение и посмотреть, как Это происходит. Один фрагмент данных слишком сложен для обработки, поэтому было бы намного проще искусственно создать данные частичного тестирования, которые являются только подмножеством данных в реальном времени для каждой цели, чем фактически воспроизводить данные в реальном времени. Частная функция не сложна Достаточно, чтобы призвать к реализации несколько других небольших классов (с соответствующим функционалом)
rbaleksandar
4

Вы не должны тестировать частные методы. Период. Классы, использующие ваш класс, заботятся только о методах, которые он предоставляет, а не о тех, которые он использует для работы.

Если вы беспокоитесь о покрытии кода, вам нужно найти конфигурации, которые позволят вам протестировать этот закрытый метод с помощью одного из открытых вызовов метода. Если вы не можете этого сделать, какой смысл иметь метод во-первых? Это просто недоступный код.

Ampt
источник
6
Суть частных методов заключается в том, чтобы облегчить разработку (путем разделения проблем, поддержки СУХОГО или любого количества вещей), но они должны быть непостоянными. Они являются частными по этой причине. Они могут появляться, исчезать или резко менять функциональность от одной реализации к другой, поэтому привязка их к модульным тестам не всегда практична или даже полезна.
День
8
Это не всегда может быть практично или полезно, но это очень далеко от того, чтобы сказать, что вы никогда не должны проверять их. Вы говорите о частных методах, как будто они являются чьими-то частными методами; «они могут появиться, исчезнуть ...». Нет, они не могли. Если вы тестируете их напрямую, это должно быть только потому, что вы поддерживаете их сами. Если вы меняете реализацию, вы меняете тесты. Короче говоря, ваше общее заявление неоправданно. Хотя хорошо предупредить ОП об этом, его вопрос все еще оправдан, и ваш ответ на самом деле не отвечает.
Нир Фридман
2
Позвольте мне также отметить: ФП сказал, что он знает, что это спорная практика. Так что, если он все равно хочет это сделать, может, у него действительно есть на то веские причины? Никто из нас не знает подробностей его кодовой базы. В коде, с которым я работаю, у нас есть несколько очень опытных и опытных программистов, и они думают, что в некоторых случаях полезно тестировать частные методы.
Нир Фридман
2
-1, ответ типа «Вы не должны тестировать частные методы». ИМХО не полезно. Тема, когда тестировать, а когда не тестировать приватные методы, достаточно обсуждалась на этом сайте. ФП показал, что ему известно об этом обсуждении, и он явно ищет решения, исходя из того, что в его случае тестирование частных методов - это путь.
Док Браун
3
Я думаю, что проблема здесь в том, что это может быть очень классическая проблема XY, в которой OP думает, что ему нужно модульное тестирование своих частных методов по той или иной причине, когда в действительности он мог бы подойти к тестированию с более прагматичной точки зрения, просматривая приватный методы как простые вспомогательные функции для общедоступных, которые являются контрактом с конечными пользователями класса.
День
3

Есть несколько вариантов сделать это, но имейте в виду, что они (по сути) изменяют открытый интерфейс ваших модулей, чтобы дать вам доступ к внутренним деталям реализации (эффективно превращая модульное тестирование в тесно связанные клиентские зависимости, где вы должны иметь нет зависимости вообще).

  • Вы можете добавить объявление друга (класса или функции) в тестируемый класс.

  • Вы можете добавить #define private publicв начало ваших тестовых файлов, прежде чем #include-проверенный код. Если тестируемый код является уже скомпилированной библиотекой, это может привести к тому, что заголовки больше не будут соответствовать уже скомпилированному двоичному коду (и вызовут UB).

  • Вы можете вставить макрос в тестируемый класс и позднее решить, что означает этот макрос (с другим определением для тестирования кода). Это позволит вам тестировать внутренние компоненты, но также позволит стороннему клиентскому коду взломать ваш класс (путем создания своего собственного определения в объявлении, которое вы добавляете).

utnapistim
источник
2

Вот сомнительное предложение для сомнительного вопроса. Мне не нравится связь с другом, так как выпущенный код должен знать о тесте. Ответ Нира - один из способов облегчить это, но мне все еще не очень нравится менять класс, чтобы он соответствовал тесту.

Поскольку я не часто использую наследование, я иногда просто защищаю закрытые методы, а тестовый класс наследует и выставляет по мере необходимости. Реальность такова, что открытый API и API тестирования могут отличаться и все же отличаться от частного API, что оставляет вас в некотором роде.

Вот практический пример, к которому я прибегаю к этому трюку. Я пишу встроенный код, и мы очень полагаемся на конечные автоматы. Внешний API не обязательно должен знать о состоянии внутреннего конечного автомата, но тест должен (возможно) проверять соответствие диаграмме конечного автомата в проектной документации. Я мог бы выставить получатель «текущего состояния» как защищенный, а затем дать ему доступ к тестированию, что позволило бы мне более полно протестировать конечный автомат. Я часто нахожу этот тип класса трудным для тестирования как черный ящик.

Дж Трана
источник
Несмотря на то, что это Java-подход, он является стандартным, когда уровень частных функций устанавливается по умолчанию, вместо этого позволяя другим классам в том же пакете (тестовым классам) видеть их.
0

Вы можете написать свой код с множеством обходных путей, чтобы вам не приходилось пользоваться друзьями.

Вы можете писать классы и никогда не иметь никаких частных методов вообще. Все, что вам нужно сделать, - это создать функции реализации внутри модуля компиляции, позволить вашему классу вызывать их и передавать любые элементы данных, к которым они имеют доступ.

И да, это будет означать, что вы можете изменить сигнатуры или добавить новые методы «реализации», не меняя заголовок в будущем.

Вы должны взвесить, стоит ли это того или нет. И многое будет зависеть от того, кто увидит ваш заголовок.

Если я использую стороннюю библиотеку, я бы предпочел не видеть объявления друзей своим тестировщикам. Я тоже не хочу создавать их библиотеку и запускать их тесты. К сожалению, слишком много сторонних библиотек с открытым исходным кодом, которые я создал, делают это.

Тестирование - это работа авторов библиотеки, а не ее пользователей.

Однако не все классы видны пользователю вашей библиотеки. Многие классы являются «реализацией», и вы реализуете их наилучшим образом, чтобы обеспечить их правильную работу. В них у вас могут быть частные методы и члены, но вы хотите, чтобы юнит-тестеры их тестировали. Так что продолжайте и делайте так, если это приведет к более быстрому созданию надежного кода, что будет легко поддерживать для тех, кому это необходимо.

Если все пользователи вашего класса находятся в вашей собственной компании или команде, вы также можете немного больше расслабиться об этой стратегии, если она допускается стандартами кодирования вашей компании.

Дойная корова
источник