Aug 9, 2019

How I learned to stop worrying and love test-driven development

Test-driven development (TDD) is not a new technique, but it is still not very widespread — neither is it well understood. Many people are only discovering it now, and many of them give up because the way they implement it doesn’t work for them.

At first, many developers are enthusiastic about it; but then, writing tests start to feel like a waste of time.

It can indeed be a waste of time if you are doing it wrong. A common mistake is to make tests an afterthought, an optional add-on. In fact, they work best when they are a part of the design process.

Be honest to yourself: how much time are you spending on writing down your design requirements on paper or making ad hoc prototypes?

Anyway, papers go to the trash, and prototypes are scrapped. Imagine that your design requirements are automatically verifiable and executable, and continue to serve you and your team. Do you like the idea?

Executable specifications

Automated tests can be your executable specifications. Writing tests for code that doesn’t exist yet may seem pointless — after all, they are doomed to fail. However, when you are writing them, you are forcing yourself to think about how the implementation should behave, and how it’s going to be used. By creating the tests early in the project, we can discover what we don’t know.

This works for both new and refactored code. It’s usually a mistake to add tests to code before refactoring it. You may end up testing implementation details that are irrelevant for the application. Instead, it’s better to start with extracting the logic and writing tests will let you see if refactored code implements it correctly or not.

You may discover redundant or missing logic in the process. Even if you expect a test to fail, run it one more time to ensure that it is failing because of expected reasons.

Add code without fear

Writing tests may require a serious initial investment, but it will pay off. Once you have a test suite, you will be able to add new code without fear. Don’t fear failing tests: if it fails on your dev setup, it gives you a chance to fix it before it ends up in production and affects customers.

No test suite is perfect of course — there will be missed cases. Whenever a bug is found, you should add a new test that will detect it. This way, regressions become obvious.

A little summary:

  1. Write tests when you design your logic, before writing the code
  2. Test compliance with project requirements, not implementation details
  3. When a bug is found, add a test for it
  4. Don’t fear failed test

How do you like TDD? Are you an enthusiast or a hater? Let me know in the comments or join the team and let’s discuss it in person.

Featured articles
Generating SwiftUI snapshot tests with Swift macros
Don’t Fix Bad Data, Do This Instead