#java #unit-testing #junit #tdd
#java #модульное тестирование #junit #tdd
Вопрос:
Мне пришлось реализовать некоторый код для обхода структуры каталогов и возврата списка найденных файлов. Требования были довольно простыми:
- Учитывая базовый каталог, найдите в нем все файлы (которые сами не являются каталогами).
- Если каталог найден, повторите для него шаг 1.
Я хотел разработать код с использованием TDD. Когда я начал писать тесты, я понял, что я издевался над классом File
, чтобы я мог перехватывать вызовы File.isDirectory()
и так далее. Таким образом, я заставлял себя использовать решение, в котором я вызывал бы этот метод.
Мне это не понравилось, потому что этот тест определенно тесно связан с реализацией. Если я когда-либо изменю способ запроса, является ли файл каталогом, то этот тест завершится неудачей, даже если я продолжу выполнять контракт. Рассматривая это как частный модульный тест, я почувствовал себя неловко по всем причинам, изложенным в этом посте. Я не уверен, что это один из тех случаев, когда мне нужно использовать такого рода тестирование. С другой стороны, я действительно хочу быть уверен, что он возвращает каждый файл, который также не является каталогом, обходя всю структуру. На мой взгляд, для этого требуется хороший, простой тест.
Я хотел избежать необходимости создавать структуру каталогов тестирования с реальными тестируемыми файлами «на диске», поскольку я увидел, что это довольно неуклюже и противоречит некоторым из лучших практик, которые я прочитал.
Имейте в виду, что мне не нужно ничего делать с содержимым, поэтому такие приемы, как использование StringReader
вместо FileReader
здесь неприменимы. Я подумал, что мог бы сделать что-то эквивалентное, например, иметь возможность создавать структуру каталогов в памяти при настройке теста, а затем удалять ее. Я не нашел способа сделать это.
Как бы вы разработали этот код с использованием TDD?
Спасибо!
Комментарии:
1. Одним из вариантов является Commons VFS , но обычно я просто использую «реальный» доступ к файлам, а в моих тестах используется специальный каталог для тестирования.
2. @DavidWallace вы должны указать это в качестве ответа.
3. Спасибо @DavidWallace! Я предполагаю, что Commons VFS может быть излишеством для моих целей. Наличие «реальной» файловой структуры упрощает тестирование и делает его гораздо менее связанным с реализацией. Я попробую это!
4. Да, я обнаружил, что использование реальных файлов работает хорошо. Однако есть несколько ошибок. Вы действительно хотите предоставить каждому методу тестирования свой собственный подкаталог, чтобы исключить возможность взаимодействия тестов друг с другом. И убедитесь, что тест очищает свой собственный каталог КАК до, так и после запуска, на случай, если он завершился на полпути при предыдущем запуске теста и оставил какие-то ненужные файлы разбросанными.
Ответ №1:
Ошибка, которую вы допустили, заключается в издевательстве File
. Существует антишаблон тестирования, который предполагает, что если ваш класс делегирует class X
, вы должны создать макет class X
, чтобы протестировать свой класс. Существует также общее правило проявлять осторожность при написании модульных тестов, которые выполняют файловый ввод-вывод, поскольку они, как правило, слишком медленные. Но в модульных тестах нет абсолютного запрета на ввод-вывод файлов.
В ваших модульных тестах настройте и удалите временный каталог и создайте тестовые файлы и каталоги в этом временном каталоге. Да, ваши тесты будут медленнее, чем тесты чистого процессора, но они все равно будут быстрыми. В JUnit даже есть код поддержки, помогающий в этом самом сценарии: @Rule
на TemporaryFolder
.
Только на этой неделе я реализовал, используя TDD, некоторый служебный код, который должен был сканировать каталог и удалять файлы, поэтому я знаю, что это работает.
Ответ №2:
Как человек, который очень беспокоится о модульных тестах, выполнение которых занимает больше нескольких миллисекунд, я настоятельно рекомендую отключить файловый ввод-вывод.
Однако я не думаю, что вам следует издеваться над File
классом напрямую. Вместо этого посмотрите на ваше использование File
класса как на «как» и попытайтесь определить «что». Затем кодифицируйте это с помощью интерфейса.
Например: вы упомянули, что одна из вещей, которые вы делаете, — это перехватывать вызовы File.isDirectory
. Вместо взаимодействия с File
классом, что, если бы ваш код взаимодействовал с некоторой реализацией интерфейса, такого как:
public interface FileSystemNavigator {
public boolean isDirectory(String path);
// ... other relevant methods
}
Это скрывает использование File.isDirectory
от остальной части вашего кода, одновременно переформулируя проблему во что-то более актуальное для вашей программы.