Tuesday, October 16, 2012

Should tests influence the production code?

For a very long time I was told that tests should not influence the production code. By this I mean, that we should not change our design only in order to be able to test it. That was quite OK for me, since - you know - test are only tests - the production code is what matters and what drives the design. Tests should just shut up and follow. Right?

Lastly I am flipping the bit and diving into TDD and I must say - this is quite an adventure. At the beginning I was like a babe in the woods. It was not easy to start. Everything was harder - writing code, designing, thinking... I had to firstly write a test, and then the code - everything was upside down - I didn't even have the code to test. Damn! I started to think about basics again, and that... was really refreshing!

The Example

One day I was given a task to figure out changes in history lines of some objects and update the meta-data for each line accordingly - with ids of objects that were changed. For the simplicity of the example let's assume, that those objects were rectangles and I was interested only in their length.

Let's use an example:


As you can see (you have to excuse me my poor Paint skills), the rectangle A was changed only in the first step (transition from A1 to A2), rectangle B was not changed at all and rectangle C was changed only in the second step (transition from C2 to C3). As a result, there should be the id of the rectangle A in the Line 2, and the id of the rectangle C in the Line 3.

The task was not that hard and my very first thought was to create some meta-data generator that would accept a list of Lines and update each of them. Sounds easy, doesn't it?  

Code-first approach

If I didn't do TDD, I would go and write the code first - iterate through the Line list, then in each one iterate through all rectangles and for each of those find matching one in previous Line. Then I would compare them and update the meta-data. Plus some corner cases - handling one Line only, or something similar.

It would probably take me some time to write - iterate through two collections, find matching rectangles in previous Line, and then the comparison. After that - I would write tests - prepare data set up for the scenario - a lot of objects, a lot of mocking, and some verification. Probably this test would be quite big. Enough to say, that my objects were much more complex, than simple rectangles. ;)

However I tried to write the test first and it was just a pure pain... Creating so many rectangles, mocking so many other involved objects and thinking about the future code that I was going to write... This approach was just not testable at all... I took a break.

Test-first approach

After couple of minutes I returned to the problem and started to think what was wrong. I figured out, that there were just too many responsibilities in that not yet existing code. Single Responsibility Principle was broken even though not a single line was written. That made me thinking.

After short time I discovered some implicit concept - a Comparison Pair. I was trying to compare whole Lines, where I only required comparing pairs (A1-A2, A2-A3, B1-B2, B2-B3, C1-C2, and C2-C3). So I started to change my design in my head.

First - I had to split those Lines into Comparison Pairs, and then for each of those Comparison Pairs generate the meta-data. I created interfaces for those two responsibilities and mocked them in tests, which were simple, readable and were covering all paths. My generator was just delegating the work to those interfaces.

Then I created tests for splitting Lines into Comparison Pairs - again smoothly - followed by implementation. And the same for comparing those Pairs.

So they should, or should not?

And we are back to the original question - should tests influence the production code? If you are doing TDD, then you don't have a choice. Your tests shape your design - just like in my example.

If you are not doing TDD and writing your code first, then you may encounter some problems with testing it later. (Of course if you are not writing tests, then you won't have any problems - right? ;) ) When the code is hard to test, you can do one of following things:
  • give up and do not test
  • do the nasty work and write this huge and unpleasant test
  • add some helping methods in the code with this "awesome" annotation - @TestOnly
  • refactor the code so that it would be testable
The first two approaches do not influence the production code, but are leaving the code and tests in a mess. The third influences, but in a really ugly way - this annotation is a code smell. The last one also influences the code, but in a good way - makes the design better.

So to summarize - I think, that tests should influence the production code. They make the design better. They are helping with finding implicit concepts and following SOLID principles. Do you agree?

No comments:

Post a Comment