Learn Python With Tests

This book introduces test driven development in Python. We follow a template while introducing the features of Python.

  • Write tests first.

  • Exercise tests.

  • Fix tests.

  • Refactor

  • And Repeat

Inspiration

This book is inspired from the work https://github.com/quii/learn-go-with-tests/ which introduces Go programming using similar pattern. After reading the Go book, I felt that I could dwell into real-world go projects confidently. I wanted a similar book for Python and I decided to write the current one.

Boiler plate code.

We want to learn Python without much overhead and dependencies. I wanted to make sure that section of the code can be copied to a filename and run with the python interpreter.

If the repetition annoys you, please excuse me, and skip ahead.

# filename: test_hello.py
if __name__ == '__main__':
pass
  • # is the start of the comment.

  • #filename indicates the filename for the snippet.

  • The if __name__ == '__main__' is a special secret that makes sure that python interpreter will execute the

    whatever comes after this line, only when invoked using python interpreter. We typically write the name of the

    tests after this to demonstrate it.

Hello, World

This first chapter can be studied with only python interpreter installed on your system. Please install the latest release of Python from https://www.python.org/downloads/ before we get started.

On my local system, the interpreter that I use is

$ ./python
Python 3.8.1 (default, Jan 10 2020, 06:25:32)
[GCC 9.2.1 20191008] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Write tests first - 1

# filename: test_hello.py
def test_hello() -> None:
expected = "Hello, World!"
actual = get_hello()
assert actual == expected, "{actual} != {expected}".format(actual=actual, expected=expected)
if __name__ == '__main__':
test_hello()

Exercise tests - 1

  • Run the tests

$ python test_hello.py
Traceback (most recent call last):
File "test_hello.py", line 11, in <module>
test_hello()
File "test_hello.py", line 6, in test_hello
actual = get_hello()
NameError: name 'get_hello' is not defined

The NameError indicates we used a term that is not defined. We have not written our get_hello function yet , so let us write it.

Fix tests - 1

Let us write the get_hello method.

def get_hello() -> None:
pass
def test_hello() -> None:
expected = "Hello, World!"
actual = get_hello()
assert actual == expected, "{actual} != {expected}".format(actual=actual, expected=expected)
if __name__ == '__main__':
test_hello()

Exercise tests - 2

$ python test_hello.py
Traceback (most recent call last):
File "test_hello.py", line 15, in <module>
test_hello()
File "test_hello.py", line 11, in test_hello
assert actual == expected, "{actual} != {expected}".format(actual=actual, expected=expected)
AssertionError: None != Hello, World!

Now, we see a new error, AssertionError, which indicates our expected output and actual output were not matching.

Fix tests - 2

Let us make sure that get_hello returns the string "Hello, World"

def get_hello() -> str:
return "Hello, World!"

The full test code will be

# filename: test_hello.py
def get_hello() -> str:
return "Hello, World!"
def test_hello() -> None:
expected = "Hello, World!"
actual = get_hello()
assert actual == expected, "{actual} != {expected}".format(actual=actual, expected=expected)
if __name__ == '__main__':
test_hello()

Running this will give no output indicating that our test was successful.

$ python test_hello.py

Refactor

This was a very simple introduction to writing a test and exercising it. We don't have anything to refactor. Rest of the book will present examples that will explain the need for refactoring the code after it is written.

Chapters