什么是摆脱StreamReader / FileStream单元测试依赖的好方法?

时间:2011-02-11 22:15:06

标签: c# .net unit-testing nunit

以下是该方案:

我有一个通过FileStream和.NET中的StreamReader读取文件的方法。我想单元测试这个方法,并以某种方式删除对StreamReader对象的依赖。

理想情况下,我希望能够提供自己的测试数据字符串,而不是使用真实文件。现在,该方法始终使用StreamReader.ReadLine方法。为了使这个测试成为可能,我现在修改设计的方法是什么?

4 个答案:

答案 0 :(得分:11)

取决于StreamTextReader。然后,您的单元测试可以使用MemoryStreamStringReader。 (或者,如果需要,可以从测试程序集内部加载资源。)

请注意TextReader最初声明ReadLine的原因,不是 StreamReader

答案 1 :(得分:3)

最简单的解决方案是让方法接受Stream作为参数,而不是打开自己的FileStream。您的实际代码可以像往常一样传入FileStream,而您的测试方法可以使用不同的FileStream来测试数据,也可以使用MemoryStream填充您想要测试的内容(不需要文件)。

答案 2 :(得分:2)

我认为这是调查Dependency Injection优点的绝佳机会。

您可能需要考虑重新设计方法,以便它需要一个返回文件内容的委托。一个委托(生产者)可能使用System.IO中的类,而第二个委托(用于单元测试)直接将内容作为字符串返回。

答案 3 :(得分:0)

我认为这个想法是依赖注入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;
    } 
}

然后使用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打开/读取的想法实际上不是单元测试的候选者,但文件处理逻辑可以通过传递文件的字符串内容来测试( AAA23 ^ ^ YKL890 ^ 300000 TTRFGYUBARC)。

请有任何想法!感谢