gpt4 book ai didi

java - 用 Java 构建一个可模拟的日志记录 API,正确的方法

转载 作者:行者123 更新时间:2023-11-28 20:28:04 24 4
gpt4 key购买 nike

我一直在阅读有关编写可测试代码的知识,现在正尝试通过重构我的日志记录框架 API 将其付诸实践。我主要关心的是它应该 1) 易于从业务代码中调用,以及 2) 易于模拟,这样业务代码就可以在不调用真实日志记录的情况下进行测试,并且还可以断言是否已记录事情说测试。

我已经达到了可以测试的地步,但我仍然觉得它可以改进。请多多包涵。这是我目前所拥有的。

/*
* The public API, which has a mockable internal factory responsible for creating log implementations.
*/
public final class LoggerManager {
private static LoggerFactory internalFactory;
private LoggerManager() {}

public static SecurityLogger getSecurityLogger() {
return getLoggerFactory().getSecurityLogger();
}
public static SystemErrorLogger getSystemErrorLogger() {
return getLoggerFactory().getSystemErrorLogger();
}
private static LoggerFactory getLoggerFactory() {
if (internalFactory == null)
internalFactory = new LoggerFactoryImpl();
return internalFactory;
}
public static void setLoggerFactory(LoggerFactory aLoggerFactory) {
internalFactory = aLoggerFactory;
}
}
/*
* Factory interface with methods for getting all types of loggers.
*/
public interface LoggerFactory {
public SecurityLogger getSecurityLogger();
public SystemErrorLogger getSystemErrorLogger();
// ... 10 additional log types
}
public final class LoggerFactoryImpl implements LoggerFactory {
private final SecurityLogger securityLogger = new SecurityLoggerImpl();
private final SystemErrorLogger systemErrorLogger = new SystemErrorLoggerImpl();

public SecurityLogger getSecurityLogger() {
return securityLogger;
}
public SystemErrorLogger getSystemErrorLogger() {
return systemErrorLogger;
}
}

API在业务代码中是这样调用的:

LoggerManager.getSystemErrorLogger().log("My really serious error");

然后我会在单元测试中使用 TestLoggerFactory 模拟这个,它创建测试记录器,简单地跟踪所有记录调用,并使它成为可能,例如执行 assertNoSystemErrorLogs():

LoggerManager.setLoggerFactory(new TestLoggerFactory());

现在这工作正常,但我仍然觉得我遗漏了什么,它可以变得更加测试友好。例如,通过使用静态 setLoggerFactory,我为所有测试设置记录器工厂,这意味着一个测试实际上可以影响另一个。所以我的大问题是,创建这种易于模拟的 API 的标准方法是什么?某种依赖注入(inject)?

请注意,这个问题更多地与编写易于访问和使用且易于模拟的 API 有关。事实上,我的示例是一个日志记录框架 API 是题外话。

最佳答案

我在评论中被要求提供一个通过 CDI 使用记录器注入(inject)的示例。有许多可能的实现,但为了展示如何使用不同的限定符,我选择了这个示例,我假设记录器共享公共(public) Logger 接口(interface)并且使用了不同的限定符。

但是如果有 10 种以上可能的日志类型,我会选择使用枚举属性的限定符来决定注入(inject)哪个记录器。

首先定义您的限定符(Security、SystemError,....)以标记您要注入(inject)的实现:

@Qualifier
@Retention(RUNTIME)
@Target({TYPE, METHOD, FIELD, PARAMETER})
public @interface Security {
}

然后您必须定义如何创建记录器实现。它是一个工厂,某种程度上。实现可以取决于注入(inject)点和赋予限定符的值。例如,我只是将注入(inject)类的类名传递给记录器。这两种方法用于创建不同的实现。这可以通过将限定符应用于方法来实现。

@Singleton
public class LoggerProducer {

// here perhaps a cache or environment related flags, ...

@Security
@Produces
public Logger getSecurityLogger(InjectionPoint ip) {
String key = getKeyFromIp(ip);
return new SecurityLoggerImpl(key);
}

@SystemError
@Produces
public Logger getSystemErrorLogger(InjectionPoint ip) {
String key = getKeyFromIp(ip);
return new SystemErrorLoggerImpl(key);
}

private String getKeyFromIp(InjectionPoint ip) {
return ip.getMember().getDeclaringClass().getCanonicalName();
}

}

现在你可以在你想使用它的地方注入(inject)你想要的记录器(见CDI配置文件beans.xml)

@Stateless
public class SampleService {

@Inject
@Security
private Logger securityLogger;

public void doSomething() {
securityLogger.log("I did something!");
}

}

当进行单元测试时,您仍然需要像模拟所有其他注入(inject)对象一样模拟记录器,使用 CDI 不会有任何改变。在集成测试时,您可以更改生成记录器的方式/内容。

别把它当作个人问题,但我不明白为什么要使用 10 多个不同的记录器而不使用大多数日志框架和/或模拟框架提供的机制。也许您有充分的理由,好吧,了解您的所有选择总是有帮助的。


编辑:添加 JUnit 示例

为了对 SampleService 进行单元测试,我们使用 Mockito 创建了以下测试用例:

@RunWith(MockitoJUnitRunner.class)
public class SampleServiceTest {

@InjectMocks
private SampleService sample;

@Mock
private Logger securityLogger;

@Test
public void testDoSomething() {
doThrow(new RuntimeException("Fail")).when(securityLogger).log(anyString());

sample.doSomething(); // will fail
}

}

测试以这样的方式设置,当调用 logger.log 时会抛出异常。

关于java - 用 Java 构建一个可模拟的日志记录 API,正确的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45500610/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com