Как выполнить единичный тест сохранения файла на диск?

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

Ответ 1

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

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

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

Если вы читаете структурированные данные (например, объекты книги) (если не заменить строку для 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 DB завтра, если вы этого захотите). Никто больше не должен знать. Напишите тесты интеграции для FileBasedBookRepository (который реализует вышеприведенную роль) и протестируйте, что вызов Load с ссылочным файлом дает правильные объекты и вызывает Save с известным списком, сохраняется на диске. то есть использует реальные файлы. Эти тесты будут медленными, поэтому отметьте их тегом или переместите его в отдельный набор.

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

Наконец, должно быть одно или несколько приемочных тестов , которые выполняют Load и Save.

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

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