Как выполнить модульное тестирование сохранения файла на диск? - PullRequest
21 голосов
/ 01 августа 2010

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

Ответы [ 3 ]

40 голосов
/ 01 августа 2010

Мой подход к этому сильно смещен в Растущем объектно-ориентированном программном обеспечении, основанном на тестах (GOOS), которую я только что прочитал, но это лучшее, что я знаю сегодня. В частности:

  • Создайте интерфейс для абстрагирования файловой системы от вашего кода. Макет, где этот класс нужен как соавтор / зависимость. Это позволяет быстро выполнять юнит-тесты и обратную связь.
  • Создание интеграционных тестов, которые проверяют фактическую реализацию интерфейса. то есть убедитесь, что вызов Save () действительно сохраняет файл на диск и содержит содержимое записи (используйте справочный файл или проанализируйте его для нескольких вещей, которые он должен содержать)
  • Создать приемочный тест, который тестирует всю систему - от начала до конца. Здесь вы можете просто проверить, что файл создан - цель этого теста - подтвердить, правильно ли подключена / подключена реальная реализация.

Обновление для комментатора:

Если вы читаете структурированные данные (например, объекты Book) (если не подставлять строку для IEnumerable)

interface BookRepository
{
  IEnumerable<Books> LoadFrom(string filePath);
  void SaveTo(string filePath, IEnumerable<Books> books);
}

Теперь вы можете использовать конструктор-инъекцию, чтобы внедрить макет в клиентский класс. Клиентский класс модульные тесты поэтому быстр; не попадать в файловую систему . Они просто проверяют, что на зависимостях вызываются правильные методы (например, Load / Save)

var testSubject = new Client(new Mock<BookRepository>.Object);

Далее необходимо создать реальную реализацию BookRepository, которая работает с файлом (или завтра с базой данных Sql, если вы этого хотите). Никто не должен знать. Запишите интеграционные тесты для FileBasedBookRepository (который реализует указанную выше роль) и проверьте, что вызов Load with reference file дает нужные объекты и вызов Save с известным списком, сохраняет их на диск. Т.е. использует реальные файлы Эти тесты будут медленными, поэтому пометьте их тегом или переместите в отдельный набор.

[TestFixture]
[Category("Integration - Slow")]
public class FileBasedBookRepository 
{
  [Test]
  public void CanLoadBooksFromFileOnDisk() {...}
  [Test]
  public void CanWriteBooksToFileOnDisk() {...}
}

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

6 голосов
/ 01 августа 2010

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

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

3 голосов
/ 05 июля 2014

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

В ваших модульных тестах есть временный каталог, настроенный и снесенный, и создайте тестовые файлы и каталоги в этом временном каталоге.Да, ваши тесты будут медленнее, чем тесты на чистом процессоре, но все равно будут быстрыми.У JUnit даже есть код поддержки, чтобы помочь с этим самым сценарием: @Rule на TemporaryFolder.

Тем не менее, большинство кодов для записи файлов принимает эту форму:

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

Только первая действительно имеет дело с файловой системой.Остальное просто использует выходной поток.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...