I have an adapter from I1
to ILogger
implemented like this:
class BAdapter() implements I1
{
void logA() { // nothing }
void logB() { new BLogger().log() }
void logC() { // nothing }
}
I would like to write JUnit test, that verify the functionality, but I found it a bit problematic, since I cannot inject my Mock object instead of BLogger
, or verify return value. I found several possible solution, but I am not sure, which is the best.
Case One:
Add void setLogger(Logger l)
to the BAdapter
class.
class BAdapter() implements I1
{
private Logger logger = new BLogger();
public void logB() { logger.log() }
public void setLogger(Logger l) { logger = l }
.. //rest of methods
}
Cons: Why to add setter which is never used in "real", non-testing code?
Case Two:
Add protected factory method and sublcass BAdapter
in test package.
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() }
}
Cons: I am not sure, if this is clean and elegant solution, but I don't see much cons here.
Case Three: Use Abstract Factory pattern.
class BAdapter() implements I1
{
public void logB() { AbstractFactory.getFactory().getBLogger().log() }
.. //rest of methods
}
And somewhere in tests:
AbstractFactory.setFactory(new MockLoggersFactory())
开发者_开发问答Cons: This is too complex, isn't it?
Case Four: Return Boolean, for example, when logging was performed. E.g.
class BAdapter() implements I1
{
Boolean logA() { return false; }
Boolean logB() { return new BLogger().log() }
Boolean logC() { return false; }
}
Cons: This is kind of wourkaround. Why to return some value, when nobody needs it in "real", non-testing code?
Better Solution? Is there anything better?
From your code it's hard to tell exactly what the class under test is trying to achieve but I'd go with Case One with the caveat that I'd use injection in the 'real' code too.
One of the benefits of injection is it makes the class, in this case your adapter, more re-usable. Forcing the logB
method to always delegate to an instance of BLogger
sets that behaviour in stone at compile time. If I want the adapter to delegate to another type of Logger
I can't use it and it is therefore slightly less re-usable.
IMVHO creating code especially to be used by tests and nowhere else is not a good idea as it may expose functionality that is not supposed to be used, yet some may think using it is cool and be badly surprised.
This leaves us with case 3 which is a generally very good approach, yet if it is only to be used during testing its still a bit of overkill.
I'd suggest using some additional mocking framework like PowerMock which can change private fields in classes in convenient way:
class BAdapter() implements I1
{
private Logger logger = new BLogger();
public void logB() { logger.log() }
.. //rest of methods
}
and then during testing swap the field to some mock:
Whitebox.setInternalState(loggerInstance, "logger", new MockLogger());
more info: http://code.google.com/p/powermock/wiki/BypassEncapsulation
Mocking frameworks are great asset during testing so knowing them helps a lot.
精彩评论