Java 如何编写JUnit for Adapter而无需过度复杂的代码?

Java 如何编写JUnit for Adapter而无需过度复杂的代码?,java,junit,mocking,inversion-of-control,adapter,Java,Junit,Mocking,Inversion Of Control,Adapter,我有一个从I1到ILogger的适配器,实现如下: class BAdapter() implements I1 { void logA() { // nothing } void logB() { new BLogger().log() } void logC() { // nothing } } 我想编写JUnit测试来验证功能,但我发现它有点问题,因为我无法插入模拟对象而不是BLogger,也无法验证返回值。我找到了几种可能的解决办法,但我不确定哪一种是最好的 案例一:

我有一个从
I1
ILogger
的适配器,实现如下:

class BAdapter() implements I1
{
   void logA() { // nothing }
   void logB() { new BLogger().log() }
   void logC() { // nothing }
}
我想编写JUnit测试来验证功能,但我发现它有点问题,因为我无法插入模拟对象而不是
BLogger
,也无法验证返回值。我找到了几种可能的解决办法,但我不确定哪一种是最好的

案例一:
void设置记录器(记录器l)
添加到
BAdapter
类中

class BAdapter() implements I1
{
   private Logger logger = new BLogger();

   public void logB() { logger.log() }
   public void setLogger(Logger l) { logger = l }
   .. //rest of methods
}
缺点:为什么要添加从未在“真实”非测试代码中使用过的setter

案例二: 在测试包中添加受保护的工厂方法和子容器
BAdapter

class BAdapter() implements I1
{
   public void logB() { createLogger().log() }
   protected Logger createLogger() { retrun new BLogger() }
   .. //rest of methods
}

class BAdapterForTesting extends BAdapter()
{
   protected Logger createLogger() { retrun new MockBLogger() }
}
缺点:我不确定这是不是一个干净优雅的解决方案,但我看不出有什么缺点

案例三: 使用抽象工厂模式

class BAdapter() implements I1
{
   public void logB() { AbstractFactory.getFactory().getBLogger().log() }
   .. //rest of methods
}
在测试中的某个地方:

AbstractFactory.setFactory(new MockLoggersFactory())
缺点:这太复杂了,不是吗

案例四: 例如,在执行日志记录时返回布尔值。例如

class BAdapter() implements I1
{
   Boolean logA() { return false; }
   Boolean logB() { return new BLogger().log() }
   Boolean logC() { return false; }
}
反对者:这有点伤人。为什么要返回一些值,而在“真实的”非测试代码中没有人需要它

更好的解决方案?
还有更好的吗?

从您的代码中很难确切地说出被测试的类想要实现什么,但是 我会使用案例一,并警告说我也会在“真实”代码中使用注入


注入的好处之一是它使类(在本例中是您的适配器)更加可重用。强制
logB
方法始终委托给
BLogger
的一个实例,在编译时将该行为设置为一成不变。如果我想将适配器委托给另一种类型的
记录器
,我不能使用它,因此它的可重用性稍差。

IMVHO创建代码,特别是供测试使用,而不是其他任何地方都不是一个好主意,因为它可能会暴露不应该使用的功能,然而,有些人可能会认为使用它很酷,并感到非常惊讶

这就给我们留下了案例3,它通常是一个非常好的方法,但是如果它只在测试期间使用,它仍然有点过分

我建议使用一些额外的模拟框架,比如PowerMock,它可以方便地更改类中的私有字段:

class BAdapter() implements I1
{
   private Logger logger = new BLogger();

   public void logB() { logger.log() }
   .. //rest of methods
}
然后在测试期间,将字段交换到某个模拟:

Whitebox.setInternalState(loggerInstance, "logger", new MockLogger());
更多信息:


模拟框架是测试过程中的重要资产,所以了解它们会有很大帮助。

我在《JUnit在行动手册》中找到了类似的方法。测试代码和其他代码一样是您代码的客户机,因此最好重新考虑或更新代码以满足单元测试需求。