Я читал, что использование «нового» в конструкторе (для любых других объектов, кроме простых значений) является плохой практикой, так как делает невозможным модульное тестирование (так как тогда эти коллаборационисты тоже должны быть созданы и не могут быть смоделированы). Поскольку я не очень опытен в модульном тестировании, я пытаюсь собрать некоторые правила, которые я изучу в первую очередь. Кроме того, действительно ли это правило действует независимо от используемого языка?
unit-testing
constructors
Эзоэла Вакка
источник
источник
new
означает разные вещи на разных языках.new
ключевое слово. На каких языках вы спрашиваете?Ответы:
Есть всегда исключения, и я беру вопрос с «всегда» в названии, но да, это руководство , как правило , действует, а также применяется вне конструктора , а также.
Использование new в конструкторе нарушает D в SOLID (принцип инверсии зависимостей). Это затрудняет тестирование вашего кода, потому что модульное тестирование - это изоляция; трудно выделить класс, если у него есть конкретные ссылки.
Однако речь идет не только о модульном тестировании. Что если я хочу указать хранилище сразу на две разные базы данных? Возможность передачи в моем собственном контексте позволяет мне создавать два разных репозитория, указывающих на разные места.
Отсутствие нового в конструкторе делает ваш код более гибким. Это также относится к языкам, которые могут использовать конструкции, отличные от
new
инициализации объекта.Тем не менее, ясно, что вам нужно использовать здравый смысл. Есть много раз, когда это хорошо использовать
new
, или где было бы лучше не делать, но у вас не будет негативных последствий. В какой-то момент, где-то,new
должен быть вызван. Просто будьте очень осторожны при вызовеnew
внутри класса, от которого зависит множество других классов.Делать что-то, например, инициализировать пустую частную коллекцию в конструкторе, - это хорошо, а вставлять это было бы абсурдом.
Чем больше ссылок на него имеет класс, тем осторожнее вы не должны вызывать
new
его изнутри.источник
head = null
за какого-либо улучшения кода? Почему лучше оставить включенные коллекции как нулевые и создавать их по требованию, чем в конструкторе?Хотя я поддерживаю использование конструктора для простой инициализации нового экземпляра, а не для создания нескольких других объектов, вспомогательные объекты в порядке, и вы должны использовать свое суждение относительно того, является ли что-то таким внутренним помощником или нет.
Если класс представляет коллекцию, то он может иметь внутренний вспомогательный массив или список или хэш-набор. Он будет использовать
new
для создания этих помощников, и это будет считаться вполне нормальным. Класс не предлагает инъекцию для использования различных внутренних помощников и не имеет оснований для этого. В этом случае вы хотите протестировать открытые методы объекта, которые могут быть направлены на накопление, удаление и замену элементов в коллекции.В некотором смысле конструкция класса языка программирования является механизмом создания абстракций более высокого уровня, и мы создаем такие абстракции, чтобы преодолеть разрыв между проблемной областью и примитивами языка программирования. Однако механизм классов - это всего лишь инструмент; это зависит от языка программирования, и для некоторых доменных абстракций в некоторых языках просто требуется несколько объектов на уровне языка программирования.
Таким образом, вы должны принять определенное решение о том, требует ли абстракция одного или нескольких внутренних / вспомогательных объектов, в то время как вызывающая сторона по-прежнему рассматривается вызывающей стороной как отдельная абстракция, или о том, будут ли другие объекты лучше отображаться вызывающей стороне для создания для контроль зависимостей, который будет предлагаться, например, когда вызывающая сторона видит эти другие объекты при использовании класса.
источник
Не все соавторы достаточно интересны для отдельного модульного тестирования, вы можете (косвенно) протестировать их через класс хостинга / создания экземпляров. Это может не совпадать с идеей некоторых людей о необходимости тестирования каждого класса, каждого открытого метода и т. Д., Особенно при выполнении теста после. При использовании TDD вы можете реорганизовать этого «соавтора», извлекая класс, в котором он уже полностью тестируется из вашего первого процесса тестирования.
источник
Not all collaborators are interesting enough to unit-test separately
конец истории :-), этот случай возможен, и никто не осмелился упомянуть. @Joppe Я призываю вас немного уточнить ответ. Например, вы можете добавить несколько примеров классов, которые являются простыми деталями реализации (не пригодными для замены) и как их можно извлечь последним, если мы сочтем это необходимым.new File()
что-либо связанное с файлом, нет смысла запрещать этот вызов. Что вы собираетесь делать, писать регрессионные тесты дляFile
модуля stdlib ? Скорее всего, не. С другой стороны, призывnew
к самодовольному классу более сомнителен.new File()
, хотя.Внимательно изучайте «правила» для проблем, с которыми вы никогда не сталкивались. Если вы сталкиваетесь с каким-то «правилом» или «наилучшей практикой», я бы предложил найти простой игрушечный пример того, как «правило» следует использовать, и попытаться решить эту проблему самостоятельно , игнорируя то, что говорит «правило».
В этом случае вы можете попытаться создать 2 или 3 простых класса и некоторые варианты поведения, которые они должны реализовать. Реализуйте классы любым удобным для вас способом и напишите модульный тест для каждого поведения. Составьте список всех проблем, с которыми вы столкнулись, например, если вы начали с того, что все работает в одну сторону, то вам нужно было вернуться и изменить это позже; если вы запутались в том, как вещи должны соответствовать друг другу; если вас раздражает написание шаблона; и т.п.
Затем попробуйте решить ту же проблему, следуя «правилу». Опять же, составьте список проблем, с которыми вы столкнулись. Сравните списки и подумайте, какие ситуации могут быть лучше при соблюдении правила, а какие нет.
Что касается вашего реального вопроса, я склоняюсь к подходу портов и адаптеров , где мы проводим различие между «базовой логикой» и «услугами» (это похоже на различие между чистыми функциями и эффективными процедурами).
Основная логика заключается в том, чтобы вычислять вещи «внутри» приложения на основе проблемной области. Он может содержать классы , как
User
,Document
,Order
,Invoice
, штраф и т.д. Это иметь базовые классы называтьnew
для других основных классов, так как они «внутренней» детали реализации. Например, созданиеOrder
может также создатьInvoice
иDocument
детализацию того, что было заказано. Во время тестов нет нужды насмехаться над этим, потому что это именно то, что мы хотим протестировать!Порты и адаптеры - это то, как ядро логики взаимодействует с внешним миром. Это где вещи , как
Database
,ConfigFile
,EmailSender
и т.д. жить. Это то, что усложняет тестирование, поэтому желательно создавать их вне базовой логики и передавать их по мере необходимости (либо с помощью внедрения зависимости, либо в качестве аргументов метода и т. Д.).Таким образом, основная логика (которая является частью конкретного приложения, где важна бизнес-логика и подвержена наибольшему оттоку) может быть проверена сама по себе, без необходимости заботиться о базах данных, файлах, электронных письмах и т. Д. Мы можем просто передать некоторые примерные значения и проверить, что получаем правильные выходные значения.
Порты и адаптеры можно тестировать отдельно, используя макеты для базы данных, файловой системы и т. Д., Не заботясь о бизнес-логике. Мы можем просто передать некоторые примеры значений и убедиться, что они хранятся / читаются / отправляются / и т.д. соответственно.
источник
Позвольте мне ответить на вопрос, собрав здесь то, что я считаю ключевыми. Я приведу некоторых пользователей для краткости.
Да, использование
new
внутренних конструкторов часто приводит к недостаткам конструкции (например, к жесткой связи), что делает нашу конструкцию жесткой. Трудно проверить да, но не невозможно. Собственность в игре здесь - устойчивость (терпимость к изменениям) 1 .Тем не менее приведенная цитата не всегда верна. В некоторых случаях могут быть классы , которые должны быть тесно связаны . Дэвид Арно прокомментировал пару.
В точку. Некоторые классы (например, inner-классы) могут быть просто деталями реализации основного класса. Они предназначены для тестирования вместе с основным классом, и они не обязательно заменяемы или расширяемы.
Более того, если наш культ SOLID заставляет нас извлекать эти классы , мы можем нарушать еще один хороший принцип. Так называемый Закон Деметры . Что, с другой стороны, мне кажется очень важным с точки зрения дизайна.
Таким образом, вероятный ответ, как обычно, зависит . Использование
new
внутренних конструкторов может быть плохой практикой. Но не всегда систематически.Итак, нам нужно оценить, являются ли классы деталями реализации (в большинстве случаев они не будут) основного класса. Если они есть, оставьте их в покое. Если это не так, рассмотрите такие методы, как корень композиции или внедрение зависимостей с помощью контейнеров IoC .
1: основная цель SOLID - не сделать наш код более тестируемым. Это делает наш код более терпимым к изменениям. Более гибкий и, следовательно, легче тестировать
Примечание: Дэвид Арно, TheWhisperCat, надеюсь, вы не возражаете, я вас процитировал.
источник
В качестве простого примера рассмотрим следующий псевдокод
Поскольку
new
это чистая реализация деталейFoo
, и то и другоеFoo::Bar
иFoo::Baz
являются частьюFoo
, при модульном тестированииFoo
нет смысла в насмешливых частяхFoo
. Вы только издеваетесь над деталями снаружиFoo
при юнит-тестированииFoo
.источник
Да, использование 'new' в корневых классах вашего приложения - это запах кода. Это означает, что вы привязываете класс к использованию определенной реализации и не сможете заменить другую. Всегда выбирайте внедрение зависимости в конструктор. Таким образом, вы не только сможете легко внедрить ложные зависимости во время тестирования, но это сделает ваше приложение гораздо более гибким, позволяя вам при необходимости быстро заменять различные реализации.
РЕДАКТИРОВАТЬ: Для downvoters - вот ссылка на книгу разработки программного обеспечения, помечающую «новый» как возможный запах кода: https://books.google.com/books?id=18SuDgAAQBAJ&lpg=PT169&dq=new%20keyword%20code%20smell&pg=PT169 # v = OnePage & д = новый% 20keyword% 20code% 20smell & F = ложь
источник
Yes, using 'new' in your non-root classes is a code smell. It means you are locking the class into using a specific implementation, and will not be able to substitute another.
Почему это проблема? Не каждая отдельная зависимость в дереве зависимостей должна быть открыта для заменыnew
ключевого слова не плохая практика, и никогда не было. Важно то, как вы используете инструмент. Вы не используете кувалду, например, там, где достаточно шарикового молотка.