Что такое хороший подход, чтобы избавиться от зависимости от StreamReader/FileStream для модульных тестов?
Здесь сценарий:
У меня есть метод, который читается в файле через FileStream и StreamReader в .NET. Я хотел бы unit test этот метод и как-то удалить зависимость от объекта StreamReader.
В идеале я хотел бы предоставить собственную строку тестовых данных вместо реального файла. Сейчас метод использует метод StreamReader.ReadLine. Каков подход к модификации дизайна, который у меня есть сейчас, чтобы сделать этот тест возможным?
Ответы
Ответ 1
Зависит от Stream
и TextReader
. Тогда ваши модульные тесты могут использовать MemoryStream
и StringReader
. (Или загрузите ресурсы из вашей тестовой сборки, если это необходимо.)
Обратите внимание, что ReadLine
изначально объявляется TextReader
, а не StreamReader
.
Ответ 2
Простейшим решением было бы заставить метод принимать Stream как параметр вместо того, чтобы открывать свой собственный FileStream. Ваш фактический код мог бы проходить в FileStream, как обычно, в то время как ваш тестовый метод мог либо использовать другой FileStream для тестовых данных, либо MemoryStream, заполненный тем, что вы хотели проверить (для этого не требуется файл).
Ответ 3
Вплоть до головы, я бы сказал, что это отличная возможность исследовать достоинства Injection of Dependency.
Возможно, вам захочется перепроектировать ваш метод, чтобы он принял делегат, который возвращает содержимое файла. Один делегат (производственный) может использовать классы в System.IO, а второй (для модульного тестирования) возвращает содержимое напрямую в виде строки.
Ответ 4
Я думаю, что идея заключается в том, чтобы вливать TextReader и высмеивать его для модульного тестирования. Я думаю, вы можете только издеваться над TextReader, потому что это абстрактный класс.
public class FileParser
{
private readonly TextReader _textReader;
public FileParser(TextReader reader)
{
_textReader = reader;
}
public List<TradeInfo> ProcessFile()
{
var rows = _textReader.ReadLine().Split(new[] { ',' }).Take(4);
return FeedMapper(rows.ToList());
}
private List<TradeInfo> FeedMapper(List<String> rows)
{
var row = rows.Take(4).ToList();
var trades = new List<TradeInfo>();
trades.Add(new TradeInfo { TradeId = row[0], FutureValue = Convert.ToInt32(row[1]), NotionalValue = Convert.ToInt32(row[3]), PresentValue = Convert.ToInt32(row[2]) });
return trades;
}
}
а затем Mock с использованием Rhino Mock
public class UnitTest1
{
[Test]
public void Test_Extract_First_Row_Mocked()
{
//Arrange
List<TradeInfo> listExpected = new List<TradeInfo>();
var tradeInfo = new TradeInfo() { TradeId = "0453", FutureValue = 2000000, PresentValue = 3000000, NotionalValue = 400000 };
listExpected.Add(tradeInfo);
var textReader = MockRepository.GenerateMock<TextReader>();
textReader.Expect(tr => tr.ReadLine()).Return("0453, 2000000, 3000000, 400000");
var fileParser = new FileParser(textReader);
var list = fileParser.ProcessFile();
listExpected.ShouldAllBeEquivalentTo(list);
}
}
НО вопрос заключается в том, является ли хорошей практикой передавать такой объект из клиентского кода, но я считаю, что его нужно управлять с использованием в классе, ответственном за обработку. Я согласен с идеей использования делегата sep для фактического кода и одного для модульного тестирования, но опять же, это немного дополнительный код в производстве. Возможно, я немного потерял идею внедрения зависимостей и высмеивал даже открытый файл IO open/read, который на самом деле не является кандидатом на модульное тестирование, но логика обработки файлов - это то, что можно протестировать, просто передав строковое содержимое файла ( AAA23 ^ YKL890 ^ 300000 ^ TTRFGYUBARC).
Любые идеи, пожалуйста! Благодаря