Подтвердить что ты не робот

Что не так с созданием unit test друга класса, который он тестирует?

В С++ я часто делал класс unit test другом класса, который я тестировал. Я делаю это, потому что иногда чувствую необходимость писать unit test для частного метода или, возможно, хочу получить доступ к некоторому частному члену, чтобы я мог более легко настроить состояние объекта, чтобы я мог его протестировать. Для меня это помогает сохранить инкапсуляцию и абстракцию, потому что я не изменяю открытый или защищенный интерфейс класса.

Если я куплю библиотеку сторонних разработчиков, я бы не хотел, чтобы ее публичный интерфейс был загрязнен множеством публичных методов, о которых мне не нужно знать просто потому, что вендор хотел unit test!

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

Вот почему я говорю, что он сохраняет абстракцию и инкапсуляцию.

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

Мне кажется, что Bcat дал очень хороший ответ, но я хотел бы пояснить исключительный случай, на который он ссылается

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

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

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

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

class Foo{
public:
     some_db_accessing_method(){
         // some line(s) of code with db dependance.

         // a bunch of code which is the real meat of the function

         // maybe a little more db access.
     }
}

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

Гораздо более прагматичным подходом было бы сделать что-то вроде этого:

 class Foo{
 public: 
     some_db_accessing_method(){
         // db code as before
         unit_testable_meat(data_we_got_from_db);
         // maybe more db code.    
 }
 private:
     unit_testable_meat(...);
 }

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

Я думаю, что это должно стать идиомой, и я думаю, что это подходящее, прагматичное решение для увеличения ROI модульного тестирования.

Ответ 4

Как и bcat, как можно больше, вам нужно найти ошибки, используя сам публичный интерфейс. Но если вы хотите делать такие вещи, как печать частных переменных и сравнение с ожидаемым результатом и т.д. (Полезно, чтобы разработчики легко отлаживали проблемы), вы можете сделать класс UnitTest дружественным к классу, который будет протестирован. Но вам может понадобиться добавить его под макросом, как показано ниже.

class Myclass
{
#if defined(UNIT_TEST)
    friend class UnitTest;
#endif
};

Включить флаг UNIT_TEST только тогда, когда требуется тестирование устройства. Для других выпусков вам необходимо отключить этот флаг.

Ответ 5

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

Ответ 6

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

Ответ 7

Сделайте функции, которые вы хотите проверить, защищены. Теперь в вашем файле unit test создайте производный класс. Создайте общедоступные функции-обертки, которые вызывают защищенные функции класса.