I was preparing yet another session about Test Driven Development (TDD) (I do quite a lot of these as part of my position) and I thought it would be a good idea to allure the audience at the beginning by showing some statistics.
The two main questions that interested me the most were:
- As software developers, how would we describe our feelings towards writing test code (unit/component/integration tests)?
- What percentage of software developers actually practice test first approach such as TDD/BDD (Test/Behavior Driven Development)?
I find the first question interesting because in recent years, in most companies, software professionals have come to the conclusion that manual QA is simply not scalable. Therefore we should rely heavily on automated tests (that also comes in adherence with agile transformation organizations go through).
And who would write these “new” automated tests? In most cases — the developers themselves (there are reasons for that which we”ll not cover in this article).
So, if it is part of the job, it gives value, and it involves coding (which software developers are supposed to love), we should like it.. right? Well, not exactly.
The second question interests me because TDD has been around for over 20 years now. I use TDD and teach TDD so I see how people struggle with it, leave it, or even object to it from the very beginning. Even though Test first approach just might be the remedy they need in order to find the “love” that’s missing in the first question.
When in need for answers, I went straight to Google to find my desired statistics but unfortunately, they were nowhere to be found. Except for some articles demonstrating some opinions as to why people like/dislike or use/not use it, no statistics what so ever.
Well, if Google can’t help so Google forms can probably do the trick, so I’ve decided to collect my statistics by publishing the following quick survey that includes the two questions above.
The survey is still accepting answers(I would appreciate if you”ll add yours) but after several hundreds of responses, the results are as follows:
The results for the first question are actually really surprising. Can it be that almost half of the developers actually like writing test code? I think not.
Unfortunately, as replying to the survey is not mandatory, I feel that most people who answer it have some affinity towards testing, and this affinity actually affects the answer they choose. In my opinion it’s more of a 80%-20% towards disliking test code than actually liking it.
Anyway, let’s assume these are the results — still, we have a major problem. ~55% of software developers do not like writing test code, and the problem with not liking something you do a lot is that it makes you to do it badly.
Where is the love..?
I believe there are 3 main reasons for the lack of “love”:
- Education: In every establishment that teaches programming (computer science/software engineering), whether it is a prestigious university or the cheapest online course, testing is almost always taught as a small chapter at the end of the syllabus — just assuming there is enough time left..correct?
Only later when people start working in the industry and are suddenly required to write tests they start doing it, usually as an afterthought, and changing that mindset is extremely hard.
- The Afterthought effect: I remember myself standing in a daily stand-up saying: “I’ve finished, it’s working.. only tests left”. This sentence makes no sense. If I’ve finished, why do I need to write tests now? Because management wants it? In order to feed some code coverage daemon?.
What if by writing tests we need to refactor/rework the code a bit because we’ve discovered it is not testable/usable enough? Should we test it again in other ways? All the above usually causes poorly written tests and bad coverage of scenarios, which leads to overhead and lack of trust in the test suite.
The afterthought effect also encourages the vicious cycle introduced in the book Test Driven Development by example by Kent Beck :
We are always busy and loaded with work, there isn’t a single sprint (or other time unit) that the product owner tells us, you know what? You can rest a bit and we’ll postpone the release. Bugs are discovered, customers have issues that requires our attention - programmers are truly busy. What can be skipped if we are so busy, yes, it is indeed testing.. Even if testing is part of the Definition of Done defined by your department, hard deadlines are sometimes stronger. No time for tests leads us directly to software defects, which only increases the stress and load. It is a vicious cycle which we need to somehow break out from.
3. The misinterpretation of a “unit “: At some point of a discussion/research about testing, the following pyramid emerges:
It educates us that we should put our main focus on unit tests as they are cheap and easy to execute. It also says that the majority of tests should be unit tests and that these types of tests should run in isolation. The combination of this pyramid with false definitions of a unit leads us to some destructive bad habits:
a. Enormous amount of tests. If every function/method (at least public) should be tests in isolation, we end up with thousand of tests. This gigantic test suite is part of our code base and needs to be maintained accordingly.
b. Excessive use of mock objects. If everything needs to be isolated, all dependencies of a given class needs to be mocked. That causes developers to spend lots of time writing mocking code or using a mocking infra which basically most people are not very fond of.
Now, what happens when requirements change and we want to change the code accordingly? we need to change the tests, sometimes — hundreds of them and perhaps the mock objects as well. This leads us to Refactoring hell which causes us to dislike the test code even further.
What can we Do:
The TDD “Mantra” helps us with many ways, but the main thing it does in my opinion is making testing, or better phrased - developing according to executable examples of the requirements - a part of the development process.
This completely eliminates the afterthought effect discussed above.
The other things that can be applied in order to increase the “love” can also be applied if you are not practicing test first, but test first definitely encourages them.
- A “unit” is a business flow. The test doesn’t “care” if you have one method, 10 methods or 10 classes involving that flow, if these entities are involved in that business flow, so be it. In fact the rule of thumb would be- test should not be aware of the internal structure of the code. This reduces the number of tests dramatically without hurting code coverage, and of course reduces the number of mock objects we need to maintain. It also allows for ongoing refactoring and for natural evolution of the code base.
- We Mock only external boundaries of our application (DB, web, I/O), we never mock an internal layer of our application. In addition, we don’t hurry to mock every external boundary, we mock it only when we have to (performance/Access/Security/Complexity /Stability). When we decide to mock a dependency, we search for an in-memory implementation that will give us the same API, only if it doesn’t exist, is not suitable or has its own penalties, we write our own mock object.
This highly increases the value of our tests, the less mock objects being used the more value we get.
- Test code is a 1st class citizen and is as important as production code. We treat our test code as if it were production code (Clean code, Adhere to SOLID principles etc.). This keeps the interest and challenge of producing high quality code for the test suite and of course creates a maintainable code base. In systems that are covered with automated tests, if the test code is hard to change and maintain, the entire code base is hard to change and maintain as well.
Where should we start?
First, try to deeply understand the points above, see if you agree with them or not, and most important try them out on your own code base.
Regarding TDD, it usually requires a significant change of mindset. I would recommend to start by doing TDD code KATAs, getting familiar with the idea and concept, afterwards, choosing a feature in production and trying to implement it using TDD and pair programming. There is a great and magical effect when people sit together and try out something new, it is described best in the illustration below: