我有一个从I1
到ILogger
的适配器,如下所示:
class BAdapter() implements I1
{
void logA() { // nothing }
void logB() { new BLogger().log() }
void logC() { // nothing }
}
我想编写JUnit测试,验证功能,但我发现它有点问题,因为我不能注入我的Mock对象而不是BLogger
,或验证返回值。我找到了几个可能的解决方案,但我不确定,哪个是最好的。
案例一:
将void setLogger(Logger 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?
案例二:
在测试包中添加受保护的工厂方法和sublcass 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())
缺点:这太复杂了,不是吗?
案例四: 返回布尔值,例如,执行日志记录时。 E.g。
class BAdapter() implements I1
{
Boolean logA() { return false; }
Boolean logB() { return new BLogger().log() }
Boolean logC() { return false; }
}
缺点:这是一种wourkaround。当没有人在“真正的”非测试代码中需要它时,为什么要返回一些值?
更好的解决方案? 还有什么更好的吗?
答案 0 :(得分:2)
从你的代码中很难确切地说出被测试的类正在尝试实现什么 我会选择Case One,但我也会在“真正的”代码中使用注入。
注入的一个好处是它使类(在本例中为适配器)更易于重用。强制logB
方法始终委托给BLogger
的实例在编译时设置该行为。如果我希望适配器委托给另一种类型的Logger
我不能使用它,因此它的可重用性稍差。
答案 1 :(得分:0)
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());
更多信息: http://code.google.com/p/powermock/wiki/BypassEncapsulation
模拟框架在测试过程中非常重要,因此了解它们有很大帮助。