Imagine you have an application and you want to make unit tests and functionnal tests over it (not quite hard to imagine). You might have an abstract class, let's call it AbstractTestClass, from which all your unit tests extends.
AbstractTestClass would look something like this (using JUnit 4) :
class AbstractTestClass {
boolean setupDone = false;
@Before
public void before() {
if(!setupDone) {
// insert data in db
setupDone = true;
}
}
}
Here is what I'm struggling with. I'm having another abstract class which test the web interfaces :
class AbstractWebTestClass extends WebTestCase {
boolean setupDone = false;
@Before
public void before() {
if(!setupDone) {
// here, make a call to AbstractTestClass.before()
开发者_如何转开发 // init the interfaces
setupDone = true;
}
// do some more thing
}
}
It's pretty much the same class, except that it extends WebTestCase. This design could give me the possibility to have the same data while unit testing than when testing the interface.
Usually, when dealing with such issue, you should favor composition over inheritance or use a strategy pattern.
Unfortunately, I don't quite like the idea to favor composition over inheritance in this particular scenario and I don't see how I could use a strategy pattern, there is probably a design flaw and I can't quite see the solution.
How could I design this architecture in order to achieve my goal.
I would implement this in the following way:
class Example {
class LazyInitStrategy implements Runnable {
private final Runnable operation;
private boolean done = false;
LazyInitStrategy(Runnable operation) {
this.operation = operation;
}
@Override
public void run() {
if (!done) {
operation.run();
done = true;
}
}
}
private final class AbstractInit implements Runnable {
public void run() {
// insert data in db
}
}
private final class AbstractWebInit implements Runnable {
public void run() {
// here, make a call to AbstractTestClass.before() init the interfaces
}
}
class AbstractTestClass {
final LazyInitStrategy setup = new LazyInitStrategy(new AbstractInit());
@Before
public void before() {
setup.run();
// do some more thing
}
}
class AbstractWebTestClass extends WebTestCase {
final LazyInitStrategy setupInfo = new LazyInitStrategy(new AbstractWebInit());
@Before
public void before() {
setupInfo.run();
// do some more thing
}
}
}
Sure this is very simplistic solution but it should eliminate if/else logic duplication for checking if setup was done. Using Runnable is optional, I did this just for demo purposes, in read world you probably will use another interface.
The important thing to accomplish is not to duplicate code. In this situation I would create a
MyScenarioTestUtil
class that has a bunch of static methods on it that sets up the data as you need to. You would invoke the utility methods from the setup. That way you keep all the code in one place.
Its really just a semantics difference from using composition...
I think that the design is wrong in general. You shouldn't use inheritance in unit tests at all. Tests should be isolated and really plain. Very often, like in your case, it's necessary to prepare some supplementary objects, that will help test methods to do their job. In such a case you should define builders of such objects, and place them somewhere outside of test cases.
For example:
public void testMethodThatNeedsSomePreparedObjects() {
Foo foo = new FooBuilder()
.withFile("some-text.txt")
.withNumber(123)
.build();
// now we are testing class Bar, using object of class Foo
Bar bar = new Bar(foo);
}
Thus, you need FooBuilder
to be defined somewhere else, and this class will do all the work you're now trying to do using stategy pattern or inheritance. Both approached are wrong, when dealing with unit tests.
精彩评论