我们应该单元测试日志记录吗?

问题描述:

这是通常看到的代码我们应该单元测试日志记录吗?

public class A { 

private static final Log LOG = LogFactory.getLog(A.class); 

和使用

 } catch (Exception e) { 
      LOG.error(e.getMessage(), e); 
      throw e; 
     } 

日志记录功能,但我从来没有见过这样的代码,甚至是单一的单元测试。

当然我会测试抛出异常和异常类型,但是我应该编写检查日志信息的测试吗?我倾向于认为日志记录是系统行为的另一部分,所以它在逻辑上放弃了在测试中覆盖它。

假设我应该覆盖它,意味着我应该更改我的原始代码以注入模拟日志并检查“错误”方法是否以预期的消息被调用。但是如果我的原始类是服务并且在春天实例化后应该怎么做,我应该注入一些记录器还是其他依赖项?

这不是由您来测试日志库。但值得一提的是,当抛出一个异常时,你的班级会在正确的级别上记录一条消息。你测试的是你的代码在日志库上做了正确的事情。

要使上面的代码可测试,请使用依赖注入。这假定记录器实现了一个接口,ILog。您可以将记录器作为构造函数参数传递给A类。然后,测试代码将创建一个ILog的模拟实现,并将其传递给构造函数。在上面的代码中没有显示异常是如何发生的,但大概是通过一些其他的依赖对象。所以你也嘲笑它,并让它抛出一个异常。然后检查模拟ILog是否调用了error方法。也许你想检查它记录的信息,但是这可能会变得太过分了,因为测试代码很脆弱。

+0

这是我在我的问题中提到的相同方法。但是对于我来说目前还不清楚在春季服务的情况下应该怎么做,因为通常为每个类创建LOG,所以它是类的特定的,我不能将它作为单例。 – 2012-08-29 17:34:05

+0

如果您无法控制实例化Log对象的代码,则很难测试行为,因为您无法使用模拟日志。您可以让它使用真实的Log对象进行登录,然后检查是否记录了消息。但是,这很难自动化! – 2012-08-29 18:25:54

+0

不,我同意你应该测试日志代码。这意味着我们应该为LOG设置setter,所以可以通过模拟登录测试,但在弹簧服务的情况下应该怎么做。我在考虑2种可能的方法:1)有一个setter,但只能从测试中调用(可能很难限制这种可见性,因为spring可以自动注入),2)通过spring注入记录器,但在这种情况下很难实例化适当的记录器。 – 2012-08-29 22:17:08

我不会单元测试代码,只是调用一个你信任的库。
你相信你的日志库吗?如果测试失败,是因为库中存在错误,或者仅仅是因为您没有正确配置库?你是否在护理关于测试配置?

+14

我没有测试日志库的工作原理,我的意图是测试我的代码是否进行日志记录。 – 2012-08-17 18:14:36

是的,我们应该在日志记录正在做某些事情时测试日志记录。例如,您在某些外部应用程序中挂接了某些事件的日志扫描。在这种情况下,你当然想确保记录完成。

当然,你不想测试每个日志事件,我会认为大多数只有错误(而不是全部)应该被测试。

使用现代日志记录框架(如SLF4j),您可以简单地注入一个自定义处理程序,该处理程序将事件存储在内存中,并且可以在事后进行声明。

有他们两个是我的脑海里来,现在:

SLF4JTesting:需要的日志记录配置的任何修改,需要注入一个日志工厂可能导致修改后的代码。

SLF4J Test:没有slf4jtesting那么强大,似乎没有开发出来,但是与现有的代码很好地协作。除了测试记录器配置之外,没有任何修改。

当使用SLF4J测试时,断言非常严格,并检查整个事件是否相等。自定义匹配是在这样的情况下大概有趣:

public static Matcher<LoggingEvent> errorMessageContains(final String s) { 
    return new TypeSafeMatcher<LoggingEvent>() { 
     @Override 
     public void describeTo(final Description description) { 
      description.appendText(" type " + Level.ERROR + " should contain ") 
        .appendValue(s); 
     } 

     @Override 
     protected void describeMismatchSafely(final LoggingEvent item, final Description mismatchDescription) { 
      mismatchDescription.appendText(" was type ").appendValue(l) 
        .appendText(" message ").appendValue(item.getMessage()); 
     } 

     @Override 
     protected boolean matchesSafely(final LoggingEvent item) { 
      return item.getLevel().equals(Level.ERROR) 
        && item.getMessage().contains(s); 
     } 
    }; 
} 

这仅检查该消息包含文本,但如果它是不相等的。因此,当消息被修改以修正错字或给出更多细节时,如果基本部分仍然被包含,则测试不会中断。

如果日志记录是业务需求,并且将提供业务价值(即在发生故障时诊断或分类问题),那么您应该将其视为任何其他需求。因此,你可能应该编写单元测试来验证你的日志库是否工作,但是为了验证在预期的情况下你的代码记录了它的内容。

更多关于这个话题:https://ardalis.com/logging-and-monitoring-are-requirements

有另一种方法:你可以嘲笑的LogFactory!例如:

import junit.framework.Assert; 
import mockit.Mock; 
import mockit.MockUp; 
import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory; 
import org.junit.Test; 

public class XXXTest { 
    class MyLog implements Log { 
     public static final String INFO = "info"; 

     private String logLevel; 
     private Object logContent; 

     public String getLogLevel() { 
      return logLevel; 
     } 

     public Object getLogContent() { 
      return logContent; 
     } 

     @Override 
     public void info(Object o) { 
      logLevel = "info"; 
      logContent = o; 
     } 

     //Implement other methods 
    } 

    @Test 
    public void testXXXFunction() { 
     final MyLog log = new MyLog(); 
     new MockUp<LogFactory>() { 
      @Mock 
      public Log getLog(String name) { 
       return log; 
      } 
     }; 

     //invoke function and log by MyLog 
     FunctionToBeTest.invoke(); 
     Assert.assertEquals("expected log text", log.getLogContent()); 
    } 
} 

好运!