Test-Driven Development: a gentle introduction

While keeping on analyzing the NYT news, I decided to apply a more robust approach to my functions and particularly to the text pre-processing transformer. At the beginning of my coding experience, considering using a TDD approach was just a remote idea, you don't think about it only when you start changing some peace of code and things break. Unfortunately, I understood how important it is only when it was too late. But let's go straight to the point and see what TDD is about:

Test-driven development (TDD) is a software development process relying on software requirements being converted to test cases before software is fully developed and tracking all software development by repeatedly testing the software against all test cases. This is as opposed to software being developed first and test cases created later. (Wikipedia, 2022)

Tests are written in advance of source-code development. This means that before we write any code for our program, we're going to write some tests. That's the first thing that really flips it on its head compared to maybe more traditional methods of software development. The idea is that before you've written anything, you've written a test.

TDD
TDD loop - source

But who coined the phrase? Well, it was Beck who wrote a book in 1999 called Extreme Programming, and test driven development was part of this new kind of way of programming and doing management, which was referred to as extreme programming. The key feature I think of this text is the idea of feedback and iteration. What we mean with extreme programming is the idea of being able to do very quick iterated cycles of programming, where there's a clear feedback cycle.

The three pillars of TDD are:

  1. You may not write production code unless you've first written a failing unit test.
  2. You may not write more of a unit test than is sufficient to fail.
  3. You may not write more production code than is sufficient to make the failing unit test pass.
(Three laws of unit-testing according to Uncle Bob, the reference: Martin, R.C. 'Professionalism and Test-Driven Development', IEEE Software 24(3) 2007, pp.32–36.)

To demonstrate an example pf TDD we will build a function that returns the maximum value of a list without using any framework and test it using unittest module. But the first question we need to ask ourselves is: does the function return something?

Let's write our first test function to check if the function exists:

First test function checking if get_highest function exists

After running this code we will have an error:

Error message showing function does not exist

This is expected since we didn't write our production code yet, but remember: You may not write production code unless you've first written a failing unit test!! Let's implement the get_highest function:

Implementation of basic get_highest function

Now the test gives a pass. Not how using Docstrings in our functions helps in better understanding the testing function and its result. Let's test now if the function returns a value or not:

Test checking if function returns a value

As before we will get an error and we will need to update our production function:

Updated function returning a simple value

Let's test now that the function returns an actual value, e.g., 5:

Test checking if function returns value 5

In this case we have an error (even worse than a fail) and that's because we are not passing any argument in our function. Also we will need to update other functions call in other tests. Let's take care of this:

Updated test functions with argument passing

Let's test now if it returns 4:

Test checking if function returns value 4

After getting the error, let's update the function:

Function implementation returning last element

We kind of cheated in this case because the highest number is always at the end. Let's test if it's in the middle:

Test with highest number in the middle of list

Update the function again after getting an error:

Updated function using max() to find highest value

Now let's test if it's a string:

Test with string input to check type handling

Return function:

Final function implementation handling different data types

Even if tedious, TDD is fundamental and it can save a lot of headaches before putting anything in production.