Иногда объекты просто должны быть тесно связаны. Например, CsvFile
класс, вероятно, должен будет тесно работать с CsvRecord
классом (или ICsvRecord
интерфейсом).
Однако из того, что я узнал в прошлом, одним из основных принципов разработки, основанной на тестировании, является «Никогда не тестируйте более одного класса за раз». Это означает, что вы должны использовать ICsvRecord
макеты или заглушки, а не фактические экземпляры CsvRecord
.
Однако, попробовав такой подход, я заметил, что издевательство над CsvRecord
классом может стать немного волосатым. Что приводит меня к одному из двух выводов:
- Трудно писать модульные тесты! Это кодовый запах! Refactor!
- Копировать каждую зависимость просто неразумно.
Когда я заменил свои издевательства на настоящие CsvRecord
экземпляры, все пошло гораздо более гладко. Когда я искал мысли других людей, я наткнулся на этот пост в блоге , который, кажется, поддерживает № 2 выше. Для объектов, которые естественно тесно связаны, мы не должны сильно беспокоиться о насмешках.
Я не в курсе? Есть ли недостатки в предположении № 2 выше? Должен ли я на самом деле думать о рефакторинге моего дизайна?
Ответы:
Если вам действительно нужна координация между этими двумя классами, напишите
CsvCoordinator
класс, который инкапсулирует ваши два класса, и проверьте это.Тем не менее, я оспариваю понятие,
CsvRecord
которое не может быть проверено независимо.CsvRecord
это в основном класс DTO , не так ли? Это просто набор полей, возможно с парой вспомогательных методов. ИCsvRecord
может использоваться в других контекстах, кроме тогоCsvFile
; Вы можете иметь коллекцию или массивCsvRecord
s, например.Проверьте
CsvRecord
сначала. Убедитесь, что он проходит все свои тесты. Затем продолжайте и используйтеCsvRecord
с вашимCsvFile
классом во время теста. Используйте его в качестве предварительно протестированной заглушки / макета; заполните его соответствующими тестовыми данными, передайте егоCsvFile
и напишите свои тестовые примеры на этот счет .источник
CsvRecord
перерывы, то, очевидно,CsvData
не получается; но это нормально, потому что выCsvRecord
сначала тестируете , а если это не удается, вашиCsvFile
тесты не имеют смысла. Вы все еще можете различать ошибки вCsvRecord
и вCsvFile
.Причина тестирования по одному классу за раз заключается в том, что вы не хотите, чтобы тесты для одного класса имели зависимости от поведения второго класса. Это означает, что если ваш тест для класса A выполняет какие-либо функции класса B, то вам следует смоделировать класс B, чтобы удалить зависимость от конкретной функции в классе B.
Класс вроде
CsvRecord
мне кажется, что он в основном предназначен для хранения данных - это не класс с слишком большой функциональностью. То есть у него могут быть конструкторы, геттеры, сеттеры, но нет методов с реальной содержательной логикой. Конечно, я предполагаю здесь - возможно, вы написали класс с именем,CsvRecord
который выполняет многочисленные сложные вычисления.Но если у
CsvRecord
него нет собственной логики, то нечего его высмеивать. Это на самом деле просто старая изречение - «не издевайтесь над объектами значения» .Поэтому при рассмотрении вопроса о том, следует ли имитировать конкретный класс (для теста другого класса), вы должны учитывать, сколько его собственной логики имеет этот класс, и сколько этой логики будет выполнено в ходе вашего теста.
источник
№ 2 в порядке. Вещи могут быть и должны быть тесно связаны, если их концепции тесно связаны. Это должно быть редко, и обычно его следует избегать, но в приведенном вами примере это имеет смысл.
источник
«Связанные» классы взаимозависимы друг от друга. Это не должно относиться к тому, что вы описываете - CsvRecord на самом деле не должен заботиться о CsvFile, содержащем его, поэтому зависимость идет только в одну сторону. Это нормально, и не жесткая связь.
В конце концов, если класс содержит переменную String name, вы бы не утверждали, что она тесно связана со String, не так ли?
Итак, юнит-тест CsvRecord для его желаемого поведения.
Затем используйте макет (Mockito отлично), чтобы проверить, взаимодействует ли ваше подразделение с объектами, от которых оно зависит. Поведение, которое вы хотите протестировать, действительно - это CsvFile обрабатывает CsvRcords ожидаемым образом. Внутренняя работа CvsRecord не должна иметь значения - это то, как CvsFile взаимодействует с ним.
Наконец, TDD касается не только модульных тестов. Вы, конечно, можете (и должны) начать с функциональных тестов, которые смотрят на функциональное поведение того, как работают ваши более крупные компоненты, то есть ваша пользовательская история или сценарий. Ваши юнит-тесты устанавливают ожидания и проверяют части, функциональные тесты делают то же самое для всего.
источник
CsvFile
он тесно связанCsvRecord
(но не наоборот). OP спрашивает , если это хорошая идея , чтобы проверитьCsvFile
путем отделения его отCsvRecord
ПОСРЕДСТВОМICsvRecord
, а не наоборот.CsvFile
зависит от внутренней работыCsvRecord
, то есть от количества предположений, которые файл имеет относительно записи. Интерфейсы помогают документировать и применять такие допущения (или, скорее, отсутствие других допущений), но степень связывания остается неизменной, за исключением того, что с интерфейсом вы можете подключить другой класс записейCsvFile
. Представлять интерфейс просто так, чтобы вы могли сказать, что у вас снижена связь, глупо.Здесь действительно два вопроса. Во-первых, если существуют ситуации, когда насмешка над объектом нецелесообразна. Это, несомненно, правда, как показывают другие отличные ответы. Второй вопрос заключается в том, является ли ваш конкретный случай одной из таких ситуаций. В этом вопросе я не убежден.
Вероятно, наиболее распространенная причина не насмехаться над классом, если это класс значения. Тем не менее, вы должны посмотреть на причину этого правила. Это не потому, что высмеиваемый класс будет как-то плохим, а потому, что он будет по сути идентичным оригиналу. Если бы это было так, ваше модульное тестирование было бы легче с использованием исходного класса.
Вполне возможно, что ваш код является одним из редких исключений, когда рефакторинг не поможет, но вы должны объявлять его таковым только после того, как усердные усилия по рефакторингу не сработали. Даже опытные разработчики могут иметь проблемы с поиском альтернативы своему дизайну. Если вы не можете придумать какой-либо возможный способ улучшить его, попросите кого-нибудь опытного взглянуть на него еще раз.
Большинство людей, кажется, предполагают, что вы относитесь к
CsvRecord
классу ценности. Попробуйте сделать это один. Сделайте его неизменным, если можете. Если у вас есть два объекта с указателями друг на друга, удалите один из них и выясните, как заставить его работать. Ищите места для разделения классов и функций. Лучшее место для разделения класса не всегда совпадает с физическим расположением файла. Попробуйте поменять отношения родитель / потомок классов. Может быть, вам нужен отдельный класс для чтения и записи CSV-файлов. Возможно, вам нужны отдельные классы для обработки файлового ввода-вывода и интерфейса с верхними уровнями. Есть много вещей, чтобы попробовать, прежде чем объявить это нерефрактивным.источник