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

bxfogqkk  于 2023-04-10  发布在  Java
关注(0)|答案(7)|浏览(163)

在代码中通常会看到日志记录功能:

public class A {

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

使用方法:

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

但我从来没有看到过这样的代码的单个单元测试。
当然我会测试抛出异常和异常类型,但是我应该编写测试来检查日志信息吗?我倾向于认为日志是系统行为的另一部分,所以在测试中涵盖它是合乎逻辑的。
假设我应该覆盖它,这意味着我应该改变我的原始代码以注入mock log并检查“error”方法是否被预期的消息调用。但是如果我的原始类是service并且它是由spring示例化的,我应该注入一些logger以及其他依赖项吗?

v1uwarro

v1uwarro1#

测试日志库不是由你决定的。但是测试当抛出异常时,你的类在正确的级别记录消息是值得的。你测试的是你的代码对日志库做了正确的事情。
要使上面的代码可测试,请使用依赖注入。这假设日志记录器实现了接口ILog。您将日志记录器作为构造函数参数传递给类A。然后测试代码将创建ILog的模拟实现,并将其传递到构造函数中。上面的代码中没有显示异常是如何发生的,但可能是通过其他依赖对象。所以你也模拟它,并让它抛出异常。然后检查mock ILog调用了error方法。也许你想检查它记录的消息,但这可能太过分了,通过使测试代码脆弱。

jw5wzhpr

jw5wzhpr2#

是的,我们应该在日志记录正在做一些需要的事情时测试日志记录。例如,您在一些外部应用程序中有钩子,它会扫描日志中的某些事件。在这种情况下,您当然希望确保日志记录已经完成。
当然,您不希望测试每个日志事件,我认为大多数情况下只应该测试ERROR(而不是所有)。
使用SLF4j等现代日志框架,您可以简单地注入一个自定义处理程序,该处理程序将事件存储在内存中,并且可以在之后对其进行Assert。
我现在想到的有两个:
SLF4JTesting:不需要修改日志配置,但需要注入日志工厂,这可能会导致代码修改。
SLF4J Test:没有slf4jtesting那么强大,而且似乎还没有开发,但可以很好地与现有代码一起工作。除了用于测试的日志记录器配置之外,没有任何修改。
当使用SLF4J Test时,Assert非常严格,并检查整个事件是否相等。在这种情况下,自定义匹配器可能很有趣:

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);
        }
    };
}

这只检查消息是否包含文本,而不检查是否相等。因此,当消息被修改以修复错别字或给予更多细节时,如果仍然包含基本部分,则测试不会中断。

qij5mzcb

qij5mzcb3#

如果日志记录是一个业务需求,并将提供业务价值(即在发生故障时,诊断或分类问题),那么您应该将其视为任何其他需求。因此,您可能应该编写单元测试,而不是验证您的日志库是否正常工作,而是验证在预期的情况下,您的代码记录了它应该记录的内容。
更多关于此主题:https://ardalis.com/logging-and-monitoring-are-requirements

zpf6vheq

zpf6vheq4#

还有另一种方法你可以模仿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());
    }
}

祝你好运!

mtb9vblg

mtb9vblg5#

我肯定会考虑对日志记录场景进行单元测试。测试时,考虑在代码失败的情况下需要的信息。如果您有一个实时问题,您希望确保您有足够的信息来找到问题的原因。
避免过多的日志记录,因为在调试时,你可能看不到树木。

ecfdbz9o

ecfdbz9o6#

我使用了Baeldung网站提示:https://www.baeldung.com/junit-asserting-logs
1️您有一个创建日志的类:

public class BusinessWorker {
    private static Logger LOGGER = LoggerFactory.getLogger(BusinessWorker.class);

    public void generateLogs(String msg) {
        LOGGER.trace(msg);
        LOGGER.debug(msg);
        LOGGER.info(msg);
        LOGGER.warn(msg);
        LOGGER.error(msg);
    }
}

2️你使用copy-verbatim另一个类临时堆叠日志:

public class MemoryAppender extends ListAppender<ILoggingEvent> {
    public void reset() {
        this.list.clear();
    }

    public boolean contains(String string, Level level) {
        return this.list.stream()
          .anyMatch(event -> event.toString().contains(string)
            && event.getLevel().equals(level));
    }

    public int countEventsForLogger(String loggerName) {
        return (int) this.list.stream()
          .filter(event -> event.getLoggerName().contains(loggerName))
          .count();
    }

    public List<ILoggingEvent> search(String string) {
        return this.list.stream()
          .filter(event -> event.toString().contains(string))
          .collect(Collectors.toList());
    }

    public List<ILoggingEvent> search(String string, Level level) {
        return this.list.stream()
          .filter(event -> event.toString().contains(string)
            && event.getLevel().equals(level))
          .collect(Collectors.toList());
    }

    public int getSize() {
        return this.list.size();
    }

    public List<ILoggingEvent> getLoggedEvents() {
        return Collections.unmodifiableList(this.list);
    }
}

3️你在单元测试中使用它

@Before
public void setup() {
    Logger logger = (Logger) LoggerFactory.getLogger(LOGGER_NAME);
    memoryAppender = new MemoryAppender();
    memoryAppender.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
    logger.setLevel(Level.DEBUG);
    logger.addAppender(memoryAppender);
    memoryAppender.start();
}

@Test
public void test() {
    BusinessWorker worker = new BusinessWorker();
    worker.generateLogs(MSG);
        
    assertThat(memoryAppender.countEventsForLogger(LOGGER_NAME)).isEqualTo(4);
    assertThat(memoryAppender.search(MSG, Level.INFO).size()).isEqualTo(1);
    assertThat(memoryAppender.contains(MSG, Level.TRACE)).isFalse();
}
7vux5j2d

7vux5j2d7#

我不会对只会调用您信任的库的代码进行单元测试。
你信任你的日志库吗?如果测试失败,是因为库中有bug,还是仅仅因为你没有正确配置库?你关心测试配置吗?

相关问题