Writing Unit Tests for inherited classes in Java

Viewed 7664

Consider the following simple class hierarchy in Java

class Foo {
    protected void someMethod(Bar bar) {
        ...
    }

    protected void someOtherMethod(Baz baz) {
        ...
    }
}

class EnhancedFoo extends Foo {
    @Override
    protected void someMethod(Bar bar) {
        ...
    }
}

I now start writing JUnit unit tests for these two classes. Since the contracts for the method someMethod are same for both the classes, I need basically exactly the same test methods (concerning the someMethod method) for both the classes, which leads to code duplication. Doing this for a much richer class hierarchy with multiple overwritten methods, it just feels like inheritance is bad for testability.

Also, even though the method someOtherMethod is not overridden in ExtendedFoo, I need to include the pertaining tests for ExtendedFoo because that is still the contract and this extended class and unit tests should test this.

Is there some other way of organizing hierarchies in Java that is better for testability? Is there some JUnit construct that would help alleviate this problem?

3 Answers

One approach we used when we had a very similar scenario was to also reuse the est classes:

class FooTest {
    @Test
    public void testSomeMethodBar() {
        ...
    }

    @Test
    public void void someOtherMethodBaz(Baz baz) {
        ...
    }
}

And extend it for subclass tests:

class EnhancedFooTest extends FooTest {
    @Test
    public void testSomeMethodBar() {
        ...
    }
}

JUnit will run this specific overridden test method, and also the other default tests in FooTest. And that eliminates unnecessary duplication.

Where appropriate, some test classes are even declared abstract, and are extended by concrete test classes (tests for concrete classes).

As I mentioned in my comments I think the tests should be decoupled from the implementation and be "use-case-driven". It could look like that:

interface Foo {
    public void doSomething(...);
}

class DefaultFoo implements Foo { ... }
class EnhancedFoo extends DefaultFoo { ... }

class MyUseCaseTest {

   private Foo foo = new DefaultFoo(...); 

   @Test
   public void someRequirement() { ... }
}

class AnotherUseCaseTest {

   private Foo foo = new EnhancedFoo(...); 

   @Test
   public void differentRequirement() { ... }
}

The best would be to get rid of inheritance whatsoever, but it's a different topic...

Quoting from your question and comments ,

As far as I understand, unit tests assume classes and their methods to be black boxes and test their contracts.

And

Since the contracts for the method someMethod are same for both the classes, I need basically exactly the same test methods (concerning the someMethod method) for both the classes, which leads to code duplication.

Both are wrong assumptions in the context of generic unit test concepts and might be correct assumptions in the very narrow context of TDD. You haven't tagged your question for TDD so I am just guessing that and TDD is all about what is acceptable & not necessarily very robust from code perspective.

TDD never stops a developer to write more comprehensive unit tests that satisfy him/her and not only contract.

You have to understand that unit tests are developer tools to assure for method accuracy and it doesn't assume methods as black boxes - it is supposed to test contract as well as implementation details ( code coverage ) . A Developer shouldn't write unit tests blindly like for trivial methods ( like setter / getter methods ) .

When you go in detail for code coverage , you will find that you will have to write multiple test methods for a target method covering all the scenarios.

If implementation of below method is not changed in class - EnhancedFoo , why write unit tests for it? It should be assumed that parent class tests would be covering it.

protected void someMethod(Bar bar) {
    ...
}

You write unit tests for methods that you change or add and shouldn't be worried about inheritance hierarchy.

I am simply trying to emphasize the importance of word unit :)

Related