Apr 25, 2022

A Simple Approach to Test-Driven Development

A Simple Approach to Test-Driven Development

Software testing is a method to check if whether the actual product matches the expected requirements and also ensures that:

  • It’s defect free
  • It responds correctly to all kinds of inputs
  • It performs its functions within an acceptable time
  • It runs properly on the intended environment
  • It’s usable

But beyond that, testing can also be used as a framework for developing software.

With a bit of research you will find that there are dozens of testing techniques and software development processes but today we will be focusing on one called Test-Driven Development (TDD) based on Unit Testing.

What is Unit Testing?

Simply put, Unit Testing is a way of testing the smallest piece of code that can be logically isolated in a system.

In other words, it means testing a specific function that doesn’t connect to or rely on external dependencies such as databases, the filesystem or HTTP services ; like for example the addfunction in a calculator program, that takes two numbers and returns their sum.

Keep in mind that the goal of this testing technique is purely to test code logic and nothing else.

What is Test-Driven Development and why?

If you are new to programming, you instincts would tell you that the best and fastest way to develop a software is to first code it, then to test it and finally to fix it.

In theory there’s nothing wrong with that for as long as the project is quite small and you’re the only one working on it.

But, let’s say your project evolves into a product and you end up hiring a whole team to help you out.

As your codebase grows bigger over time, the complexity of the product too which usually implies that:

  • Bugs start to appear
  • Adding features becomes hacky
  • New developers find it hard to understand the code
  • The velocity of the team ultimately drops

At this point you realise that the CODE / TEST / FIX approach doesn’t work so well anymore and that your team has to come up with something new that would improve code readability, solidity and flexibility.

Now, that’s exactly what Test-Driven Development is aiming to solve.

TDD is a test-first approach that takes software development the other way around and relies on product requirements being first converted into test cases before the software is developed.

Writing the tests before the functionality’s code therefore ensures:

  • A better understanding of the product
  • The effectiveness of the code
  • A continual focus on quality

TDD principles: Red, Green, Refactor

Now this method works in three stages: Red, Green and Refactor.

Red

  • First we write a new test that matches the feature’s specifications based on use cases and user stories.
  • Then we run the test cases and witness that new test fails because the feature hasn’t been implemented yet.

Green

  • Second, we write the simplest code possible to make the new test pass, which doesn’t need to be elegant or easy to understand — it just needs to work.
  • We then re-run all of the tests cases, this time making sure that they all successfully pass.

Refactor

  • Finally — if needed — we refactor the code we just wrote without forgetting to re-run all of the cases one last time to certify that nothing was broken in the process.

Best practices

As we’ve seen, the implementation of this pattern is fairly easy and straight forward but there are a few things to keep in mind when it comes to writing these tests.

They should be fast

If they are slow, developers won’t run them so we really need to make them as short as possible and totally independent from one another.

They should be readable

They act as a the best form of documentation since they don’t get out of sync with the code they document.

For that it is recommended to:

Use BDD-style test cases in the form of: Given/When/Then

  • Given my bank account is in credit, and I made no withdrawals recently,
  • When I attempt to withdraw an amount less than my card’s limit,
  • Then the withdrawal should complete without errors or warnings

Preferably have only one logical assertion per method

  • “It should return 10 when the balance is 30.”

Avoid at all cost using magic values

  • 0x159621

They should be deterministic

Deterministic is a fancy word to say that no matter how many times we run the tests, they should behave the same way if the underlying code has not changed, otherwise there’s no reason for developers to trust them.

This is why they should not depend on:

  • Other test cases
  • Environmental values that are likely to change such as the current time
  • The file system, the network or APIs that are likely to fail

They should follow the same naming convention

Behaviour-Driven Development has an elegant way of writing that: should + expected behaviour + when + state under test.

For example:

  • “It should return true when X is bigger than 0.”
  • “It should throw an error when divider is zero.”

Related posts