Должны ли непубличные функции быть проверены модулем и как? - PullRequest
8 голосов
/ 12 февраля 2009

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

Мой первоначальный вопрос: должен ли я стремиться к модульному тестированию и этих внутренних функций, поскольку они проще и, следовательно, проще для написания тестов? Мое инстинктивное чувство говорит, что да, что приводит к последующему вопросу, если так, как бы я поступил так в C ++?

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

Ответы [ 12 ]

16 голосов
/ 12 февраля 2009

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

11 голосов
/ 12 февраля 2009

Краткий ответ: да.

Что касается того, как я поймал мимолетную ссылку на SO несколько дней назад:

#define private public

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

Очень крутая идея.


Немного более длинный ответ: Проверьте, не является ли код явно правильным . Что означает практически любой код, который делает что-то нетривиальное.


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

5 голосов
/ 12 февраля 2009

Есть несколько возможных подходов. предполагая, что ваш класс X:

  1. Используйте только открытый интерфейс X. У вас будут серьезные проблемы с настройкой, и вам может понадобиться инструмент покрытия, чтобы убедиться, что ваш код покрыт, но никаких специальных уловок не предусмотрено.
  2. Используйте "#define private public" или аналогичный трюк для связи с версией X.o, доступной для всех.
  3. Добавить открытый метод "static X :: unitTest ()". Это означает, что ваш код будет поставляться связанным с вашей средой тестирования. (Однако одна компания, с которой я работал, использовала это для удаленной диагностики программного обеспечения.)
  4. Добавьте "класс TestX" как друга X. TestX не поставляется в вашей производственной dll / exe. Он определен только в вашей тестовой программе, но имеет доступ к внутренним компонентам X.
  5. Другие ...
5 голосов
/ 12 февраля 2009

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

Но мой опыт модульного тестирования (к сожалению) невелик; если кто-то может привести убедительный пример, когда большая часть закрытого кода не может быть выделена, я, безусловно, готов пересмотреть это!

3 голосов
/ 12 февраля 2009

Мое мнение - нет, как правило, они должны не проверяться напрямую.

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

Так, например, строковый класс (который не нуждается в устаревшей поддержке char *):

  • Вы должны убедиться, что метод length () работает правильно.
  • вам не нужно проверять, что он помещает символ '\ 0' в конец своего внутреннего буфера. Это деталь реализации.

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

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

2 голосов
/ 12 февраля 2009

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

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

2 голосов
/ 12 февраля 2009

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

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

2 голосов
/ 12 февраля 2009

Я бы сказал, использовать инструмент покрытия кода, чтобы проверить, были ли эти функции уже протестированы.

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

Я знаю, что есть инструменты для работы с C / C ++. CoverageMeter является одним из них.

0 голосов
/ 15 января 2019

Если ваш класс выполняет сложные внутренние вычисления, класс полезности или даже внешняя функция могут быть лучшим вариантом, чтобы разбить вычисления. Но если объект имеет сложную внутреннюю структуру, он должен иметь функции проверки согласованности. Т.е., если объект представляет собой специализированное дерево, у класса должны быть методы для проверки правильности дерева. Дополнительные функции, такие как глубина дерева, часто полезны для пользователей класса. Некоторые из этих функций могут быть объявлены внутри #ifdef DEBUG или аналогичных конструкций для ограничения пространства времени выполнения во встроенных приложениях. Использование внутренних функций, которые компилируются только при установленном DEBUG, намного лучше с точки зрения инкапсуляции. Вы не нарушаете инкапсуляцию. Кроме того, зависящие от реализации тесты сохраняются вместе с реализацией, поэтому очевидно, что тест должен изменяться при изменении реализации.

0 голосов
/ 12 февраля 2009

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

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

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