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


这是场景:

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

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


1983
2018-02-11 22:15


起源



答案:


取决于 Stream 和 TextReader 代替。然后您的单元测试可以使用 MemoryStream 和 StringReader。 (或者,如果需要,可以从测试程序集内部加载资源。)

请注意如何 ReadLine 最初由...声明 TextReader  StreamReader


11
2018-02-11 22:18



但这不会暂停这个问题吗?任何使用“读者类”的类都需要创建 StreamReader 要么 TextReader或者我错过了什么? - royalTS
@royalTS:他们可以使用 任何  TextReader 和 Stream 虽然实施。在测试中它可能是一个 StringReader;在真正的代码中,它可能是一个 StreamReader 包装文件或网络流等(从OP问题是否真的需要两个问题还不清楚 Stream  和 一个 TextReader或者只是一个 TextReader但是关键是通过向上移动抽象层,您可以获得更大的灵活性。 - Jon Skeet


答案:


取决于 Stream 和 TextReader 代替。然后您的单元测试可以使用 MemoryStream 和 StringReader。 (或者,如果需要,可以从测试程序集内部加载资源。)

请注意如何 ReadLine 最初由...声明 TextReader  StreamReader


11
2018-02-11 22:18



但这不会暂停这个问题吗?任何使用“读者类”的类都需要创建 StreamReader 要么 TextReader或者我错过了什么? - royalTS
@royalTS:他们可以使用 任何  TextReader 和 Stream 虽然实施。在测试中它可能是一个 StringReader;在真正的代码中,它可能是一个 StreamReader 包装文件或网络流等(从OP问题是否真的需要两个问题还不清楚 Stream  和 一个 TextReader或者只是一个 TextReader但是关键是通过向上移动抽象层,您可以获得更大的灵活性。 - Jon Skeet


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


3
2018-02-11 22:19





在我的头脑中,我会说这是一个调查优点的好机会 依赖注入

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


2
2018-02-11 22:23





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

有任何想法!谢谢


0
2017-08-14 09:38