A long time ago, it was scarce for a software project to have any automated tests written, on any level. It was clear though that developers were not infallible machines. Their code was also not, by any means, a 100% error-prone, never failing wonder (I know, we all think like that from time to time, myself included). I still remember the hype that automated tests were something that you simply cannot omit while developing software. And so, the team leads and architects insisted, literally by force (meaning bonuses) to write a certain number of tests per commit. It was a start, but most were thinking about their lost benefits rather than believing that, if practised every day, would lead to significant quality improvement of an application. Those were the Test-Last exclusive days.
At some point, a group of coders started to question a scenario where, we do not write our production code first, but we focus on the test itself. That, initially a bit crazy idea, began to give quite favourable results and gained immense popularity and interest in the programming world. The rules, as with chess, are simple enough to memorise within an hour. The real trick comes when you want to master that game. As with chess, it requires day to day practice and persistence to keep trying new things and learn new techniques. Then maybe after a few years, you could get close to calling yourself a master. If that is the path you would like to undertake, here are a few TDD common mistakes that you will definitely stumble upon on your automation artist journey. It is sometimes better to study the mistakes of others and avoid them rather than falling into the same traps yourself.
2) TDD mistake #1: focusing on both testing and production code
If you did not have much contact with unit testing before, TDD would be a bit easier for you to learn. For someone like me, who had a good few years writing test-last, it was a harsh journey, to say the least, to switch my frame of mind and my testing style to test-first. It is proven that a change of habit takes about 4 to 8 times more effort than to learn a skill properly from the beginning. It is no different I would say in TDD case, so lucky you if you are just starting your journey.
The bad habit that I have developed was simply this: write a bit of test and a bit of implementation, then a bit of test followed by a bit of refactoring the finally a bit of implementation to make the test pass. Do not get me wrong, some people code this way, and it is still way better than writing no tests at all or at the very end. Because as we all know writing tests at the end most likely ends up in writing no tests at all due to lack of time, management pressure etc.
Try to avoid such a style because what you are doing eventually, is writing and having an implementation in mind first and just confirming it with a unit test at the end. This is still driven by design and implementation, not testing. Not in its purest form but it still is. You eventually end up with more code than needed and design not as slick as it could be.
3) TDD mistake #2: overthinking it
"Between the acting of a dreadful thing and the first motion, all the interim is like a phantasma or a hideous dream."
This line is from Shakespeare's Julius Ceasar, spoken by Brutus while he was plotting to kill Ceasar. Shakespeare, being the master of understanding human nature, captured how a person may feel in that kind of situation. This could be translated as: In between the moment that you are hesitating to do something that you really do not want to do or do not know how to do, and the action itself, that whole time is a neverending dream. It is really neverending because the longer you stay in that moment, the harder it is to wake up.
Many of us, especially at the beginner stages, have problems with merely starting. The thoughts begin to stack up, every idea seems not that bad and possible, you begin to hesitate. After that, you get nervous as the clock is ticking and you have not made any progress at all. Those give up thoughts are next. Give up this TDD thing, I will just write what I am supposed to do and maybe later write some tests. You are done at this point. All of this could have never happened. Only if you start by writing anything that came into your mind. Then a make it pass. Then throw in another first idea into you test.. and so it goes. The machine has started. It picks up the pace and momentum, and there is no stopping it until it reaches its goal.. top quality solution covered with high-quality test suite.
4) TDD mistake #3: skipping the refactoring phase
Once we have made our test pass with a piece of logic, it is tempting to move on to extend the tests and make them more precise. That is a mistake, a big one. It is like driving a car without ever changing the oil, changing the tyres, break pads, waxing the body twice a year etc. Your car, which seemed to be a state-of-the-art wonder is now a driving hazard on the road and a wreck as you look at it. Do not let that happen to your application and always, I repeat always, obey the step 3 of the TDD cycle: REFACTORING.
Thanks to refactoring, our implementation is clearer and has better maintainability. Running the previously written test suite will ensure us that the logic has not changed and all tests still pass. So we do not need to worry about breaking anything.
Refactoring is like an icing on the cake. Imagine a situation where you start to climb the highest hill in the area, and after all the effort, when you reach the peak, you immediately begin to descend. Now, why would you want to do that.. stay a bit on the top, taking pride in what you accomplished by enjoying the fantastic view.
Wrapping your implementation with step 1 and 3 of TDD cycle, we end up with a tandem which just immensely boosts the quality of the code. Take advantage of that.
5) TDD mistake #4: applying tdd to legacy code
In most cases, legacy code that you will be dealing with is not even close to being maintainable and extendable. Having that said, we also imply that it is not testing-friendly. TDD is about writing code, and the design with a solution will follow. When it comes to legacy code, the code and the solution, however it is written are already there. It is not the best idea to start TDD cycles right there and then. Legacy code requires a different approach. It requires you to sit down, think really carefully what needs to be done to implement the change and not to break anything in the process.
I am sure you have heard that you should cover the public method that needs a change with a full set of unit tests before even touching it. That might be true if the method is relatively small. That happens very rarely though in a legacy codebase. The methods are mostly humongous, and to cover them, you would need tens if not hundreds of tests. All of that to change a single line? Well, for sure you would have 100% certainty that you did not break anything but being stuck on one line for a week or two would not make your manager ecstatic.
From my experience, the best way to handle legacy code with a flavour of TDD is to use the Sprouting Method/Class technique. You basically extract the smallest part of the public method possible into a separate class. You cover that with tests, and then you could start a few TDD cycles to get the actual change.
You need to congratulate yourself. I mean it. If you decided to be an advocate of TDD, that is respectable in itself. Even if you made all of the mistakes above and got trapped in pitfalls. There is absolutely nothing to be ashamed of and you should be proud that you took a dive straight into the deep water. On the other hand if you just would like to start off you journey you know have a list of what NOT to focuson. No you know, what to avoid instantly once you spot it!