【问题标题】:Unit Testing: Logging and Dependency Injection单元测试:日志记录和依赖注入
【发布时间】:2010-11-13 04:19:38
【问题描述】:

因此,对于从 SO 和 Internet 上的其他站点进行日志记录,最佳响应似乎是:

void DoSomething() {
    Logger.Log("Doing something!");
    // Code...
}

现在您通常会避免使用静态方法,但在记录日志的情况下(一种特殊情况),这是最简单和最干净的方法。在静态类中,您可以轻松地通过配置文件/框架注入实例,从而获得与 DI 相同的效果。

我的问题来自单元测试的角度。

在上面的示例代码中,假设 DoSomething() 的目的是将两个数字相加。我会为此编写单元测试。日志记录呢?

我会为日志记录编写一个单元测试吗(但对记录器本身使用模拟实例)?我知道如果是这种情况,我将不得不编写一个集成测试来证明记录器确实写入了日志文件,但我不确定。

在测试驱动开发(我这样做)之后,我需要进行单元测试来指定接口吗?

有什么建议吗?

【问题讨论】:

    标签: unit-testing language-agnostic logging tdd


    【解决方案1】:

    就个人而言,我非常虔诚地练习 TDD/BDD,而且我几乎从不测试日志记录。除了一些例外,日志记录要么是开发人员的便利,要么是可用性因素,而不是方法核心规范的一部分。它也往往比方法的实际语义具有更高的变化率,因此您最终会因为添加了更多信息日志记录而中断测试。

    进行一些简单地测试日志子系统的测试可能是值得的,但对于大多数应用程序,我不会测试每个类以特定方式使用日志。

    【讨论】:

    • 这是一个公平的观点。我非常清楚 100% 的代码覆盖率没有任何意义,这通常似乎是最实用的解决方案。正如您所说,无论日志记录是否有效,都不应影响业务逻辑。
    • 日志流失正是您应该对某些日志语句进行单元测试的原因。日志条目成为下游管理系统的事件信号源。您确实应该为这些源编写单元测试,以便在您以破坏下游系统的方式删除或更改它们时收到警报。例如,如果操作员正在挖掘您的日志以查找提交失败,您需要确保这些消息的内容得到保留,即使您更改行为或以其他方式添加或降低日志保真度也是如此。与运营建立良好的关系是收集真实用例以使其成为测试的关键。
    【解决方案2】:

    我只为日志编写过几个单元测试。这很痛苦,要么使生产代码混乱(由于注入记录器),要么使测试变臭(用模拟替换静态记录器)。与 McWafflestix 不同的是,我经常发现之后的努力并不值得。

    真的有多少想知道日志记录是否正常工作,而不是通过其他(动手)测试看到的?您可能希望在日志记录非常重要的偶尔课程中使用 DI,但否则我不会费心测试日志记录。

    这是假设日志具有调试性质 - 如果它是 审计 日志或类似的东西(有功能要求的东西),那就是另一回事了。

    【讨论】:

    • 好吧,有人告诉我,你永远不要在没有编写测试来证明其价值 (TDD) 的情况下编写一行代码,所以这就是其背后的动机。
    • @Dockers,可能被引用到死,但这是 Kent Beck 对此的看法。我认为这条线更适合初学者。一旦你知道你在做什么,你确实可以跳过一些事情,正如乔恩建议的那样。 stackoverflow.com/questions/153234/how-deep-are-your-unit-tests/…
    • 优秀。我不知道这是在这里,阅读肯特贝克斯的信息。我会检查整个讨论。干杯。
    【解决方案3】:

    我将日志分为三类:

    1) 要求。一些系统出于审计目的需要日志记录,或满足项目的一些其他要求(例如应用服务器中的日志记录标准)。那么它确实是一项要求,值得进行单元测试和验收测试,直到您可以确信满足要求。所以在这种情况下,可以测试日志的确切字符串。

    2) 解决问题。如果您在 QA 或生产中开始出现奇怪的状态,您希望能够跟踪正在发生的事情。一般来说,我会说如果这很重要(比如在一个线程繁重的应用程序中,状态可能会变得复杂但无法通过已知步骤复制),那么测试给定的状态值最终记录可能是有价值的(所以你'不测试日志的整个可读性,只是某些事实进入那里)。即使稍后更改类,该状态仍然可能被记录(连同其他状态),因此测试和记录之间的耦合是合理的。所以在这种情况下,只测试部分日志记录(a contains test)。

    3) 发展援助。在许多情况下,我使用日志记录作为一种更强大的评论形式。您可以编写如下语句:

     logger.debug("Extract the fifth instance of BLAH from the string " + s);
    

    这样您就可以记录代码,同时拥有一个有用的工件,如果您确实需要调试正在发生的事情。在那种情况下,我根本不会进行单元测试,因为给定语句的存在与否本身并不重要。

    关于你必须对所有事情都进行 100% 测试的观点,请参阅 Kent Beck 的回答 here。我认为“测试一切”对初学者来说是个好建议,因为当您从 TDD 开始时,诱惑将是不测试任何难以测试的东西,或者促使您考虑设计以使其可测试,并且将其合理化为不重要。但是,一旦您知道自己在做什么,并欣赏测试的价值,那么平衡您正在做的事情和值得测试的事情就很重要了。

    【讨论】:

    • +1 用于在业务需求和技术需求之间划分日志记录。
    【解决方案4】:

    大多数日志框架允许您为组件提供自定义实现。您可以使用该配置机制来提供您自己的实现。


    例如,Java 的 Log4J 允许您声明自定义 appenders,它们是负责“传递” LoggingEvent 的组件。

    可以使用以下方法轻松模拟和注入记录器:

    Appender appenderMock = EasyMock.createMock(Appender.class);
    /* expect */ appenderMock.doAppend(EasyMock.isA(LoggingEvent.class));
    EasyMock.replay(appenderMock);
    
    Logger.getLogger(/* replace with your own */"My logger").addAppender(appenderMock);
    
    EasyMock.verify(appenderMock);
    

    此测试仅验证是否发送了日志记录事件,但您可以使用EasyMock 对其进行更多改进。

    【讨论】:

      【解决方案5】:

      我想说为日志编写单元测试可能是合理的;我以前这样做过,结果证明它比我最初预期的更有用。一般来说,单元测试日志可以很好地保证日志工具在单元测试时工作,这是一个很好的保证。我不认为它对单元测试日志记录很重要,但如果你有这种倾向,我怀疑它也会是一件坏事。

      【讨论】:

        【解决方案6】:

        我个人认为对单元测试日志语句来说太过分了。但是如果你真的想这样做,那么一个模拟记录器就可以工作,或者,如果使用像 log4j 这样的框架,编写一个在测试运行期间使用的自定义附加程序。

        【讨论】:

          【解决方案7】:

          我通常不会通过断言记录的内容来对日志语句进行单元测试,但我会检查单元测试所采用的代码路径是否涵盖了日志语句,以确保在记录异常时不会出现异常!

          【讨论】:

            【解决方案8】:

            我可能会对记录器本身进行单独的单元测试,以将其各种功能与其他所有功能分开测试。在使用记录器的方法中,我只会测试是否使用正确的参数调用了记录器(即期望它被调用)。例如,如果我有一个方法:

            DoSomething()
            {
                if (FatalErrorOccurred)
                {
                    Logger.Log("Fatal Error", ErrorLevel.Fatal);
                }
            }
            

            我会编写一个测试,显示当 FatalErrorOccurred 为真时记录器记录了一条致命错误消息。当然,我不会测试错误消息本身的内容,因为它很容易改变。

            【讨论】:

              【解决方案9】:

              虽然我同意其他人的观点,即我不会将 TDD 应用于日志记录,但我会尝试确保单元测试涵盖所有包含日志记录语句的代码路径。并且重要的是确保在运行单元测试时配置最高详细级别,以便执行所有日志记录语句。

              例如,以下代码有一个错误,只有在启用调试级别跟踪时才会抛出 FormatException。

              if (logger.IsDebugEnabled)
              {
                  string message = String.Format(CultureInfo.CurrentCulture, 
                        "... {1} ...", someValue);
                  logger.Debug(message);
              }
              

              【讨论】:

                猜你喜欢
                • 2013-07-10
                • 1970-01-01
                • 2018-03-03
                • 2021-06-19
                • 1970-01-01
                • 2017-11-03
                • 2016-02-16
                相关资源
                最近更新 更多