Write failing tests and make them pass with TDD

Test driven development (TDD) is perhaps one of the most controversial software development methodologies out there, as it ties the actual coding stage to the testing stage. Because of this premise, it forces programmers to not only be good at writing actual code but also actual test cases.

As most of you know, this sparks some controversy, as for a very long time these two disciplines were separated. Programmers programmed first and testers tested after.

Under the TDD directive, however, these two activities drive one another and must be done by the same person. Given this major topic, it’s important to know what TDD is, what problems it solves, as well as what are its advantages and disadvantages.

Fortunately, we discuss about all these things in this article, so let’s get to it.

What is Test Driven Development? βœ…

Test Driven Development workflow diagram
Test Driven Development workflow diagram

Test Driven Development is a software development strategy that starts not with a piece of functional code but with a failing test. The developer must then write actual code to make the test pass. Then they need to stop writing code and write another failing test. You then follow it with new code, and the cycle repeats itself.

Of course, along the way the developer must also stop to refactor code and ensure that the new code is not causing previously written and passing tests to fail.

Of course, there are versions of TDD that are more or less restrictive but, in general, these are the main steps of the process that were pioneered by Kent Beck.

What’s the cost of missing tests? ❌

Test driven development meme
An opinion of some developers

Tests are often seen as an afterthought in the software development lifecycle. Programmers throw them in at the end just to validate the “happy path” in which the user doesn’t do anything out of the ordinary.

However, this approach can have massive costs in terms of money, effort, reputation, and much more.

Let’s use this example: you’re working as a software developer on a legacy code base. This means that there is a lot of unpredictable behavior because of old and deprecated code.

You need to make a small change that, unfortunately, means going deep into the codebase. You finish your work in a short amount of time and you even added unit tests to confirm that your new functionality works as intended.

However, soon after deploying the new version of the software (because you’re using version control), small things start going wrong, affecting core functionality.

Because you had no way of knowing how the application behaves before and after the change, you thought it was safe to release. But it wasn’t.

If you had at least some tests for the main code base, you would be able to run them everything you changed some core code. They would have failed and you would have resolved them before release.

Advantages of TDD πŸ‘

I don't always test my code, but when I do, I do it in production
Try to test sooner rather than later

TDD eliminates fragile code

The example in the previous section dealt with fragile code. This issue appears when there are no or very little tests. As such, you have no idea if changes you’re making may affect the functionality of the software.

Using TDD, you create a full suite of passing tests that are well-written. As such, the code base is no longer fragile or brittle. If new code affects the functionality, one or more unit tests will fail, signalling the issue to the programmer.

You gain confidence in your code and the existing code

In addition to eliminating fragile code, TDD also helps programmers. You have access to the entire suite of tests to verify that you’re not introducing new issues into the software. This makes you confident that you’re not working on a barely-functional piece of software

While practicing TDD, you also have confidence that you’re writing code focused on making a failing test pass. As such, you don’t just write things and hope that they work and that they don’t introduce new issues into the application.

Requirements -> Test -> Code hierarchy

Ideally, TDD uses functional documents, such as requirements or specifications to create the actual tests, and then the tests to motivate the creation of the actual code.

This hierarchy means that your code is focused on resolving a test that is representative of the requirements that the software must accomplish as a whole.

You aren’t tempted to add extra features if a test doesn’t need them. You’re also less likely to interpret the requirements in a misguided manner. You always have a test in front of you that clearly dictates what your code should and should not do.

You program from the low level to the high level

One of my favorite simple jokes is:

– How do you eat an elephant?

– Piece by piece

You can create a complex system with interconnecting components for an application that can help you eat an elephant. But by using TDD, you already break down the problem into smaller pieces, much like the elephant.

The first test can be something like When I cut the elephant, I should get a piece small enough to eat. This test already points you in the right direction.

Going from a low level and building on top of the existing functionality is a sure-fire way of eliminating overly complex designs. Of course, refactoring should always come into play, to ensure that you’re building your code in a correct manner, not just at random to solve the failing tests.

Disadvantages of TDD πŸ‘Ž

Should we write tests? Developers don't write tests
Some people don’t like writing tests

You still need well written tests

Test driven development makes you write tests. While that’s always good, it’s also important to mention that they should be good tests. If the tests you create miss important functionality or don’t mention negative scenarios, then your application will still have major issues.

As such, developers need to have the tester mindset and think of potential vulnerabilities, edge cases, and so on. Just writing a test is never enough, the whole pack needs to cover useful scenarios.

Full end-to-end testing is still necessary

The tests you write during actual test driven development are not always enough to verify that the application runs as expected. You still need more complex end-to-end tests to validate that everything works together as expected.

You want your unit tests to be fast and rely only on the code that they are actually validating. This means using mocks and stubs to eliminate the need of outside components. While this is a good practice for unit tests, it means that your tests never fully test how your application works as a whole.

You spend extra time writing and maintaining the tests

Using TDD means that the programmers aren’t spending all their time creating new code to power new functionality for the software. This can be hard to justify to management, not to mention the fact that the tests must also be maintained in addition to the code.

As such, team leads will need to uses metrics and explain the situation to other stakeholders, so that everyone is on board with the approach, from the actual programmers to management and others.

Not all developers like TDD

While this point may be trivial, it’s still quite important: not everyone likes test driven development. If you have a programmer that considers writing tests beneath them, you will have poor or no tests, as well as code that won’t be covered by the full test pack.

This introduces a lot of uncertainty, the very same thing that TDD is supposed to eliminate. The team must resolve this issue as soon as possible, since every developer needs to realize the importance of TDD.

Conclusion 🏁

Test driven development is a controversial topic but with this article you can at least know what it means, why tests are important, as well as what advantages and disadvantages TDD can give you.

Since it’s such an important subject, more articles about TDD are coming, so make sure you keep on checking out QA By Example.

Leave a Reply

Your email address will not be published. Required fields are marked *