Code for this post can be found here

I was recently looking at a video about clean code and realized I’ve never tried this thecnique of software development before, despite the fact of having years of experience, at one point, that was also the case for the speaker in the video at the time he fisrt tryed Test Driven Development, so this post aims to document my first steps into this world. You’ll need some basic knowledge of python and git Here’s an old good friend Hello World written in python:


print("Hello World")

Let’s start

Let’s create our project structure or simply clone the repo above:

mkdir -p ~/dev/tdd-hello_world/src
cd ~/dev/tdd-hello_world/src
echo "print(\"Hello World\")" > hello.py

You can run this code with python hello.py and the output will be Hello World.

Make it testable

Now we have to refactor this code to make it more testable which means separate our domain code from the outside world, in this case, our domain is just a string of text, refactored code will be:


def hello() -> str:
    """Return a greeting"""
    return "Hello World"

print(hello())

Hello test

Now we’re ready to start writing our first test, create a file called hello_test.py next to our hello.py file.


from hello import hello

def test_hello():
    want = "Hello World"
    got = hello()

    assert want == got
Run pytest, if you don’t have it installed do pip install pytest, it should show the test passed, try changing the want string and running pytest again to check if it fails.

Rules for writing tests

There are a couple conventions we must follow when writing a test, it is basically like writing a function with the following conditions:

  • The name of the file must be xxx_test.py or test_xxx.py, where xxx is the name of the file with our business logic code.
  • The function inside that file must start with test.

Change of requirement

Now, the user has a new idea, what about if we know the name of the user we’re greeting? He wants to get a customized greeting message! First, we change our test to reflect that new requirement:


from hello import hello

def test_hello():
    want = "Hello, Douglas!"
    got = hello("Douglas")

    assert want == got
Runing pytest now, it fails!

    def test_hello():
        want = "Hello, Douglas!"
        >       got = hello("Douglas")
        >       E       TypeError: hello() takes 0 positional arguments but 1 was given
Let’s apply the change to our business logic:

def hello(name: str) -> str:
    """Return a greeting"""
        return f"Hello, {name}!"
Great!, now we’ve implemented the new requirement, but what happens if we don’t have a name for all the users? We still need to have the original greeting available! Again, first we define a new set of tests:

def test_hello_without_name():
    want = "Hello, World!"
    got = hello()

    assert want == got

def test_hello_with_name():
    want = "Hello, Douglas!"
    got = hello("Douglas")

    assert want == got
We already know this tests will fail, and we have to update our business logic to make all tests pass:

def hello(name: str = None) -> str:
    """Return a greeting with and without name"""
    if not name:
        name = "World"
    return f"Hello, {name}!"
Now if we run pytest -v will get the following result:

=============================== test session starts ================================
platform darwin -- Python 3.9.7, pytest-7.0.1, pluggy-1.0.0 -- /.../bin/python3.9
cachedir: .pytest_cache
rootdir: ~/dev/tdd-hello_world/src
collected 2 items

hello_test.py::test_hello_without_name PASSED                                [ 50%]
hello_test.py::test_hello_with_name PASSED                                   [100%]

================================ 2 passed in 0.01s =================================

Keep the process simple with 3 key steps

  1. Always start by writing a failing test and make sure it fails!
  2. Write the minimum amount of code to pass the test, this way we are certain of having a working version of the software! And,
  3. Refactor the code to make it secure, readable, fast, and easy to maintain.

From here, we can continue working on new requirements like adding translations or new user’s ideas.

Thanks for dropping by!