Модульное тестирование класса, использующего файловую систему

У меня есть класс, который выводит простой файл отчета. Он считывает некоторые идентификационные номера записей из файла XML: каждый из них используется для поиска соответствующей записи, хранящейся в базе данных. Затем он записывает детали каждой записи в файл CSV.

Я задаюсь вопросом - каков наилучший способ организовать его так, чтобы его было легко тестировать, но при этом следовать принципам инкапсуляции? Я понимаю, что лучше избегать взаимодействия с файловой системой, если это не является абсолютно необходимым, поэтому я имею дело с объектами Stream. При модульном тестировании я могу использовать частичные фиктивные объекты для переопределения битов, которые читают или записывают в файлы.

Я также не уверен, когда и где располагать потоки, не усложняя юнит-тестирование. Похоже, мне, возможно, придется выставить потоки для модульного теста.

Мой проект использует NHibernate для доступа к данным, Spring .NET для внедрения зависимостей и Rhino.Mocks для модульного тестирования.

В настоящее время у меня есть что-то похожее на это:

public class ReportBiz
{
    //Access to repository, populated by Spring
    internal ICardRequestDAO CardRequestData { get;set;} 

    //This normally returns a FileStream containing data from the XML file. When testing this is overridden by using a Rhino.Mocks partial mock and returns a MemoryStream
    internal virtual Stream GetSourceStream()
    {
        //Load file and return a Stream
        ...
    }

    //This normally returns a FileStream where CSV data is saved. When testing this is overridden by using a Rhino.Mocks partial mock and returns a MemoryStream
    internal virtual Stream GetOutputStream()
    {
        //Create file where CSV data gets saved and return a Stream
        ...
    }

    public void CreateReportFile()
    {
        Stream sourceStream = GetSourceStream();
        ...

        //Create an XmlDocument instance using the stream
        //For each XML element, get the corresponding record from the database
        //Add record data to CSV stream     
        ...
    }
    }

Было бы лучше использовать какую-то собственную фабрику или что-то в этом роде и передавать потоки в конструктор? Но что делать, если задействована некоторая бизнес-логика, например, имя файла определяется на основе результатов запроса?

Или все дело в доступе к файлам не проблема?

Извиняюсь, если я упускаю что-то очевидное. Буду благодарен за любые советы.

2 ответа

Решение

Самый простой способ сделать доступ к файлу макетным, сохранив при этом контроль над жизненным циклом одноразовых ресурсов, - ввести StreamFactory в ваш класс:

public class ReportBiz {

    private IStreamFactory streamFactory;

    public ReportBiz(IStreamFactory streamFactory) {
        this.streamFactory = streamFactory
    }

    public void CreateReportFile() {
        using(Stream stream = this.streamFactory.CreateStream()) {
            // perform the important work!
        }
    }
}

Когда задействовано больше бизнес-логики, ваш фабричный метод может быть немного более сложным, но ненамного:

public void CreateReportFile() {
    string sourcePath   = this.otherComponent.GetReportPath();
    using(Stream stream = this.streamFactory.CreateStream(sourcePath)) {
        // perform the important work!
    }
}

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

Ключевым моментом в этом сценарии является то, что вы должны признать, что у вас есть три этапа в потоке данных:

  • Чтение данных: синтаксический анализ данных является уникальной, особой задачей
  • Содержание вывода: вы должны убедиться, что при заданных правильных данных у вас есть правильный вывод CSV-строки
  • Запись этого содержимого в файл

Честно говоря, проблема записи всего файла не так важна -.NET Framework предоставляет довольно хорошо протестированные функции записи файлов, и это выходит за рамки вашего тестирования. Большинство проблем, с которыми вы столкнетесь, - это точность CSV, которую вы выплевываете на файлы.

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

Другие вопросы по тегам