Take the world as it is, not as it ought to be
— German Proverb
With the passing every single year in the world of software development I find myself more and more convinced of the fact that every project and every team is unique. They have unique situations and requirements. One should accept and adopt tools and processes only to the extent that fits the personality and culture of the project team and caters to the needs of the project. Let me share a tale of a team that I worked with in recent times.
The team was maintaining a suit of unit-tests around the code. Though the code coverage was decent, the execution coverage was really poor. If you ran the test-suit, at any given point, many if not most tests would fail. Not a happy situation to be in, especially, if you are willing to leverage the benefits of units tests. One of the most important benefits of having a unit tests around your code is to provide you with the courage and the confidence to refactor your code fearlessly. You should be able to make a change in the code and run the tests to make sure everything is alright. And this is what the team was badly missing with it’s tests. Any “red” test that we came across would not clearly conclude that weather the test failed because of bad data or the functionality was actually broken. We dug into the each and every unit-test. To our relief we found almost all the failing tests were failing because bad test-data and not broken logic. Believe me or not this is the case with many teams claiming to maintain unit-tests.
We fixed the test-data, created dummies, mocks and stubs where required and made the test-suite hundred percent pass. We would setup data before running the tests and undo the changes to the database (or other states) after the tests ran. Hence, the test-suit always remained automated.
The next logical question then was how are we going to make sure that the tests we fixed this time around would not run into the same issue in the future. The answer was obvious – Continuous Integration (CI). With Microsoft’s Team Foundation Server (TFS), it was fairly easy to set up the build for Continuous Integration. Once we configured the CI, we realized that the builds were heavy and slow as many of these tests talked to the database. It was turning out to be an overkill for the return we were achieving. Again, we followed the “horses for courses” strategy. The pragmatic solution that we all agreed upon was that instead of queuing a build at every check-in, we should trigger the test-run only in the nightly builds. That way we were sure that code integrates at least once every day. Something we happily lived with.
The idea behind telling the story is that tools, process and best-practices are not meant to be followed like commandments. One should certainly look out for and be aware of what’s working for most teams out there, borrow the idea as and when necessary but should not impose them on the team. They should be adopted and accepted according to the teams comfort and adopted as per the teams need.
As you would have already understood that what we followed was not even close to be called TDD. We just maintained unit tests. But at the end of the day we delivered, and delivered well. Isn’t that all that matters? Isn’t it more important to do what works than what is right? After all, isn’t it true that there is no single key for every lock?