One of the things that I have observed both in my own practice of software development, and from what I can see of others is that the concepts of Test Driven Development (TDD) are so counter intuitive and it takes considerable self-discipline, self-restraint and self-awareness to ensure that tests are constructed before code is written. So what does it take to overcome this barrier?

I suspect part of the problem is in the way that software development is taught. I totally understand that it makes sense to focus on the immediate gratification of “Look! See what you can do!”. The problem is that this idea becomes so ingrained and instinctive that software developers are naturally oriented towards “I can just fix this”, or “this needs to be tweaked” as opposed to “how do I test the correctness of the changes I want to make?”

I wonder if part of the problem is the ubiquitous “Hello World”. Yes, it’s a simple and obvious implementation to show that you have successfully implemented something in your language of choice, but there are (usually) no tests. So this got me thinking, what would a unit or integration tested Hello World look like?

Here’s my attempt at a test-driven “Hello World” application implemented in Python3. Keep in mind that this is not me saying “I got the answer!”, but more an exploration of “what if?” to see if some of relevant issues can be uncovered.

What’s the problem?

Of course, in TDD we would write a test before we implemented any code, but for the purpose of this post, we are deconstructing an existing implementation of a “Hello World” application for pedagogical purposes. We want to understand what concepts it is necessary to understand to be able to implement a TDD style “Hello World” application. So here’s an example application in Python.

# hello_world.py

def my_main_function():
    print('Hello World')

if __name__ == "__main__":
    my_main_function()

We have a single function with no arguments my_main_function that calls a standard function print to print the message onto the console. The if statement is Python boilerplate to ensure that the function is called when run from the command line using a command such as python3 hello_world.py.

From a TDD perspective we need to be able to detect that the text emitted to the console is “Hello World”, or perhaps simply that the print command is called with the appropriate argument. At some level this involves the concept of mocking. It is necessary to understand the concept of stdout, how it relates to the print functions and thus mock stdout as an indirect way of acquiring the result of “Hello World”. Complicated? Yeah, seems so, for a beginner.

But let’s keep exploring this idea and see where it leads…

Python TDD Hello world

For the Python implementation I’m using the pytest unit test framework, along with the pytest_mock package for mocking. You could just as easily implement the concept using the unittest framework with the usual caveats regarding the additional boilerplate that people are usually trying to avoid by using pytest, although it would have the advantage that unittest and its mocking capability is in the standard Python library and does not require any additional dependencies use.

In Python, tests are conventionally implemented in separate files from the code it is testing. In trying maintain the spirit of a “first testable application” I’m going to implement the test in the same file as the application.

# hello_world.py

import io


def my_main_function():
    print('Hello World')


def test_hello_world(mocker):
    # `print` appends a newline character that we have to allow for in the test.
    EXPECTED_VALUE = 'Hello World\n'

    mock_stdout = mocker.patch('sys.stdout', new_callable=io.StringIO)
    my_main_function()

    assert mock_stdout.getvalue() == EXPECTED_VALUE


if __name__ == "__main__":
    my_main_function()

Now you can run the test using the command pytest hello_world.py on the command line, or as before, run the application using python3 hello_world.py.

Conclusion

At least in Python, a test driven “Hello World” application is clearly possible. Unfortunately there are a number of extra concepts that must be understood that may represent a barrier to a learner; mocking, test frameworks and additional build dependencies and execution commands. Also, when extending this to statically typed languages such as Rust, C or C++, it becomes much less clear how the concepts of mocking could be applied (admittedly perhaps due to my own inexperience with mocking in those languages).

Interesting though, no?