Как избежать необходимости модульного тестирования приватных методов


15

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

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

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

Я сумасшедший?


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

Ответы:


24

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

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

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

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


1
«Не издевайся над своими личными методами», да, я могу понять тестирование, когда ты издеваешься над ними, ты, возможно, перешагнул через тонкую грань сумасшествия
Эван

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

8
@FranSevillano Если вам нужно так много заглянуть или посмеяться, я бы посмотрел ваш общий дизайн. Что-то чувствует себя плохо.
Томас Оуэнс

Инструмент покрытия кода помогает в этом.
Энди

5
@FranSevillano: У класса не так много веских причин, чтобы сделать одну вещь, чтобы иметь массу зависимостей. Если у вас тонны зависимостей, у вас, вероятно, есть класс Бога.
Mooing Duck

4

Я думаю, что это очень плохая идея.

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

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

Кроме того, ключевое различие между публичными и частными участниками заключается в том, что вы должны тщательно продумывать свой публичный API, хорошо документировать его и тщательно проверять (утверждения и т. Д.). Но с частным API было бы бессмысленно тщательно обдумывать это (потраченные впустую усилия, поскольку его использование настолько локализовано). Внедрение частных методов в модульные тесты означает создание внешних зависимостей от этих методов. Это означает, что API должен быть стабильным и хорошо документированным (поскольку кто-то должен выяснить, почему эти модульные тесты не пройдены, если / когда они это делают).

Я предлагаю тебе:

  • ПЕРЕСМОТРИТЕ, должны ли эти методы быть частными
  • Напишите одноразовые тесты (просто чтобы убедиться, что они верны, временно сделайте их общедоступными, чтобы вы могли протестировать, а затем удалить тест)
  • Встроенное условное тестирование в вашей реализации с использованием #ifdef и утверждений (тесты выполняются только в отладочных сборках).

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


6
Я не согласен. ИМО, это на 100% правильно для интеграционных тестов. Но с юнит-тестами все иначе; Цель юнит-тестирования состоит в том, чтобы точно определить, где находится ошибка, достаточно узкая, чтобы вы могли быстро ее исправить. Я часто оказываюсь в такой ситуации: у меня очень мало публичных методов, потому что это действительно основная ценность моих занятий (как вы и сказали). Однако я также избегаю написания 400 строковых методов, ни публичных, ни частных. Поэтому мои несколько открытых методов могут достичь своей цели только с помощью десятков частных методов. Это слишком много кода, чтобы «быстро это исправить». Я должен запустить отладчик и т.д ..
marstato

6
@marstato: предложение: сначала начните писать тесты и передумайте насчет юнит-тестов: они не находят ошибок, но проверяют, что код работает так, как задумал разработчик.
Тимоти Траклле

@marstato Спасибо! Да. Тесты всегда проходят в первый раз, когда вы регистрируете вещи (иначе вы бы не зарегистрировались!). Они полезны, так как вы развиваете код, предупреждаете, когда вы что-то ломаете, и если у вас ХОРОШИЕ регрессионные тесты, то они дают вам КОМФОРТ / РУКОВОДСТВО о том, где искать проблемы (не в том, что стабильно). и регрессия хорошо проверена).
Льюис Прингл

@marstato «Цель юнит-тестирования состоит в том, чтобы точно определить, где ошибка» - именно это недоразумение приводит к вопросу ОП. Целью модульного тестирования является проверка предполагаемого (и предпочтительно документированного) поведения API.
StackOverthrow

4
@marstato Название «интеграционный тест» происходит от тестирования того, что несколько компонентов работают вместе (т.е. они интегрируются должным образом). Модульное тестирование - это тестирование одного компонента, который делает то, что он должен делать изолированно, что в основном означает, что его открытый API работает так, как задокументировано / требуется. Ни в одном из этих терминов ничего не говорится о том, включаете ли вы тесты внутренней логики реализации как часть гарантии того, что интеграция работает, или что отдельный модуль работает.
Бен

3

UnitTests проверяет общедоступное наблюдаемое поведение , а не код, где «публичный» означает: возвращаемые значения и связь с зависимостями.

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

Основная причина, по которой вы не хотите тестировать private methods: они являются деталями реализации, и вы можете изменить их во время рефакторинга (улучшите свой код, применяя OO-принципы без изменения функциональности). Это как раз тогда, когда вы не хотите, чтобы ваши юнит-тесты изменились, чтобы они могли гарантировать, что поведение вашего CuT не изменилось во время рефакторинга.

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

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

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

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


1

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

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

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

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

В этом случае, что произойдет, если вы включите тестирование частных методов? Понятно, что это не имеет смысла.

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


Я борюсь с этим в том смысле, что я смоделирую зависимости так, что, если какая-либо зависимость изменит их поведение, мой тест не провалится. Предполагается ли этот сбой в интеграционном тесте, а не в модульном тесте?
Тодд

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

1

Прежде чем ответить на такой вопрос, вам нужно решить, чего вы на самом деле хотите достичь.

Вы пишете код. Вы надеетесь, что он выполнит свой контракт (другими словами, он делает то, что должен делать. Записать, что он должен делать, для некоторых людей - огромный шаг вперед).

Чтобы быть уверенным в том, что код выполняет то, что он должен делать, вы либо пристально смотрите на него, либо пишете тестовый код, который проверяет достаточно случаев, чтобы убедить вас, «если код пройдет все эти тесты, тогда он верен».

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

Но вы создаете библиотеку. Заставить его работать правильно может быть трудно достичь. Допустим, меня заботит только то, что библиотека выполняет операцию X правильно, поэтому у меня есть модульный тест для X. Вы, разработчик, отвечающий за создание библиотеки, реализуете X, комбинируя шаги A, B и C, каждый из которых абсолютно нетривиален. Чтобы ваша библиотека работала, вы добавляете тесты, чтобы убедиться, что A, B и C работают правильно. Вы хотите эти тесты. Говорить «у вас не должно быть юнит-тестов для частных методов» - совершенно бессмысленно. Вы хотите тесты для этих частных методов. Может быть, кто-то скажет вам, что юнит-тестирование приватных методов неверно. Но это только означает, что вы можете называть их не «модульными тестами», а «частными тестами» или как вы их называете.

Язык Swift решает проблему, заключающуюся в том, что вы не хотите выставлять A, B, C как публичные методы только потому, что вы хотите протестировать его, предоставив функциям атрибут «testable». Компилятор позволяет вызывать закрытые тестируемые методы из модульных тестов, но не из не тестового кода.


0

Да, ты сумасшедший .... как лиса!

Есть несколько способов тестирования приватных методов, некоторые из которых зависят от языка.

  • отражение! правила созданы для того, чтобы их нарушали!
  • сделать их защищенными, наследовать и переопределять
  • друг / InternalsVisibleTo классы

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


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

Я думал, что я покрыл все основания :(
Эван

3
Я проголосовал, потому что идея выставить ваши приватные вспомогательные методы для публичного API просто для того, чтобы проверить их, безумна, и я всегда так думал.
Роберт Харви

0

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

Я добавляю дополнительный internalаксессор (вместе с флагом InternalsVisibleToтестовой сборки) с четким именем DoSomethingForTesting(parameters), чтобы протестировать эти «приватные» методы.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.