Unit-testing – Inheritance in test classes

compositiondesigninheritancetddunit testing

I have an interface Serializer with methods serialize and isSerializerFor. I created a first implementation of this using TDD, and ended up with a nice clean test case fully covering a nice clean implementation. To get a more concrete idea, here is one relevant commit.

Now someone else (who is not used to doing TDD) started with writing the second implementation of this Serializer interface. This person figured the test needed for the second implementation would have some overlap with my initial test. So an abstract base class for serializer tests was created, holding the methods that are suspected to be common to all serializer test cases.

I'm not happy with this for two main reasons. First of all, the only reason inheritance is used here is for code reuse, which is a big code smell. Secondly, this breaks the TDD cycle. If I now want to create another implementation of Serializer, and create a derivative of the base test class, I end up having to implement all production code in one step.

On the other hand, simply duplicating the common code in the test classes seems rather odd as well. I'm hoping composition can be used here in a sane way to avoid these problems.

This seems like a reasonably common situation. How would you solve this? What would you do differently?

Best Answer

First of all, the only reason inheritance is used here is for code reuse, which is a big code smell.

Different serializer implementations (like SerializerA, SerializerB, SerializerC) lead to different SerializerTest classes (SerializerTestA, SerializerTestB, SerializerTestC), which are all "Serializer testers", giving your a "is-a" relationship to the common SerializerTester base class, so inheritance is probably the right tool here.

If I now want to create another implementation of Serializer, and create a derivative of the base test class, I end up having to implement all production code in one step.

I don't think so. You start TDD by creating a test class SerializerTestA (derived from SerializerTester, with nothing more than creating a SerializerA object. This won't compile - test is RED. Then you implement SerializerA with nothing more than a constructor and empty method implementations for the Serializer interface - test is GREEN. Next, you add your first test method to SerializerTestA, which may just delegate a test call to a corresponding method in SerializerTester. Because of the missing implementations in SerializerA, your test fails - RED again. Then, you implement the first empty method in SerializerA, until your first does not fail any more - test status is GREEN again.

So, as long as you don't call any test methods from SerializerTester needing the full implementation of the Serializer class you can still do this step-by-step, staying on the classic TDD path.

Related Topic