Search code examples
inheritanceinterfaceconstructortddcomposition

Derivation, composition, constructors, interfaces and TDD


When developing TDD your objects 'grow' as code evolves . First they include only some functionalities, and later you add new ones. You can basically do it with composition and/or inheritance.

At the same time, you almost always use interfaces (for test, and for production code) to ensure low coupling, dependency inversion, dependency injection and mocking.

How do you MANAGE this?

I mean, for example: you have created a class, and now it must include a new attribute (for example, a Person now can have an e-mail). What to do?

  • Modify the class, adding the new attribute: Then you must ADD new constructors. But if you modify existing constructors then you must refactor all TEST involved, not only production code. You could try to keep all constructors as you add new attributes, but at the end mostly become unusefull, obsolete (only exist because of TESTS), with a complex logic of 'constructor calling another constructors' (or use some kind of 'factory')
  • Derive the class: This is more compilant to OCP. But if you make a real TDD, with small steps, if you always derive, starting from a very simple class, you would end with a terribly complex and somehow unneccesary inheritance. And besides, it's supposed you always must test only PUBLIC exposed members. So, again, as base classes become protected, must refactor tests to access the public ones.
  • Compose: Composing has no problem with constructors, but is not always a good solution. As for our current example.

And a similar thing happens, in some way, with interfaces.

So, as code grows, constructors an interfaces must evolve. And I have three precepts in mind:

  • You should avoid refactor tests.
  • The production code must not keep 'code only intended for testing' (i.e. constructors)
  • ISP states to use specific interfaces. As code grows many of them become unnecesary, as they are only used for testing (i.e. mocking) classes that, in many cases, should be protected or private.

So, as I said... how can i manage all this while developing TDD? I know it's a question for a long answer, so, if you could refer to some good article, or resource


Solution

  • Changing your tests is OK

    What you're describing (Person now has an Email) is a change in the specs. Unit Tests are supposed to be your specs, so it's only normal that you have to change them when introducing new data.

    This kind of test fragility is a trade off for quality, a necessary evil.

    Don't overengineer

    By trying to apply OCP, inheritance and composition just to add one field, you're only making things more complicated for yourself. As you pointed out, it results in production code monstrosities just for the sake of testing, and you also have to modify the tests anyway.

    OCP might be good if you're adding a new behavior in a family of existing behaviors, but it's not the case here.

    Make changes more manageable

    There are tactics to make your tests more resistant to small changes, but they are mainly implemented at the test level, not in production code. Besides, opportunities for this are easily spottable if you frequently look for duplication in your tests, during the Refactor step of the TDD cycle for instance.

    One of them is to encapsulate your "system under test" creation in a Factory Method and call it instead of the normal constructor in each of your tests. Make most of its parameters optional with a default value. This will allow you to make changes (e.g. add the Email field) in one place and all the tests will benefit from it. This doesn't save you from modifying the test suite, but you do it just in one place.