When to Use TDD and When Not to Use TDD

by Hit Subscribe 8. November 2018 06:55

When I started to learn about unit testing, I was also introduced to test-driven development, or TDD for short. TDD defines a process for how to write unit tests and the code being tested. This process is a great way to approach most development work, but sometimes it might not make sense. It can be hard when you're starting out with TDD to know when you should follow it and when you should skip it. To help clear things up, I'm going to share some examples of when you'd want to use TDD and when you can do something else.

When to not use TDD

What Is TDD?

First off, what is TDD? Test-driven development is the practice of writing unit tests before writing code. You'd start with a test that checks for a result you'd expect from your completed code. Initially, your test should fail because you haven't written the actual code to create that result yet. This failing test is step number one in the TDD process ("red," as shown in the diagram below).

Next, you'd write code that makes your test succeed. As you build out your production code, you'd verify that all tests are still passing validation ("green"). Now that your tests are all passing, you'd look at areas to improve your code or tests while not breaking your tests ("refactor"). Repeat this red, green, refactor cycle until you have enough tests to cover all the expected results.

red green refactor TDD cycle

For a deeper look at what TDD is, you can check out NCrunch's Test-Driven Development: A First-Principles Explanation. But as for this post, we're moving straight to a discussion of when TDD is appropriate.

When to Do TDD

There are many reasons why you should follow TDD. TDD lends itself really well to when you have a pure logic function that you need to write. When the work you need to do has a clearly defined set of expected inputs and outputs, it's a great signal that you should use TDD to build out your tests and code. In fact, this works so well that you should look for opportunities to do so. Anytime you recognize that your code needs to make decisions or calculations, you should break it out into its own method and use TDD to write that method.

I find that TDD also works really well for when you're trying to define layers within your application. When coordinating different modules or integrating with systems you don't control, TDD can help find the correct seams between layers. To do this, start with the entry point to the system and try to integrate with all of the dependencies. When it becomes too difficult to test because the setup is complicated, move that hurdle into another layer. Now you can mock that layer in your tests to make them easier. After completing those tests, you can go back to that new layer and work on it in isolation. Continue with this pattern until each layer is well defined and easy to test isolated from one other.

When to Unit Test and Skip TDD

I believe that most of the arguments I've heard against TDD come from a misunderstanding of what TDD is or how to do it properly. If you want to read some common arguments against TDD and why those arguments don't hold water, check out 5 Excuses for Not Doing TDD, Debunked. There are, however, some instances when it might not make sense to follow TDD strictly, but you should still write unit tests.

If you have an existing codebase that doesn't have any unit tests at all, it can be difficult to start doing TDD. It also might not be worth it to use TDD on something that's already working. In these situations, when you make changes, you should definitely start by creating some unit tests around the existing system. These tests should be used to prove that when you make changes, you're not breaking a working system. As you build a suite of tests, you can look for opportunities to start following TDD. Don't try to rewrite or start from scratch something that's already working just so you can use TDD.

Writing unit tests can be a great way to learn about how a system works. Write unit tests around any given portion of the system; see how the code handles different inputs. Hopefully, you'll start to understand interactions and patterns. Running unit tests like this will be a much faster way to learn about the code than trying to run the entire system. While learning about the system, you're also adding value by writing tests.

Quality Assurance

Someone in a quality assurance role should consider writing unit tests as well. I've found in the past that QA generally happens at the end of a block of work and only captures end-to-end scenarios. By writing unit tests, their work can be done earlier on in the process and at a smaller scale. Doing this reduces the complexity of their work while speeding up the feedback time to developers. I've also seen companies shift from wanting a QA person that just does record-and-playback-style testing to wanting a QA person that can write code and understand the systems on a more detailed level. Learning how to write unit tests, I believe, will distinguish people in a QA role from others and will serve them well in the future.

When to Not Unit Test At All

Test-driven development is not always the correct approach to take when writing code. However, there's rarely a time when adding at least some unit tests won't be helpful. One of the only times I wouldn't write any unit tests is when writing something that's going to be thrown away. I'm talking about creating a demo or proof of concept that you need to work just long enough to show off. After you find out if what you have is worth moving forward with or not—that's when you should think about testing.

One stumbling block I often see is a desire to cover everything with unit tests. Unit tests and TDD are fantastic tools for writing good code. They inspire trust that what you wrote will work correctly. However, there's a point of diminishing returns where more tests aren't worth the effort. Code that you don't write yourself or code that your language or framework takes care of for you is code that probably doesn't need to be unit tested. Think of a sum function provided by a math library. You can probably assume that the sum method works without writing tests for it.

When you're doing simple CRUD—create, read, update, and delete operations—you probably don't need to follow TDD. Use the repository pattern to isolate the operations to the database. If you can completely isolate these operations, you shouldn't need to worry about unit testing them. You should test that your repository methods are called when you expect them to be, but testing the methods themselves isn't needed.

What Should You Do Next?

Now that you know what to look for, it's important to recognize that writing any sort of automated tests is better than writing none. As you get experience writing unit tests, try to recognize the times you're dealing with pure logic and try out TDD there. When you're not actively working on your project, you can practice TDD by following a coding kata. But at a minimum, remember: you should be writing unit tests for your code.

This post is by Christian Peters. Christian a web developer who has worked primarily in the financial service industry. He's passionate about learning and sharing knowledge, and he's always looking for ways to experiment with new things, even if they're not tech related. In the past few years, he's found that growth in non-tech specific activities has improved his ability to provide solutions more that just writing code would.



Month List