Вы бы предпочли сделать приватные вещи внутренними / публичными для тестов или использовать какой-нибудь хак, такой как PrivateObject?

26

Я довольно новичок в тестировании кода и assertраньше был шлюхой. В модульном тестировании меня беспокоит то, что от вас часто требуется создавать public(или, по крайней мере internal) поля, которые были бы в privateпротивном случае, отменять readonlyих, создавать вместо них privateметоды protected virtualи т. Д.

Недавно я обнаружил, что вы можете избежать этого, используя такие вещи, как класс PrivateObject, чтобы получить доступ к чему-либо в объекте с помощью отражения. Но это делает ваши тесты менее удобными для сопровождения (что-то не получится во время выполнения, а не во время компиляции, оно будет сломано простым переименованием, сложнее отладить ...). Каково ваше мнение по этому поводу? Каковы лучшие практики в модульном тестировании относительно ограничения доступа?

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

Zonko
источник
2
У людей питона есть только публика. Они счастливы. Зачем волноватся? Почему бы не сделать все публичным?
С.Лотт
12
Потому что это далеко от лучших практик в большинстве топовых языков. Реальный ответ - использовать хорошие интерфейсы и тому подобное, чтобы вы могли использовать фиктивные объекты для проведения тестирования.
Рог
2
@Rig: Python не лучший язык? Интересный.
С.Лотт
7
«это далеко от лучших практик в большинстве топовых языков», логически не подразумевает (или даже не ссылается на это) «Python не является топовым языком».
Майк Накис
1
Точнее, это топовый язык, но большинство топовых языков не являются Python и рекомендуют устанавливать соответствующую область видимости. Я поддерживаю мое заявление. Существуют шаблоны, разработанные для создания тестируемого программного обеспечения, обеспечивая при этом переменные, поддерживающие область действия. Даже программисты на Python стремятся эмулировать область видимости с префиксами из того, что я видел.
Рог

Ответы:

36

Вы никогда не должны

сделать public(или, по крайней мере internal) поля, которые были бы в privateпротивном случае, чтобы снять readonlyих, вместо этого сделать privateметодыprotected virtual

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

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

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

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

Майк Накис
источник
2
В точку! Если вы не можете выполнить юнит-тестирование без размышлений или взломов, возможно, вы ошиблись в своем API или в своем дизайне.
Сокол
Вы говорите, что создание privateметодов, protected virtualпомогающих с насмешками в тестах, считается плохой практикой?
Робула
1
@Robula В моей книге, да. Я даже не пишу свои тесты в том же пакете, что и производственный код, потому что у меня нет возможности получать доступ к закрытым членам. Я тоже не пользуюсь издевательствами. Конечно, если вам нужно использовать макеты, вам придется делать все возможное, чтобы облегчить имитацию, поэтому защищенная виртуальная среда может быть практическим компромиссом во многих ситуациях. Но в идеале дизайн не нуждается в макетах, только в альтернативных реализациях, и в идеале тестирование - это тестирование по принципу черного ящика, а не тестирование по принципу белого ящика.
Майк Накис
13

В C # вы можете использовать, InternalsVisibleToAttributeчтобы позволить вашей тестовой сборке видеть internalклассы в тестируемой сборке . Похоже, вы уже знаете это.

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

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

Скотт Уитлок
источник
8

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

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

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

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

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

flamingpenguin
источник
6

Лично я предпочитаю оставлять код, который я тестирую, настолько чистым, насколько я могу, и если тестируемый код нуждается во взломах (и в неприятных указателях C ++ и т.д.), я счастлив сделать это

Муравей
источник
1
Я согласен, это способ сделать это. Сохраняйте уродство в тестовом коде, где оно не может повредить.
Алан Делимон
@AlanDelimon Может быть, это не повредит уму последующих программистов обслуживания?
пламенеющий пингвин
1
уродливый код @flamingpenguin может повредить программистам где угодно; но уродливый тестовый код, скорее всего, принесет меньше вреда, поскольку затронет только людей, изменяющих класс, а не людей, которые его просто потребляют.
Дэн Нили
2
@flamingpenguin Возможно, но если вам нужно куда-то поместить некрасивый код, он также может быть в модульном тесте, где с меньшей вероятностью потребуется модификация и он будет частью приложения.
Алан Делимон
5

Видимость вашего кода не имеет ничего общего с вашими модульными тестами!

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

Существует множество способов разрешить доступ к скрытым (частным или внутренним) методам и свойствам. Многие тестовые фреймворки и IDE (например, Visual Studio) поддерживают вас здесь, генерируя все необходимое для доступа, вам нужно только создать свои тестовые случаи. Так зачем волноваться? Лучше держать ваш код в чистоте и порядке.

Александр Галкин
источник