5

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?

  • You could create a test class that tests against the interface (i.e. isSerializerFor) and use something like parameterized tests. Anything implementation specific could then be in a different test specific to just that implementation. – Mike Dec 20 '13 at 14:21
  • Are you looking for a solution to the people problem (ie. go talk to whoever created the abstract base class) or are you looking for technical solutions that would resolve something else? Please edit your question to make it more clear what you need answered. – GlenH7 Dec 20 '13 at 14:41
  • @GlenH7 I'm looking for a solution the the issues I outlined. Not sure which part you think is unclear - can you be a bit more specific? – Jeroen De Dauw Dec 20 '13 at 14:55
5

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.

  • In the environment I am using, which is PHP+PHPUnit, calling tests in SerializerTester is not needed for them to run. PHPUnit will run all test cases in your test class, which includes all inherited ones. I could change the methods in the base class so they are regular methods and not test methods, and then explicitly invoke them one by one. Though then one ends up with those invocations in every test, which is not far from the original situation in terms of duplication. – Jeroen De Dauw Dec 20 '13 at 15:36
  • @JeroenDeDauw: The original situation was that you had to duplicate the test implementations, now you only have to duplicate the test invocations, I would consider that as a big difference. Nevertheless, for TDD you need a way to switch your tests "on" incrementally and individually for every serializer class. If that really bothers you still and you want to remove that duplicate invocation code after you have all your tests in place ... – Doc Brown Dec 20 '13 at 18:10
  • ... then you could add an additional SerializerTesterAll class, derived from SerializerTester, with one test method for each regular method in SerializerTester, calling that method, and once you have your SerializerTestA complete, refactor it by changing its base class from SerializerTest to SerializerTesterAll and remove all the duplicate test invocations. – Doc Brown Dec 20 '13 at 18:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.