Wes Turner


Teaching Test-Driven-Development First

Hello world is wrong! Every textbook is doing it wrong, and here’s why:

A traditional first hello world program:

# helloworld.py

def hello_world(name):
    return "Hello, {}".format(name)

if __name__ == "__main__":  # w/o __name__ this'd run inadvertantly

And then run the program:

python ./helloworld.py
# "It should say 'Hello, @westurner!'"  # (check this manually every time)

Hello world with tests (Test-Driven-Development (TDD)):

# test_helloworld.py

import unittest

class TestHelloWorld(unittest.Testcase):
    def setUp(self):
        # print("setUp")
        self.data = {'name': 'TestName'}

    def test_hello_world(self, data=None):
        if data is None:
            data = self.data
        name = data['name']
        expected_output = "Hello, {}!".format(name)
        output = hello_world(name)
        assert expected_output == output
        self.assertEqual(ouput, expected_output)

     # def tearDown(self):
     #    print("tearDown")
     #    print(json.dumps(self.data, indent=2))

And then run the automated tests:

python -m site                         # sys.path and $PWD (`pwd`)
python -m unittest test_helloworld     # ./test_helloworld.py
python -m unittest -v test_helloworld  # ./test_helloworld.py
test $? -eq 0 || echo "Tests failed! (nonzero returncode)"

Why should this first run of the tests fail?

  • Because we didn’t remember to import from helloworld import helloworld in test_helloworld.py.
  • Because, in keeping with TDD, we run the test first to make sure it actually fails without our changes.

Advantages of this Test-Driven-Development (TDD) first approach:

  • TDD from the start: any future breaking changes will be detected (given the completeness of the functional test specification)
  • This teaches Object-Orientation (OO) (Encapsulation, Separation of Concerns)
  • This teaches separation of data and code.
  • This teaches Given-When-Then.

And then what? (Teach what next?):

Concepts and References: