需要一种更好的方法来将测试代码排除在生产代码之外 - slf4jtesting



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

我的问题是com.portingle的开发人员是依赖注入的强烈倡导者,并建议仅使用依赖注入来利用其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类中的私有ILoggerFactory实例,并在测试期间分配测试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

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 实践,这将使您的代码更清晰、更易于维护。像这样的黑客是代码的味道,是设计不佳的明确指标。仅仅因为有允许变通的工具并不能消除所做的糟糕的设计选择。

最新更新