【问题标题】:Unit testing void methods?单元测试无效方法?
【发布时间】:2010-09-19 18:00:02
【问题描述】:

对不返回任何内容的方法进行单元测试的最佳方法是什么?特别是在 c# 中。

我真正想要测试的是一种获取日志文件并将其解析为特定字符串的方法。然后将字符串插入数据库。以前没有做过任何事情,但是对于 TDD 来说是非常新的,我想知道是否有可能对此进行测试,或者它是否真的没有经过测试。

【问题讨论】:

    标签: c# unit-testing void


    【解决方案1】:

    它会对一个对象产生一些影响....查询效果的结果。如果它没有明显的效果,则不值得进行单元测试!

    【讨论】:

      【解决方案2】:

      取决于它在做什么。如果它有参数,则传入你可以稍后询问它们是否使用正确的参数集调用的模拟。

      【讨论】:

      • 同意 - 验证模拟测试方法的行为将是一种方法。
      【解决方案3】:

      一如既往:测试方法应该做什么!

      是否应该在某处更改全局状态(呃,代码异味!)?

      它应该调用一个接口吗?

      用错误的参数调用时是否应该抛出异常?

      使用正确的参数调用时是否应该不抛出异常?

      应该...?

      【讨论】:

        【解决方案4】:

        大概该方法做了一些事情,而不是简单地返回?

        假设是这样,那么:

        1. 如果它修改了它的所有者对象的状态,那么你应该测试状态改变是否正确。
        2. 如果它接受某个对象作为参数并修改该对象,那么您应该测试该对象是否被正确修改。
        3. 如果在某些情况下抛出异常,请测试是否正确抛出了这些异常。
        4. 如果其行为根据自身对象或其他对象的状态而变化,请预设状态并通过上述三种测试方法之一测试该方法是否具有正确的 I)。

        如果你让我们知道该方法的作用,我可以更具体。

        【讨论】:

          【解决方案5】:

          如果一个方法没有返回任何东西,那么它就是以下之一

          • 势在必行 - 您要么要求对象对自己做某事.. 例如更改状态(不期待任何确认.. 它假设它会完成)
          • 信息性 - 只是分别通知某人某事发生了(不期望采取行动或回应)。

          命令式方法 - 您可以验证任务是否实际执行。验证状态更改是否实际发生。例如

          void DeductFromBalance( dAmount ) 
          

          可以通过验证发送此消息的余额是否确实小于初始值 dAmount 来测试

          信息性方法 - 作为对象公共接口的成员很少见...因此通常不经过单元测试。但是,如果必须,您可以验证是否要对通知进行处理。例如

          void OnAccountDebit( dAmount )  // emails account holder with info
          

          可以通过验证是否正在发送电子邮件来测试

          发布有关您的实际方法的更多详细信息,人们将能够更好地回答。
          更新:您的方法正在做两件事。我实际上将它分成两种现在可以独立测试的方法。

          string[] ExamineLogFileForX( string sFileName );
          void InsertStringsIntoDatabase( string[] );
          

          String[] 可以通过为第一个方法提供一个虚拟文件和预期的字符串来轻松验证。第二个有点棘手..您可以使用 Mock(谷歌或在模拟框架上搜索 stackoverflow)来模拟数据库或点击实际的数据库并验证字符串是否插入到正确的位置。查看this thread 以获得一些好书...如果您处于紧要关头,我会推荐实用单元测试。
          在代码中,它将像

          一样使用
          InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );
          

          【讨论】:

          • 嘿 gishu,很好的答案。你给出的例子......他们不是更多的集成测试......吗?如果是这样,问题仍然存在,如何真正测试 Void 方法....也许不可能?
          • @andy - 取决于您对“集成测试”的定义。命令式方法通常会改变状态,因此您可以通过询问对象状态的单元测试来验证。信息方法可以通过插入模拟侦听器/协作器的单元测试来验证,以确保测试主体发出正确的通知。我认为两者都可以通过单元测试进行合理的测试。
          • @andy 可以通过访问器接口模拟/分离数据库,从而允许通过传递给模拟对象的数据来测试操作。
          • 保存到数据库似乎不属于您的两个类别,除非它算作“通知某人”,这是一个奇怪的特征。您可能想在此处澄清您的措辞。
          • @MarredCheese - 这不是命令式的吗?您可以通过执行 Load 来验证行是否确实被持久化 - 即状态更改。
          【解决方案6】:

          测试它的副作用。这包括:

          • 它会抛出任何异常吗? (如果应该,请检查它是否存在。如果不应该,请尝试一些极端情况,如果您不小心可能会出现这种情况 - 最明显的是空参数。)
          • 它的参数能很好地发挥作用吗? (如果它们是可变的,它会在不应该改变它们的时候改变它们吗?反之亦然?)
          • 它对您调用它的对象/类型的状态有正确的影响吗?

          当然,您可以测试多少是有限度的。例如,您通常无法测试所有可能的输入。务实地进行测试 - 足以让您确信您的代码设计得当并正确实施,并且足以充当调用者可能期望的补充文档。

          【讨论】:

            【解决方案7】:

            使用Rhino Mocks 设置可能预期的调用、操作和异常。假设您可以模拟或存根您的方法的某些部分。如果不知道这里有关该方法甚至上下文的一些细节,很难知道。

            【讨论】:

            • 如何进行单元测试的答案永远不应该是让第三方工具为您完成它。不过,当一个人知道如何进行单元测试时,使用第三方工具来简化它是可以接受的。
            【解决方案8】:

            Void 返回类型/子例程是旧消息。我已经有 8 年没有做过 Void 返回类型(除非我非常懒惰)(从这个答案开始,所以就在这个问题被问到之前)。

            而不是像这样的方法:

            public void SendEmailToCustomer()
            

            创建一个遵循 Microsoft 的 int.TryParse() 范例的方法:

            public bool TrySendEmailToCustomer()
            

            也许从长远来看,您的方法不需要返回任何信息以供使用,但在方法执行其工作后返回该方法的状态对调用者来说是一个巨大的用途。

            另外,bool 不是唯一的状态类型。很多时候,先前制作的子程序实际上可以返回三个或更多不同的状态(好、正常、坏等)。在这些情况下,您只需使用

            public StateEnum TrySendEmailToCustomer()
            

            但是,尽管 Try-Paradigm 在某种程度上回答了这个关于如何测试 void return 的问题,但也有其他考虑因素。例如,在“TDD”周期期间/之后,您将“重构”并注意到您正在用您的方法做两件事……从而打破了“单一责任原则”。所以应该先处理好。其次,您可能已经确定了一个依赖项......您正在触摸“持久”数据。

            如果您在有问题的方法中进行数据访问,则需要重构为 n 层或 n 层架构。但是我们可以假设,当您说“然后将字符串插入数据库”时,您实际上的意思是您正在调用业务逻辑层或其他东西。是的,我们会假设。

            当您的对象被实例化时,您现在了解您的对象具有依赖关系。这是您需要决定是否要对对象或方法进行依赖注入的时候。这意味着您的构造函数或有问题的方法需要一个新参数:

            public <Constructor/MethodName> (IBusinessDataEtc otherLayerOrTierObject, string[] stuffToInsert)
            

            现在您可以接受业务/数据层对象的接口,您可以在单元测试期间对其进行模拟,并且无需依赖或担心“意外”集成测试。

            因此,在您的实时代码中,您传入一个 REAL IBusinessDataEtc 对象。但是在您的单元测试中,您传入一个 MOCK IBusinessDataEtc 对象。在该 Mock 中,您可以包含非接口属性,例如 int XMethodWasCalledCount 或在调用接口方法时更新其状态的东西。

            因此,您的单元测试将通过您的 Method(s)-In-Question,执行它们具有的任何逻辑,并调用 IBusinessDataEtc 对象中的一个或两个或一组选定的方法。当您在单元测试结束时执行断言时,您现在有几件事要测试。

            1. “子程序”的状态,现在是 Try-Paradigm 方法。
            2. Mock IBusinessDataEtc 对象的状态。

            有关构建级别的依赖注入思想的更多信息......因为它们与单元测试有关......请查看构建器设计模式。它为您当前拥有的每个接口/类增加了一个接口和类,但它们非常小,并为更好的单元测试提供了巨大的功能增加。

            【讨论】:

            • 这个好答案的第一部分为所有新手/中级程序员提供了惊人的一般建议。
            • 不应该是public void sendEmailToCustomer() throws UndeliveredMailException这样的东西吗?
            • @A.Emad 好问题,但没有。依靠抛出异常来控制代码流是一种众所周知的坏习惯。虽然,在void 方法中,尤其是在面向对象的语言中,它是唯一真正的选择。微软最古老的替代方案是我讨论的 Try-Paradigm,以及像 Monads / Maybes 这样的函数式范例。因此,命令(在 CQS 中)仍然可以返回有价值的状态信息,而不是依赖于 throw,这类似于 GOTO(我们知道这是不好的)。 throwing(和 goto)速度很慢,难以调试,而且不是好的做法。
            • 感谢您解决此问题。即使在 Java 和 C++ 等语言中抛出异常,是 C# 特有的还是一般来说都是不好的做法?
            【解决方案9】:

            试试这个:

            [TestMethod]
            public void TestSomething()
            {
                try
                {
                    YourMethodCall();
                    Assert.IsTrue(true);
                }
                catch {
                    Assert.IsTrue(false);
                }
            }
            

            【讨论】:

            • 这不是必须的,但可以这样做
            • 欢迎来到 StackOverflow!请考虑为您的代码添加一些解释。谢谢。
            • ExpectedAttribute 旨在让这个测试更清晰。
            【解决方案10】:

            你使用什么实例来调用 void 方法,你可以使用,Verfiy

            例如:

            在我的例子中,_Log 是实例,LogMessage 是要测试的方法:

            try
            {
                this._log.Verify(x => x.LogMessage(Logger.WillisLogLevel.Info, Logger.WillisLogger.Usage, "Created the Student with name as"), "Failure");
            }
            Catch 
            {
                Assert.IsFalse(ex is Moq.MockException);
            }
            

            Verify 是否由于测试失败的方法失败而引发异常?

            【讨论】:

              【解决方案11】:

              你甚至可以这样尝试:

              [TestMethod]
              public void ReadFiles()
              {
                  try
                  {
                      Read();
                      return; // indicates success
                  }
                  catch (Exception ex)
                  {
                      Assert.Fail(ex.Message);
                  }
              }
              

              【讨论】:

              • 这是我想的最简单的方法。
              猜你喜欢
              • 2016-08-14
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-11-30
              • 2021-10-29
              • 1970-01-01
              相关资源
              最近更新 更多