Что такое хороший подход, чтобы избавиться от зависимости от 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).

Любые идеи, пожалуйста! Благодаря