【问题标题】:What is a good approach to get rid of dependency on a StreamReader/FileStream for Unit Tests?摆脱对单元测试的 StreamReader/FileStream 的依赖的好方法是什么?
【发布时间】:2011-02-11 22:15:05
【问题描述】:

这是场景:

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

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

【问题讨论】:

    标签: c# .net unit-testing nunit


    【解决方案1】:

    改为依赖StreamTextReader。然后你的单元测试可以使用MemoryStreamStringReader。 (或在必要时从您的测试程序集中加载资源。)

    注意ReadLine 最初是如何由TextReader 声明的,不是 StreamReader

    【讨论】:

    • 但这不会暂停问题吗?任何使用“阅读器类”的类都需要创建StreamReaderTextReader,还是我错过了什么?
    • @royalTS:他们可以使用 any TextReaderStream 实现。在测试中它可能是StringReader;在实际代码中,它可能是包装文件的StreamReader,或网络流等。(从问题中不清楚OP是否真的需要Stream TextReader ,或者只是一个TextReader。)但关键是通过向上移动抽象层,您可以获得更多的灵活性。
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      在我的脑海中,我想说这是一个很好的机会来调查Dependency Injection 的优点。

      您可能需要考虑重新设计您的方法,以便它接受一个返回文件内容的委托。一个委托(生产委托)可能使用 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;
            } 
        }
        

        然后使用 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)。

        请有任何想法!谢谢

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多