【问题标题】:Need a better way to keep test code out of production code - slf4jtesting需要一种更好的方法将测试代码排除在生产代码之外 - slf4jtesting
【发布时间】:2018-07-09 12:25:24
【问题描述】:

我正在使用com.portingle:slf4jtesting:1.1.3 来帮助测试一些日志记录功能。

我的问题是com.portingle 的开发人员是strong advocates of dependency injection,并建议仅使用他们的slf4jtesting::ILoggerFactory 实用程序进行依赖注入(slf4j 的实现,它存储日志条目以便于测试和验证)。

通过依赖注入,我可以像这样在我的类中创建我的 slf4j 记录器并注入生产或测试LoggerFactory

import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;

public class Example1 {

    private final Logger logger;

    public Example1(ILoggerFactory lf) {
        this.logger = lf.getLogger(Example1.class.getName());
    }

    public void aMethodThatLogs() {
        logger.info("Hello World!");
    }
}

足够合理,但我有一个遗留应用程序,并且我所有的记录器都已经编码并且有时在静态代码块/方法中使用,因此标准 DI 构造函数注入将不起作用。

目前我正在这样做:

private static final Logger log = LoggingUtils.getLogger(
        RequestLoggingFilter.class);

LoggingUtils 看起来像这样:

public class LoggingUtils {

    private LoggingUtils() {
    }

    private static ILoggerFactory iLoggerFactory =
            LoggerFactory.getILoggerFactory();

    /**
     * We don't want to call this in production.
     */
    public static void switchToTestLogging() {
        iLoggerFactory = Settings.instance().enableAll().buildLogging();
    }

    /**
     * Return logger for a class, of whatever implementation is running,
     * e.g. test or prod logger.
     *
     * @param loggingClass the class doing the logging
     * @return logger
     */
    public static Logger getLogger(Class loggingClass) {
        return iLoggerFactory.getLogger(loggingClass.getName());
    }

所以在测试中,我可以通过调用我的switchToTestLogging() 切换到slf4jtesting::ILoggerFactory,但最终结果是我的生产代码中有slf4jtesting 代码。

或者,我可以将 iLoggerFactory 公开,以便测试可以在必要时替换它,但允许任何生产代码这样做是不好的做法。

最后,我可以在我的LoggingUtils 班级中use reflection to hack the private ILoggerFactory instance 并在测试期间分配一个测试LoggerFactory

@BeforeAll
public static void setupLogging()
        throws NoSuchFieldException, IllegalAccessException {
    Field loggerFactoryField =
            LoggingUtils.class.getDeclaredField("iLoggerFactory");
    loggerFactoryField.setAccessible(true);
    loggerFactoryField.set(null,
        Settings.instance().enableAll().buildLogging());
}

但这也不完全是“最佳实践”。

有什么方法可以让ILoggerFactory 实例保持私有,避免反射并让测试库停止生产?

【问题讨论】:

  • 虽然不是静态耦合的忠实拥护者,但您可以完全删除 switchToTestLogging 并在测试模拟工厂方法以在调用时返回所需的记录器。 PowerMockito 应该可以让你模拟静态成员。

标签: java unit-testing dependency-injection slf4j


【解决方案1】:

我不是静态耦合的忠实拥护者,但从技术上讲,您过于关注实现问题。

您可以完全删除switchToTestLogging

public class LoggingUtils {

    private LoggingUtils() {
    }

    private static ILoggerFactory iLoggerFactory;

    /**
     * Return logger for a class
     *
     * @param loggingClass the class doing the logging
     * @return logger
     */
    public static Logger getLogger(Class loggingClass) {
        //Lazy loading.
        if(iLoggerFactory == null) {
            iLoggerFactory = LoggerFactory.getILoggerFactory();
        }
        return iLoggerFactory.getLogger(loggingClass.getName());
    }
}

并在测试模拟工厂方法以在调用时返回所需的记录器。

PowerMockito 应该能够让您模拟静态成员。

@RunWith(PowerMockRunner.class)
@PrepareForTest(LoggingUtils.class) //<-- important
public class SomeTest {
    @Test
    public void someTestMethod() {
        //Arrange
        //get the logger used in testing
        ILoggerFactory testLoggerFactory = Settings.instance().enableAll().buildLogging();
        //set up util for mocking
        PowerMockito.mockStatic(LoggingUtils.class);
        //setup mocked member
        Mockito.when(LoggingUtils.getLogger(any(Class.class)))
            .thenAnswer(i -> testLoggerFactory.getLogger(i.getArguments()[0].getName()));

        //Act
        //call subject under test that is coupled to LoggingUtils

        //Assert
        //...
    }
}

LoggingUtils 现在只关注生产问题,PowerMockito 允许您在执行测试时调用LoggingUtils.getLogger 时存根测试记录器。

免责声明:这未经测试。根据我对框架的回忆提供。

完成后,我强烈建议重构您的代码以遵循我的 SOLID 实践,这将使您的代码更简洁、更易于维护。像这样的黑客是代码异味和糟糕设计的明确指标。仅仅因为有一些工具可以解决问题并不能消除所做的糟糕设计选择。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-04-30
    • 2011-11-09
    • 1970-01-01
    • 2011-11-06
    • 2015-05-23
    • 1970-01-01
    相关资源
    最近更新 更多