Модульное тестирование класса, использующего файловую систему
У меня есть класс, который выводит простой файл отчета. Он считывает некоторые идентификационные номера записей из файла 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, которые уже существуют - их много в сети.