This is my first ever post on dev.to! I have been thinking about doing this for some time, I finally had a good enough idea to share with everyone and the time. Now lets get down to the point. I love using test driven development have been using it for several years. I think it is a technique that every developer should at least be familiar with and understand what problems it is good at solving.
This past week I read this article https://blog.usejournal.com/lean-testing-or-why-unit-tests-are-worse-than-you-think-b6500139a009. While I liked the idea that we should take careful look at how we test and what kinds of tests we use, the author seemed very negative towards TDD. Even in the comment section of the article the hostility to TDD and unit testing in general continued. I have also read similar blog posts and articles in the past. These experience differ greatly from what I have seen from unit testing and TDD in general. It is clear that there some misconceptions and myths out there about TDD. Some of it is because of well meaning people trying to teach others about it, and in their zeal they leave a bad taste in people's mouths. Others it is misunderstanding of what TDD is. So today I wanted to go over these misconceptions and myths about TDD, and help people see TDD does not deserve the bad rap many give it.
I think where most people get this idea is from the first rule or step of TDD, you must start from a failing unit test, and only when you have a failing test are you allowed to write any production code. So many take this as you must have 100% code coverage, because you can't have code without tests! But this is wrong. There are cases where TDD is not a good fit. One example is working on a front end GUI of an application. There is some code that is really hard to test, but really easy and trivial to write. So in those cases we would not write a unit test, and instead isolate the that hard to test code so we test everything else.
This article from Martin Fowler about test coverage (https://martinfowler.com/bliki/TestCoverage.html) made some good points. High code coverage does not mean your code is high quality. If we make code coverage the goal instead of a tool, we can end up with high number of useless tests that get our code coverage numbers up, but do little to improve things, and may in the end make things worse. In the article above Martin Flower made this point:
Like most aspects of programming, testing requires thoughtfulness. TDD is a very useful, but certainly not sufficient, tool to help you get good tests. If you are testing thoughtfully and well, I would expect a coverage percentage in the upper 80s or 90s. I would be suspicious of anything like 100% - it would smell of someone writing tests to make the coverage numbers happy, but not thinking about what they are doing.
Many have heard if you follow TDD all your coding problems will be solved! If you just follow the steps, your code will come out well designed and no need to do any upfront design! Others have complained that TDD causes damage because in the pursuit of test-ability and they feel the code has too many layers or forced to do things just for the sake of tests.
The truth is you can write well designed software with or with out TDD. Just because you follow TDD will not automatically mean that you code will be well designed or that your code will be poorly designed. You are the developer/engineer. When writing any code we must spend some time upfront giving thought to how are we going to organize the code, how will the major parts of the application be structured, and how they will communicate with each other.
What TDD is good at is once you at least have an idea of where you want to go, it will guide you along the way and you can test out these ideas before you end up writing a lot of code. It also forces you to constantly self review your work and see if there are any patterns emerging that might alter the design you originally came up with. You may even realize that your design is not going to work and you need to start over.
Many of us in the TDD camp often promote the use of dependency injection to allow us to control what dependencies are used in a test, and allow us to inject mocks or stubs into the code we are testing so that the tests run fast. But is this a requirement of TDD? No, the only requirement is that the tests run fast, and that you can run them reliably on your own computer. There is nothing in TDD that says we can't have integration or other kinds of tests in the mix of things. The point is you are in control of how you want to design and layout your code. TDD just allows you to constantly self reflect and refactor your code.
Anther sub issue here is the fact that you don't need to design your tests. this is wrong also, you need to put the same though and care that you put into your production code into your test code. Robert Martin wrote a great blog post on the need for design in tests (http://blog.cleancoder.com/uncle-bob/2017/03/03/TDD-Harms-Architecture.html). Here is a great quote from the article:
Yes. That’s right. Tests need to be designed. Principles of design apply to tests just as much as they apply to regular code. Tests are part of the system; and they must be maintained to the same standards as any other part of the system.
Robert Martin goes on to make the point that many demos of TDD show a direct one to one relationship between the unit tests and the production code. In fact many who start with TDD emulate this kind of testing. The issue here is if we go down this road, it will end up in code the is highly coupled to the tests, and will make it painful to change things up later. So tests, just like our production code require careful thought to avoid these issues.
Many have the feeling that if you using TDD you don't need integration tests or any other kinds of tests for that mater. You will be just fine with unit tests. The kinds of tests we write while using TDD help a lot with logical issues, and ensure the code works as you the developer expect that it should work. What they do not do is show you issues when you hook everything up together. They do not show you that you missing a requirement or that you made a wrong assumption about an existing requirement.
So many who have been using TDD for some time know what TDD is good at and what it is not good at, and recognize that unit tests are just one tool in the tool box, and we must also have acceptance tests, integration tests, and end to end tests to really flush out all the possible issues in an application. Any one of these kind of tests can be over used and abused. It is up to us as engineers to choose the right test for the job at hand. We are the ones shipping the code, and it is up to us to pick the right level of testing to put in place so when we change something it does not break something else and we are confident things work.
Here is a good example of why reliance on only unit tests is not a good thing from Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce:
Nat was once brought onto a project that had been using TDD since its inception. The team had been writing acceptance tests to capture requirements and show progress to their customer representatives. They had been writing unit tests for the classes of the system, and the internals where clean and easy to change. They had been making great progress, and the customer representatives had signed off all the implemented features on the basis of the passing acceptance tests.
But the acceptance tests did not run end-to-end -- they instantiated the system internal objects and directly invoked their methods. The application actually did nothing at all. Its entry point contained only a single comment:
//TODO implement this
Side point that is a great book that explains how to use all levels of testing in real world software project. Good read!
Because of the zeal of many proponents of TDD, many get a bad taste in their mouths when they see them cheer lead TDD. The fact is that yes you can write well tested, and well designed code with out TDD. I just think it is harder to do it with out TDD. On the same hand, you can still write badly designed, confusing mess of code while following TDD.
Also consider the fact that it takes a lot of time and practice to get comfortable with TDD. Many devs have been working for years with out TDD. If I came along and forced them to start using it, they would be way out of their comfort zone and make many mistakes. For myself personally it took me few years before things clicked in my head. Some devs really do not have the time to do this for various reasons. I think if you do have the time, it is well worth the investment. At the same time we also need to be realists and realize that there no one true way to code. There was a time I started to think this way, but I have realized that really all that matters is the out come. Is the code easy to understand? Is the code well tested where it needs to be so I am not afraid to change it? Can we easily change it if we need to? This is the real world and we have to be reasonable.
I hope this clears up some misunderstandings out there about TDD. Many have a bad taste form how others may have forced this upon them or how they where introduced to it. Also in our zeal to let others know how awesome TDD we forget everything else that many in the TDD community have learned through experience and learning from others. Remember TDD is just a tool, just as useful as any other tool we use to write code.