Unit testing 单元测试:日志记录和依赖注入

Unit testing 单元测试:日志记录和依赖注入,unit-testing,language-agnostic,logging,tdd,Unit Testing,Language Agnostic,Logging,Tdd,因此,关于从So和互联网上的其他网站登录,最好的回答似乎是: void DoSomething() { Logger.Log("Doing something!"); // Code... } 现在,通常您会避免使用静态方法,但在日志记录(一种特殊情况)的情况下,这是最简单、最干净的方法。在静态类中,您可以通过配置文件/框架轻松地注入实例,从而获得与DI相同的效果 我的问题来自单元测试的角度 在上面的示例代码中,假设DoSomething()的作用是将两个数字相加。我会为此编写

因此,关于从So和互联网上的其他网站登录,最好的回答似乎是:

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

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

在上面的示例代码中,假设DoSomething()的作用是将两个数字相加。我会为此编写单元测试。那伐木呢

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

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


有什么建议吗?

我只为日志编写了一些单元测试。这是一种痛苦,要么使生产代码凌乱(由于注入记录器),要么使测试难闻(用模拟替换静态记录器)。与McWafflestix不同的是,我常常觉得事后的努力不值得

除了通过其他(实际操作)测试所看到的内容之外,您还想知道日志记录是否正常工作?您可能希望在日志记录非常重要的偶尔类中使用DI,但如果不是这样,我就不会费心测试日志记录了


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

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

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

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


例如,Java的Log4J允许您声明定制,这是负责“交付”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);

此测试仅验证是否发送了日志事件,但您可以使用更精细的方法来改进它。

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


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

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

我将日志记录分为三类:

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

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

3) 发展援助。在许多情况下,我使用日志作为一种更健壮的注释形式。您可以编写如下语句:

 logger.debug("Extract the fifth instance of BLAH from the string " + s);
因此,如果您确实需要调试正在发生的事情,您可以记录代码,同时拥有一个有用的工件。在这种情况下,我根本不会进行单元测试,因为给定语句的存在与否本身并不重要


关于你必须100%测试所有东西的观点,见肯特·贝克的答案。我认为“测试一切”对初学者来说是一个很好的建议,因为当你开始使用TDD时,你会倾向于不测试任何难以测试的东西,或者促使你考虑设计以使其可测试,并将其合理化为不重要。但是,一旦您确实知道自己在做什么,并了解测试的价值,那么就必须平衡您正在做的和值得测试的内容。

我可能会为记录器本身提供一个单独的单元测试主体,以测试其不同的功能,与其他功能分开。在使用logger的方法中,我只需测试是否使用正确的参数调用了logger(即预期调用了logger)。例如,如果我
DoSomething()
{
    if (FatalErrorOccurred)
    {
        Logger.Log("Fatal Error", ErrorLevel.Fatal);
    }
}
if (logger.IsDebugEnabled)
{
    string message = String.Format(CultureInfo.CurrentCulture, 
          "... {1} ...", someValue);
    logger.Debug(message);
}