test telephone with unittest

Part of Computer Programming is sending input data to a process and getting output data back

input_data -> process -> output_data

I send things (input data) to a program to test it, and check if what I think will happen (my expectation) is the same as the results I get (reality). This helps me answer two questions:

  • what is the same?

  • what is different?

The difference helps me know what to change to get what I want. I use assertions to test if the result of a call to a function with input is the same as my expectation.

assert reality == my_expectation

where

  • reality is what happens when I do something with code

  • my expectation is what I think will happen when I do something with code

The exercises in this chapter show how I can pass objects to a function and use it to make a string (anything in quotes). It will also show another way to organize tests.


preview

I have these tests by the end of the chapter


start the project

  • I name this project telephone

  • I open a terminal

  • I use uv to make a directory for the project and initialize it

    uv init telephone
    

    the terminal shows

    Initialized project `telephone`
    at `.../pumping_python/telephone`
    

    then goes back to the command line.

  • I change directory to the project

    cd telephone
    

    the terminal shows I am in the telephone folder

    .../pumping_python/telephone
    
  • I make a directory for the tests

    mkdir tests
    

    the terminal goes back to the command line.

  • I make the tests directory a Python package

    Danger

    use 2 underscores (__) before and after init for __init__.py not _init_.py

    touch tests/__init__.py
    
    New-Item tests/__init__.py
    

    the terminal goes back to the command line.

  • I use the mv program to change the name of main.py to test_telephone.py and move it to the tests folder

    mv main.py tests/test_telephone.py
    
    Move-Item main.py tests/test_telephone.py
    

    the terminal goes back to the command line.

  • I open test_telephone.py

  • I delete all the text then add the first failing test to test_telephone.py

    1def test_failure():
    2    assert False is True
    
  • I go back to the terminal to make a requirements file for the Python packages I need

    echo "pytest" > requirements.txt
    

    the terminal goes back to the command line.

  • I add pytest-watcher to the requirements file

    echo "pytest-watcher" >> requirements.txt
    

    the terminal goes back to the command line.

  • I use uv to install pytest-watcher with the requirements file

    uv add --requirement requirements.txt
    

    the terminal shows that it installed pytest-watcher and its dependencies.

  • I add the new files and folders to git for tracking

    git add .
    

    the terminal goes back to the command line.

  • I add a git commit message

    git commit --all --message 'setup project'
    

    the terminal shows

    [main (root-commit) a0b12c3] setup project
     8 files changed, X insertions(+)
     create mode 100644 .gitignore
     create mode 100644 .python-version
     create mode 100644 README.md
     create mode 100644 pyproject.toml
     create mode 100644 requirements.txt
     create mode 100644 tests/__init__.py
     create mode 100644 tests/test_telephone.py
     create mode 100644 uv.lock
    

    then goes back to the command line.

  • I use pytest-watcher to run the tests automatically

    uv run pytest-watcher . --now
    

    the terminal is my friend, and shows AssertionError

    ======================== FAILURES ========================
    ______________________ test_failure ______________________
    
        def test_failure():
    >       assert False is True
    E       assert False is True
    
    test_telephone.py:2: AssertionError
    ================ short test summary info =================
    FAILED test_telephone.py::test_failure - assert False is True
    =================== 1 failed in X.YZs ====================
    

    because True is NOT False.

    if the terminal does not show the same error, then check

    • if your tests/__init__.py has two underscores (__) before and after init for __init__.py not _init_.py

    • if you ran echo "pytest-watcher" >> requirements.txt, to add pytest-watcher to the requirements file

    and try uv run pytest-watcher . --now again

  • I add AssertionError to the list of Exceptions seen in test_telephone.py

    1def test_failure():
    2    assert False is True
    3
    4
    5# Exceptions seen
    6# AssertionError
    
  • I change True to False in the assertion

    1def test_failure():
    2    # assert False is True
    3    assert False is False
    4
    5
    6# Exceptions seen
    7# AssertionError
    

    the test passes.


test_passing_none

I can pass None (the simplest object) as input to a function?


RED: make it fail



GREEN: make it pass


  • I add the function

    1def test_passing_none():
    2    def text():
    3        return None
    4
    5    assert text(None) == 'I got: None'
    6
    7
    8# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: test_passing_none.<locals>.text()
               takes 0 positional arguments but 1 was given
    

    because the assertion called the text function which belongs to test_passing_none with input (None) and the function definition does not allow any inputs, the parentheses are empty.

  • I add TypeError to the list of Exceptions seen

     8# Exceptions seen
     9# AssertionError
    10# NameError
    11# TypeError
    
  • I add a name to the function definition

    1def test_passing_none():
    2    # def text():
    3    def text(the_input):
    4        return None
    5
    6    assert text(None) == 'I got: None'
    7
    8
    9# Exceptions seen
    
    • the_input is the name I used for the input, I can use any name I want.

    • the terminal is my friend, and shows AssertionError

      E       assert None == 'I got: None'
      

    because the assertion expects 'I got: None' and the text function returns None.

  • I copy the string from the terminal and paste it in the return statement to replace None

     1def test_passing_none():
     2    # def text():
     3    def text(the_input):
     4        # return None
     5        return 'I got: None'
     6
     7    assert text(None) == 'I got: None'
     8
     9
    10# Exceptions seen
    

    the test passes.


REFACTOR: make it better


  • I remove the commented lines

    1def test_passing_none():
    2    def text(the_input):
    3        return 'I got: None'
    4
    5    assert text(None) == 'I got: None'
    6
    7
    8# Exceptions seen
    
  • I open a new terminal then change directories to telephone

    cd telephone
    

    the terminal shows I am in the telephone folder

    .../pumping_python/telephone
    
  • I add a git commit message in the new terminal

    git commit -am 'add test_passing_none'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can pass None as input to a function.

The problem with this solution is that the text function does not care about what it gets, it always returns 'I got: None' when it is called. I want it to return the object it gets as part of the string.


test_passing_booleans

I can pass booleans from a test to a :ref:`function<what is a function?>.


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a test for booleans (there are only two), first with an assertion for False

    18def test_passing_none():
    19    def text(the_input):
    20        return 'I got: None'
    21
    22    assert text(None) == 'I got: None'
    23
    24
    25def test_passing_booleans():
    26    assert text(False) == 'I got: False'
    27
    28
    29# Exceptions seen
    

    the terminal is my friend, and shows NameError

    NameError: name 'text' is not defined
    

    because the text function belongs to the test_passing_none function and I cannot reach it from outside.


GREEN: make it pass


  • I move the text function out of test_passing_none so that it can be called from anywhere in the file

    1def text(the_input):
    2    return 'I got: None'
    3
    4
    5def test_passing_none():
    6    assert text(None) == 'I got: None'
    7
    8
    9def test_passing_booleans():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'I got: None' == 'I got: False'
    

    because the text function always returns 'I got: None' and this assertion expects 'I got: False'

  • I change the return statement to give the test what it wants

    1def text(the_input):
    2    # return 'I got: None'
    3    return 'I got: False'
    4
    5
    6def test_passing_none():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'I got: False' == 'I got: None'
    

what is string interpolation?

`String Interpolation`_ is the placing of objects in strings. It allows me to make one string that can have changing values.

I can use an `f-string`_ (short for formatted string literal) for `string interpolation`_.

A string is anything inside quotes, for example

  • 'single quotes'

  • '''triple single quotes'''

  • "double quotes"

  • """triple double quotes"""


how to write an f-string


f'characters {object}'
  • I change the return statement to an f-string

    1def text(the_input):
    2    # return 'I got: None'
    3    # return 'I got: False'
    4    return f'I got: {the_input}'
    5
    6
    7def test_passing_none():
    

    the test passes because Python uses the string representation of the object in the curly braces { }

    text(None)
        text(the_input)
            the_input = None
            return f'I got: {the_input}'
            return  'I got:  None      '
    
    text(False)
        text(the_input)
            the_input = False
            return f'I got: {the_input}'
            return  'I got:  False     '
    

REFACTOR: make it better


  • I remove the commented lines

    1def text(the_input):
    2    return f'I got: {the_input}'
    3
    4
    5def test_passing_none():
    
  • I add an assertion for True (the other boolean) to test_passing_booleans

     9def test_passing_booleans():
    10    assert text(False) == 'I got: False'
    11    assert text(True) == 'I got: "True"'
    12
    13
    14# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert 'I got: True' == 'I got: "True"
    
  • I remove the quotes around True in my expectation

     9def test_passing_booleans():
    10    assert text(False) == 'I got: False'
    11    # assert text(True) == 'I got: "True"'
    12    assert text(True) == 'I got: True'
    13
    14
    15# Exceptions seen
    

    the test passes because Python uses the string representation of the object in the curly braces { }

    text(True)
        text(the_input)
            the_input = True
            return f'I got: {the_input}'
            return  'I got:  True      '
    
  • I remove the commented line

     9def test_passing_booleans():
    10    assert text(False) == 'I got: False'
    11    assert text(True) == 'I got: True'
    12
    13
    14# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'add test_passing_booleans'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can pass booleans as input to a function.


test_passing_an_integer

Can I pass an integer (a whole number without decimals) as input to a function?.


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a test for an integer

     9def test_passing_booleans():
    10    assert text(False) == 'I got: False'
    11    assert text(True) == 'I got: True'
    12
    13
    14def test_passing_an_integer():
    15    assert text(1234) == 'I got: "1234"'
    16
    17
    18# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert 'I got: 1234' == 'I got: "1234"'
    

GREEN: make it pass


I remove the quotes around the integer in my expectation

14def test_passing_an_integer():
15    # assert text(1234) == 'I got: "1234"'
16    assert text(1234) == 'I got: 1234'
17
18
19# Exceptions seen

the test passes because Python uses the string representation of the object in the curly braces { }

text(1234)
    text(the_input)
        the_input = None
        return f'I got: {the_input}'
        return  'I got:  1234      '

REFACTOR: make it better


  • I add a variable for 1234

    14def test_passing_an_integer():
    15    an_integer = 1234
    16    # assert text(1234) == 'I got: "1234"'
    17    assert text(1234) == 'I got: 1234'
    18
    19
    20# Exceptions seen
    
  • I use the variable and an f-string to remove repetition of 1234

    14def test_passing_an_integer():
    15    an_integer = 1234
    16    # assert text(1234) == 'I got: "1234"'
    17    # assert text(1234) == 'I got: 1234'
    18    assert text(an_integer) == f'I got: {an_integer}'
    19
    20
    21# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

    14def test_passing_an_integer():
    15    an_integer = 1234
    16    assert text(an_integer) == f'I got: {an_integer}'
    17
    18
    19# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am 'add test_passing_an_integer'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can pass an integer as input to a function.


test_passing_a_float

Can I pass a float (binary floating point decimal number) as input to a function?.


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a test for a float (binary floating point decimal numbers)

    14def test_passing_an_integer():
    15    an_integer = 1234
    16    assert text(an_integer) == f'I got: {an_integer}'
    17
    18
    19def test_passing_a_float():
    20    assert text(5.678) == 'I got: "5.678"'
    21
    22
    23# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert 'I got: 5.678' == 'I got: "5.678"'
    

GREEN: make it pass


I remove the quotes around the float in my expectation

19def test_passing_a_float():
20    # assert text(5.678) == 'I got: "5.678"'
21    assert text(5.678) == 'I got: 5.678'
22
23
24# Exceptions seen

the test passes because Python uses the string representation of the object in the curly braces { }

text(5.678)
    text(the_input)
        the_input = 5.678
        return f'I got: {the_input}'
        return  'I got:  5.678     '

REFACTOR: make it better


  • I add a variable for 5.678

    19def test_passing_a_float():
    20    a_float = 5.678
    21    # assert text(5.678) == 'I got: "5.678"'
    22    assert text(5.678) == 'I got: 5.678'
    23
    24
    25# Exceptions seen
    
  • I use the variable and an f-string to remove repetition of 5.678

    19def test_passing_a_float():
    20    a_float = 5.678
    21    # assert text(5.678) == 'I got: "5.678"'
    22    # assert text(5.678) == 'I got: 5.678'
    23    assert text(a_float) == f'I got: {a_float}'
    24
    25
    26# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

    19def test_passing_a_float():
    20    a_float = 5.678
    21    assert text(a_float) == f'I got: {a_float}'
    22
    23
    24# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message \
    'add test_passing_a_float'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can pass a float as input to a function.


test_passing_a_string

Can I pass a string as input to a function?.


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a test for a string (anything in quotes)

    19def test_passing_a_float():
    20    a_float = 5.678
    21    assert text(a_float) == f'I got: {a_float}'
    22
    23
    24def test_passing_a_string():
    25    assert text('hi') == f'I got: hello'
    26
    27
    28# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'I got: hi' == 'I got: hello'
    

GREEN: make it pass


I change my expectation to match reality

24def test_passing_a_string():
25    # assert text('hi') == f'I got: hello'
26    assert text('hi') == f'I got: hi'
27
28
29# Exceptions seen

the test passes because Python uses the string representation of the object in the curly braces { }

text(hi)
    text(the_input)
        the_input = hi
        return f'I got: {the_input}'
        return  'I got:  hi        '

REFACTOR: make it better


  • I add a variable for 'hi'

    24def test_passing_a_string():
    25    a_string = 'hi'
    26    # assert text('hi') == f'I got: hello'
    27    assert text('hi') == f'I got: hi'
    28
    29
    30# Exceptions seen
    
  • I use the variable and an f-string to remove repetition of 'hi'

    24def test_passing_a_string():
    25    a_string = 'hi'
    26    # assert text('hi') == f'I got: hello'
    27    # assert text('hi') == f'I got: hi'
    28    assert text(a_string) == f'I got: {a_string}'
    29
    30
    31# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

    24def test_passing_a_string():
    25    a_string = 'hi'
    26    assert text(a_string) == f'I got: {a_string}'
    27
    28
    29# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am 'add test_passing_a_string'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can pass a string as input to a function.


test_passing_a_tuple

Can I pass a tuple (anything in parentheses ( ) separated by a comma) as input to a function?


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a test for a tuple

    24def test_passing_a_string():
    25    a_string = 'hi'
    26    assert text(a_string) == f'I got: {a_string}'
    27
    28
    29def test_passing_a_tuple():
    30    assert text((0, 1, 2, 'n')) == 'I got: (1, 2, 3, n)'
    31
    32
    33# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: (0, 1, 2, 'n')"
                == 'I got: (1, 2, 3, n)'
    

GREEN: make it pass


I change the tuple in my expectation to match reality

29def test_passing_a_tuple():
30    # assert text((0, 1, 2, 'n')) == 'I got: (1, 2, 3, n)'
31    assert text((0, 1, 2, 'n')) == "I got: (0, 1, 2, 'n')"
32
33
34# Exceptions seen

the test passes because Python uses the string representation of the object in the curly braces { }

text((0, 1, 2, 'n'))
    text(the_input)
        the_input = (0, 1, 2, 'n')
        return f'I got: {the_input    }'
        return  'I got:  (0, 1, 2, 'n')'

REFACTOR: make it better


  • I add a variable for (0, 1, 2, 'n')

    29def test_passing_a_tuple():
    30    a_tuple = (0, 1, 2, 'n')
    31    # assert text((0, 1, 2, 'n')) == 'I got: (0, 1, 2, 'n')'
    32    assert text((0, 1, 2, 'n')) == "I got: (0, 1, 2, 'n')"
    33
    34
    35# Exceptions seen
    
  • I use the variable and an f-string to remove repetition of (0, 1, 2, 'n')

    29def test_passing_a_tuple():
    30    a_tuple = (0, 1, 2, 'n')
    31    # assert text((0, 1, 2, 'n')) == 'I got: (0, 1, 2, 'n')'
    32    # assert text((0, 1, 2, 'n')) == "I got: (0, 1, 2, 'n')"
    33    assert text(a_tuple) == f'I got: {a_tuple}'
    34
    35
    36# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

    29def test_passing_a_tuple():
    30    a_tuple = (0, 1, 2, 'n')
    31    assert text(a_tuple) == f'I got: {a_tuple}'
    32
    33
    34# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message 'add test_passing_a_tuple'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can pass a tuple as input to a function.


test_passing_a_list

Can I pass a list (anything in square brackets [ ]) from a test to a function?


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a test for a list

    29def test_passing_a_tuple():
    30    a_tuple = (0, 1, 2, 'n')
    31    assert text(a_tuple) == f'I got: {a_tuple}'
    32
    33
    34def test_passing_a_list():
    35    assert text([0, 1, 2, 'n']) == 'I got: [0, 1, 2, "n"]'
    36
    37
    38# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    assert "I got: [0, 1, 2, 'n']"
        == 'I got: [0, 1, 2, "n"]'
    

GREEN: make it pass


I change the list in my expectation to match reality

34def test_passing_a_list():
35    # assert text([0, 1, 2, 'n']) == 'I got: [0, 1, 2, "n"]'
36    assert text([0, 1, 2, 'n']) == "I got: [0, 1, 2, 'n']"
37
38
39# Exceptions seen

the test passes because Python uses the string representation of the object in the curly braces { }

text([0, 1, 2, 'n'])
    text(the_input)
        the_input = [0, 1, 2, 'n']
        return f'I got: {the_input    }'
        return  'I got:  [0, 1, 2, 'n']'

Python changed the double quotes (") in the list to a single quote (').


REFACTOR: make it better


  • I add a variable for [0, 1, 2, 'n']

    34def test_passing_a_list():
    35    a_list = [0, 1, 2, 'n']
    36    # assert text([0, 1, 2, 'n']) == 'I got: [0, 1, 2, "n"]'
    37    assert text([0, 1, 2, 'n']) == "I got: [0, 1, 2, 'n']"
    38
    39
    40# Exceptions seen
    
  • I use the variable and an f-string to remove repetition of [0, 1, 2, 'n']

    34def test_passing_a_list():
    35    a_list = [0, 1, 2, 'n']
    36    # assert text([0, 1, 2, 'n']) == 'I got: [0, 1, 2, "n"]'
    37    # assert text([0, 1, 2, 'n']) == "I got: [0, 1, 2, 'n']"
    38    assert text(a_list) == f'I got: {a_list}'
    39
    40
    41# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

    50def test_passing_a_list():
    51    a_list = [0, 1, 2, 'n']
    52    assert text(a_list) == f'I got: {a_list}'
    53
    54
    55# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am 'add test_passing_a_list'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can pass a list as input to a function.


test_passing_a_set

Can I pass a set (anything in curly braces { }, not key-value pairs) from a test to a function?


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a test for a set

    34def test_passing_a_list():
    35    a_list = [0, 1, 2, 'n']
    36    assert text(a_list) == f'I got: {a_list}'
    37
    38
    39def test_passing_a_set():
    40    assert text({0, 1, 2, 'n'}) == 'I got: {0, 1, 2, "n"}'
    41
    42
    43# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: {0, 1, 2, 'n'}"
                == 'I got: {0, 1, 2, "n"}'
    

GREEN: make it pass


  • I change the set in my expectation to match reality

    39def test_passing_a_set():
    40    # assert text({0, 1, 2, 'n'}) == 'I got: {0, 1, 2, "n"}'
    41    assert text({0, 1, 2, 'n'}) == "I got: {0, 1, 2, 'n'}"
    42
    43
    44# Exceptions seen
    
  • I use ctrl/command+s (Windows & Linux/MacOS) to run the test a few times

    • if the result of text({0, 1, 2, 'n'}) is equal to "I got: {0, 1, 2, 'n'}" the test passes because Python uses the string representation of the object in the curly braces { }

      text({0, 1, 2, 'n'})
          text(the_input)
              the_input = {0, 1, 2, 'n'}
              return f'I got: {the_input    }'
              return  'I got:  {0, 1, 2, 'n'}'
      
    • if the result of text({0, 1, 2, 'n'}) is NOT equal to "I got: {0, 1, 2, 'n'}", the terminal shows AssertionError

      E       assert "I got: {0, 'n', 2, 1}"
                  == "I got: {0, 1, 2, 'n'}"
      

    Python cannot guarantee the order of the things in the set and the order matters for the assertion that is comparing the strings because

    • these two are the same set

      {0, 'n', 2, 1} == {0, 1, 2, 'n'}
      
    • these two are not the same string

      "{0, 'n', 2, 1}" != "{0, 1, 2, 'n'}"
      
  • I add a variable for {0, 1, 2, 'n'}

    39def test_passing_a_set():
    40    a_set = {0, 1, 2, 'n'}
    41    # assert text({0, 1, 2, 'n'}) == 'I got: {0, 1, 2, "n"}'
    42    assert text({0, 1, 2, 'n'}) == "I got: {0, 1, 2, 'n'}"
    43
    44
    45# Exceptions seen
    
  • I use the variable and an f-string to remove repetition of {0, 1, 2, 'n'}

    34def test_passing_a_set():
    35    a_set = {0, 1, 2, 'n'}
    36    # assert text({0, 1, 2, 'n'}) == 'I got: {0, 1, 2, "n"}'
    37    # assert text({0, 1, 2, 'n'}) == "I got: {0, 1, 2, 'n'}"
    38    assert text(a_set) == f'I got: {a_set}'
    39
    40
    41# Exceptions seen
    
    • I use ctrl/command+s (Windows & Linux/MacOS) to run the test a few times and the test stays green with no random failures because Python uses the string representation of the object in the curly braces { }.

    • It can guarantee the order when I use a variable and the f-string to refer to the same exact set.


REFACTOR: make it better


  • I remove the commented lines

    50def test_passing_a_set():
    51    a_set = {0, 1, 2, 'n'}
    52    assert text(a_set) == f'I got: {a_set}'
    53
    54
    55# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message 'add test_passing_a_set'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can pass a set as input to a function.


test_passing_a_dictionary

Can I pass a dictionary (any key-value pairs in curly braces ‘{ }’ separated by commas as input to a function?


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a test for a dictionary

    39def test_passing_a_set():
    40    a_set = {0, 1, 2, 'n'}
    41    assert text(a_set) == f'I got: {a_set}'
    42
    43
    44def test_passing_a_dictionary():
    45    reality = text({
    46        'key0': 'value0',
    47        'keyN': [0, 1, 2, 'n'],
    48    })
    49    my_expectation = (
    50        "I got: "
    51        "{key0: value0, keyN: [0, 1, 2, n]}"
    52    )
    53    assert reality == my_expectation
    54
    55
    56# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    assert "I got: {'key..., 1, 2, 'n']}"
        == 'I got: {key0...[0, 1, 2, n]}'
    

    I want more detail in my error messages.


GREEN: make it pass


I change my_expectation to match reality

44def test_passing_a_dictionary():
45    reality = text({
46        'key0': 'value0',
47        'keyN': [0, 1, 2, 'n'],
48    })
49    my_expectation = (
50        "I got: "
51        # "{key0: value0, keyN: [0, 1, 2, n]}"
52        "{'key0': 'value0', 'keyN': [0, 1, 2, 'n']}"
53    )
54    assert reality == my_expectation
55
56
57# Exceptions seen

the test passes because Python uses the string representation of the object in the curly braces { }

text({'key0': 'value0', 'keyN': [0, 1, 2, 'n'],})
    text(the_input)
        the_input = {'key0': 'value0', 'keyN': [0, 1, 2, 'n']}
        return f'I got: {the_input    }'
        return  "I got: {'key0': 'value0', 'keyN': [0, 1, 2, 'n']}"

REFACTOR: make it better


  • I add a variable for {'key0': 'value0', 'keyN': [0, 1, 2, 'n'],}

    44def test_passing_a_dictionary():
    45    a_dictionary = {
    46        'key0': 'value0',
    47        'keyN': [0, 1, 2, 'n'],
    48    }
    49    reality = text({
    50        'key0': 'value0',
    51        'keyN': [0, 1, 2, 'n'],
    52    })
    53    my_expectation = (
    54        "I got: "
    55        # "{key0: value0, keyN: [0, 1, 2, n]}"
    56        "{'key0': 'value0', 'keyN': [0, 1, 2, 'n']}"
    57    )
    58    assert reality == my_expectation
    59
    60
    61# Exceptions seen
    
  • I use the variable and an f-string to remove repetition of {'key0': 'value0', 'keyN': [0, 1, 2, 'n'],}

    44def test_passing_a_dictionary():
    45    a_dictionary = {
    46        'key0': 'value0',
    47        'keyN': [0, 1, 2, 'n'],
    48    }
    49    # reality = text({
    50    #     'key0': 'value0',
    51    #     'keyN': [0, 1, 2, 'n'],
    52    # })
    53    # my_expectation = (
    54    #     "I got: "
    55    #     # "{key0: value0, keyN: [0, 1, 2, n]}"
    56    #     "{'key0': 'value0', 'keyN': [0, 1, 2, 'n']}"
    57    # )
    58    reality = text(a_dictionary)
    59    my_expectation = f'I got: {a_dictionary}'
    60    assert reality == my_expectation
    61
    62
    63# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

    56def test_passing_a_dictionary():
    57    a_dictionary = {
    58        'key0': 'value0',
    59        'keyN': [0, 1, 2, 'n'],
    60    }
    61    reality = text(a_dictionary)
    62    my_expectation = f'I got: {a_dictionary}'
    63    assert reality == my_expectation
    64
    65
    66# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am 'add test_passing_a_dictionary'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can pass a dictionary as input to a function.


test_passing_a_class

Can I pass any object as input to a function?


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a failing test to see what happens when I pass a class from a test to the text function, in test_telephone.py

    44def test_passing_a_dictionary():
    45    a_dictionary = {
    46        'key0': 'value0',
    47        'keyN': [0, 1, 2, 'n'],
    48    }
    49    reality = text(a_dictionary)
    50    my_expectation = f'I got: {a_dictionary}'
    51    assert reality == my_expectation
    52
    53
    54def test_passing_a_class():
    55    assert text(object) == 'I got: object'
    56
    57
    58# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: <class 'object'>"
                == 'I got: object'
    

    object is the mother class that all Python classes come from, and everything in Python is an object.


GREEN: make it pass


I change my expectation to match reality

54def test_passing_a_class():
55    # assert text(object) == 'I got: object'
56    assert text(object) == "I got: <class 'object'>"
57
58
59# Exceptions seen

the test passes because Python uses the string representation of the object in the curly braces { }

text(object)
    text(the_input)
        the_input = object
        return f'I got: {the_input       }'
        return  "I got:  <class 'object'> "

REFACTOR: make it better


  • I add an assertion for bool (the class for booleans)

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    assert text(bool) == 'I got: bool'
    58
    59
    60# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: <class 'bool'>" == 'I got: bool'
    
  • I change my expectation to match reality

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59
    60
    61# Exceptions seen
    

    the test passes.

  • I add an assertion for int (the class for whole numbers without decimals)

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    assert text(int) == 'I got: int'
    60
    61
    62# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: <class 'int'>" == 'I got: int'
    
  • I change my expectation to match reality

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61
    62
    63# Exceptions seen
    

    the test passes.

  • I add an assertion for float (the class for binary floating point decimal numbers)

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    assert text(float) == 'I got: float'
    62
    63
    64# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: <class 'float'>" == 'I got: float'
    
  • I change my expectation to match reality

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    # assert text(float) == 'I got: float'
    62    assert text(float) == "I got: <class 'float'>"
    63
    64
    65# Exceptions seen
    

    the test passes.

  • I add an assertion for str (the class for anything in quotes)

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    # assert text(float) == 'I got: float'
    62    assert text(float) == "I got: <class 'float'>"
    63    assert text(str) == 'I got: str'
    64
    65
    66# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: <class 'str'>" == 'I got: str'
    
  • I change my expectation to match reality

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    # assert text(float) == 'I got: float'
    62    assert text(float) == "I got: <class 'float'>"
    63    # assert text(str) == 'I got: str'
    64    assert text(str) == "I got: <class 'str'>"
    65
    66
    67# Exceptions seen
    

    the test passes.

  • I add an assertion for tuple (the class for anything in parentheses ( ) separated by a comma)

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    # assert text(float) == 'I got: float'
    62    assert text(float) == "I got: <class 'float'>"
    63    # assert text(str) == 'I got: str'
    64    assert text(str) == "I got: <class 'str'>"
    65    assert text(tuple) == 'I got: tuple'
    66
    67
    68# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: <class 'tuple'>" == 'I got: tuple'
    
  • I change my expectation to match reality

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    # assert text(float) == 'I got: float'
    62    assert text(float) == "I got: <class 'float'>"
    63    # assert text(str) == 'I got: str'
    64    assert text(str) == "I got: <class 'str'>"
    65    # assert text(tuple) == 'I got: tuple'
    66    assert text(tuple) == "I got: <class 'tuple'>"
    67
    68
    69# Exceptions seen
    

    the test passes.

  • I add an assertion for list (the class for anything in square brackets ‘[ ]’)

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    # assert text(float) == 'I got: float'
    62    assert text(float) == "I got: <class 'float'>"
    63    # assert text(str) == 'I got: str'
    64    assert text(str) == "I got: <class 'str'>"
    65    # assert text(tuple) == 'I got: tuple'
    66    assert text(tuple) == "I got: <class 'tuple'>"
    67    assert text(list) == 'I got: list'
    68
    69
    70# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: <class 'tuple'>" == 'I got: tuple'
    
  • I change my expectation to match reality

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    # assert text(float) == 'I got: float'
    62    assert text(float) == "I got: <class 'float'>"
    63    # assert text(str) == 'I got: str'
    64    assert text(str) == "I got: <class 'str'>"
    65    # assert text(tuple) == 'I got: tuple'
    66    assert text(tuple) == "I got: <class 'tuple'>"
    67    # assert text(list) == 'I got: list'
    68    assert text(list) == "I got: <class 'list'>"
    69
    70
    71# Exceptions seen
    

    the test passes.

  • I add an assertion for set (the class anything in curly braces { }, not key-value pairs)

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    # assert text(float) == 'I got: float'
    62    assert text(float) == "I got: <class 'float'>"
    63    # assert text(str) == 'I got: str'
    64    assert text(str) == "I got: <class 'str'>"
    65    # assert text(tuple) == 'I got: tuple'
    66    assert text(tuple) == "I got: <class 'tuple'>"
    67    # assert text(list) == 'I got: list'
    68    assert text(list) == "I got: <class 'list'>"
    69    assert text(set) == 'I got: set'
    70
    71
    72# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: <class 'set'>" == 'I got: set'
    
  • I change my expectation to match reality

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    # assert text(float) == 'I got: float'
    62    assert text(float) == "I got: <class 'float'>"
    63    # assert text(str) == 'I got: str'
    64    assert text(str) == "I got: <class 'str'>"
    65    # assert text(tuple) == 'I got: tuple'
    66    assert text(tuple) == "I got: <class 'tuple'>"
    67    # assert text(list) == 'I got: list'
    68    assert text(list) == "I got: <class 'list'>"
    69    # assert text(set) == 'I got: set'
    70    assert text(set) == "I got: <class 'set'>"
    71
    72
    73# Exceptions seen
    

    the test passes.

  • I add an assertion for dict (the class for key-value pairs in curly braces ‘{ }’ separated by commas)

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    # assert text(float) == 'I got: float'
    62    assert text(float) == "I got: <class 'float'>"
    63    # assert text(str) == 'I got: str'
    64    assert text(str) == "I got: <class 'str'>"
    65    # assert text(tuple) == 'I got: tuple'
    66    assert text(tuple) == "I got: <class 'tuple'>"
    67    # assert text(list) == 'I got: list'
    68    assert text(list) == "I got: <class 'list'>"
    69    # assert text(set) == 'I got: set'
    70    assert text(set) == "I got: <class 'set'>"
    71    assert text(dict) == 'I got: dict'
    72
    73
    74# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: <class 'dict'>" == 'I got: dict'
    
  • I change my expectation to match reality

    54def test_passing_a_class():
    55    # assert text(object) == 'I got: object'
    56    assert text(object) == "I got: <class 'object'>"
    57    # assert text(bool) == 'I got: bool'
    58    assert text(bool) == "I got: <class 'bool'>"
    59    # assert text(int) == "I got: int"
    60    assert text(int) == "I got: <class 'int'>"
    61    # assert text(float) == 'I got: float'
    62    assert text(float) == "I got: <class 'float'>"
    63    # assert text(str) == 'I got: str'
    64    assert text(str) == "I got: <class 'str'>"
    65    # assert text(tuple) == 'I got: tuple'
    66    assert text(tuple) == "I got: <class 'tuple'>"
    67    # assert text(list) == 'I got: list'
    68    assert text(list) == "I got: <class 'list'>"
    69    # assert text(set) == 'I got: set'
    70    assert text(set) == "I got: <class 'set'>"
    71    # assert text(dict) == 'I got: dict'
    72    assert text(dict) == "I got: <class 'dict'>"
    73
    74
    75# Exceptions seen
    

    the test passes because Python uses the string representation of the object in the curly braces { }

    text(dict)
        text(the_input)
            the_input = dict
            return f'I got: {the_input     }'
            return  "I got:  <class 'dict'> "
    
  • I remove the commented lines

    54def test_passing_a_class():
    55    assert text(object) == "I got: <class 'object'>"
    56    assert text(bool) == "I got: <class 'bool'>"
    57    assert text(int) == "I got: <class 'int'>"
    58    assert text(float) == "I got: <class 'float'>"
    59    assert text(str) == "I got: <class 'str'>"
    60    assert text(tuple) == "I got: <class 'tuple'>"
    61    assert text(list) == "I got: <class 'list'>"
    62    assert text(set) == "I got: <class 'set'>"
    63    assert text(dict) == "I got: <class 'dict'>"
    64
    65
    66# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit --all --message 'add test_passing_a_class'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can pass any object as input to a function.


separate and equal

So far all functions I have written have been in the same file as the tests, some are even in the same function as the assertions of the tests.

In earlier tests I found it better to keep functions outside of functions so that anything could call them from outside.

I can also place them in other modules then use the import statement to bring in the function so I can test it. This helps me keep tests and solutions separate. It also means I can send tests only, solutions only or both.


RED: make it fail


  • I go back to the terminal where the tests are running

  • I change the assertion to call the text function of the telephone module in the src folder

     1def text(the_input):
     2    return f'I got: {the_input}'
     3
     4
     5def test_passing_none():
     6    # assert text(None) == 'I got: None'
     7    reality = src.telephone.text(None)
     8    my_expectation = 'I got: None'
     9    assert reality == my_expectation
    10
    11
    12def test_passing_booleans():
    

    the terminal is my friend, and shows NameError

    NameError: name 'src' is not defined
    

    because there is nothing with that name in test_telephone.py.


GREEN: make it pass


  • I add an import statement at the top of test_telephone.py

    1import src
    2
    3
    4def text(the_input):
    

    the terminal is my friend, and shows ModuleNotFoundError

    E   ModuleNotFoundError: No module named 'src'
    

    because there is nothing named src in the project.

  • I add ModuleNotFoundError to the list of Exceptions seen

    73# Exceptions seen
    74# AssertionError
    75# NameError
    76# TypeError
    77# ModuleNotFoundError
    
  • I go to the other terminal

  • I use mkdir to make a folder named src

    mkdir src
    

    the terminal goes back to the command line.

  • I go back to the terminal where the tests are running

  • I use ctrl/command+s (Windows & Linux/MacOS) in test_telephone.py to run the test again, and the terminal shows AttributeError

    AttributeError: module 'src' has no attribute 'telephone'
    

    because there is nothing named telephone in the src folder.

  • I add AttributeError to the list of Exceptions seen

    73# Exceptions seen
    74# AssertionError
    75# NameError
    76# TypeError
    77# ModuleNotFoundError
    78# AttributeError
    
  • I change the import statement to make it import telephone.py from the src folder

    1# import src
    2import src.telephone
    3
    4
    5def text(the_input):
    

    the terminal is my friend, and shows ModuleNotFoundError

    E   ModuleNotFoundError: No module named 'src.telephone'
    

    because Python cannot find telephone.py in the src folder since I have not made it yet.

  • I go to the other terminal

  • I use touch to make telephone.py in the src folder

    touch src/telephone.py
    
    New-Item src/telephone.py
    

    the terminal goes back to the command line.

  • I go back to the terminal where the tests are running and it shows AttributeError

    AttributeError: module 'src.telephone' has no attribute 'text'
    

    because telephone.py in the src folder does not have anything named text inside it.

  • I open telephone.py from the src folder

  • I add a copy of the text function to telephone.py

    1def text(the_input):
    2    return f'I got: {the_input}'
    

    the test passes because

    • when import src.telephone runs, Python brings in an object for the telephone.py file from the src folder so I can use it in test_telephone.py as src.telephone

    • when src.telephone.text(None) runs, Python calls the text function from the object it imported for the telephone.py file from the src folder (src.telephone)

    I think of src.telephone.text like an address

    src.telephone.text
    src
    └── telephone.py
        └── def text(the_input):
            └── return f'I got: {the input}'
    

REFACTOR: make it better


  • I remove the commented lines from test_telephone.py

     1import src.telephone
     2
     3
     4def text(the_input):
     5    return f'I got: {the_input}'
     6
     7
     8def test_passing_none():
     9    reality = src.telephone.text(None)
    10    my_expectation = 'I got: None'
    11    assert reality == my_expectation
    12
    13
    14def test_passing_booleans():
    
  • I change the call in the first assertion of test_passing_booleans to src.telephone.text

    14def test_passing_booleans():
    15    # assert text(False) == 'I got: False'
    16    reality = src.telephone.text(False)
    17    my_expectation = 'I got: False'
    18    assert reality == my_expectation
    19    assert text(True) == 'I got: True'
    20
    21
    22def test_passing_an_integer():
    

    the test is still green because when src.telephone.text is called, Python follows this path

    src.telephone.text
    src
    └── telephone.py
        └── def text(the_input):
            └── return f'I got: {the input}'
    

    then uses the string representation of the object in the curly braces { }

    text(None)
        text(the_input)
            the_input = None
            return f'I got: {the_input}'
            return  'I got:  None      '
    
  • I make the same change for the next assertion

    14def test_passing_booleans():
    15    # assert text(False) == 'I got: False'
    16    reality = src.telephone.text(False)
    17    my_expectation = 'I got: False'
    18    assert reality == my_expectation
    19    # assert text(True) == 'I got: True'
    20    reality = src.telephone.text(True)
    21    my_expectation = 'I got: True'
    22    assert reality == my_expectation
    23
    24
    25def test_passing_an_integer():
    

    still green.

  • I remove the commented lines from test_passing_booleans

    14def test_passing_booleans():
    15    reality = src.telephone.text(False)
    16    my_expectation = 'I got: False'
    17    assert reality == my_expectation
    18
    19    reality = src.telephone.text(True)
    20    my_expectation = 'I got: True'
    21    assert reality == my_expectation
    22
    23
    24def test_passing_an_integer():
    
  • I change the call in the assertion of test_passing_an_integer to src.telephone.text

    24def test_passing_an_integer():
    25    an_integer = 1234
    26    # assert text(an_integer) == f'I got: {an_integer}'
    27    reality = src.telephone.text(an_integer)
    28    my_expectation = f'I got: {an_integer}'
    29    assert reality == my_expectation
    30
    31
    32def test_passing_a_float():
    

    green.

  • I remove the commented line from test_passing_an_integer

    24def test_passing_an_integer():
    25    an_integer = 1234
    26
    27    reality = src.telephone.text(an_integer)
    28    my_expectation = f'I got: {an_integer}'
    29    assert reality == my_expectation
    30
    31
    32def test_passing_a_float():
    
  • I change the call in the assertion of test_passing_a_float

    32def test_passing_a_float():
    33    a_float = 5.678
    34    # assert text(a_float) == f'I got: {a_float}'
    35    reality = src.telephone.text(a_float)
    36    my_expectation = f'I got: {a_float}'
    37    assert reality == my_expectation
    38
    39
    40def test_passing_a_string():
    

    still green because when src.telephone.text is called, Python follows this path

    src.telephone.text
    src
    └── telephone.py
        └── def text(the_input):
            └── return f'I got: {the input}'
    

    then uses the string representation of the object in the curly braces { }

    a_float = 5.678
    
    text(a_float)
        text(the_input)
            the_input = 5.678
            return f'I got: {the_input}'
            return  'I got:  5.678     '
    
  • I remove the commented line

    32def test_passing_a_float():
    33    a_float = 5.678
    34
    35    reality = src.telephone.text(a_float)
    36    my_expectation = f'I got: {a_float}'
    37    assert reality == my_expectation
    38
    39
    40def test_passing_a_string():
    

    the test is still green.

  • I change the call in the assertion of test_passing_a_string

    40def test_passing_a_string():
    41    a_string = 'hi'
    42    # assert text(a_string) == f'I got: {a_string}'
    43    reality = src.telephone.text(a_string)
    44    my_expectation = f'I got: {a_string}'
    45    assert reality == my_expectation
    46
    47
    48def test_passing_a_tuple():
    
  • I remove the commented line

    40def test_passing_a_string():
    41    a_string = 'hi'
    42
    43    reality = src.telephone.text(a_string)
    44    my_expectation = f'I got: {a_string}'
    45    assert reality == my_expectation
    46
    47
    48def test_passing_a_tuple():
    
  • I change the call in the assertion of test_passing_a_tuple

    48def test_passing_a_tuple():
    49    a_tuple = (0, 1, 2, 'n')
    50    # assert text(a_tuple) == f'I got: {a_tuple}'
    51    reality = src.telephone.text(a_tuple)
    52    my_expectation = f'I got: {a_tuple}'
    53    assert reality == my_expectation
    54
    55
    56def test_passing_a_list():
    

    still green.

  • I remove the commented line

    48def test_passing_a_tuple():
    49    a_tuple = (0, 1, 2, 'n')
    50
    51    reality = src.telephone.text(a_tuple)
    52    my_expectation = f'I got: {a_tuple}'
    53    assert reality == my_expectation
    54
    55
    56def test_passing_a_list():
    
  • I change the call in the assertion of test_passing_a_list

    56def test_passing_a_list():
    57    a_list = [0, 1, 2, 'n']
    58    # assert text(a_list) == f'I got: {a_list}'
    59    reality = src.telephone.text(a_list)
    60    my_expectation = f'I got: {a_list}'
    61    assert reality == my_expectation
    62
    63
    64def test_passing_a_set():
    

    green.

  • I remove the commented line

    56def test_passing_a_list():
    57    a_list = [0, 1, 2, 'n']
    58
    59    reality = src.telephone.text(a_list)
    60    my_expectation = f'I got: {a_list}'
    61    assert reality == my_expectation
    62
    63
    64def test_passing_a_set():
    
  • I change the call in the assertion of test_passing_a_set

    64def test_passing_a_set():
    65    a_set = {0, 1, 2, 'n'}
    66    # assert text(a_set) == f'I got: {a_set}'
    67    reality = src.telephone.text(a_set)
    68    my_expectation = f'I got: {a_set}'
    69    assert reality == my_expectation
    70
    71
    72def test_passing_a_dictionary():
    

    still green.

  • I remove the commented line

    64def test_passing_a_set():
    65    a_set = {0, 1, 2, 'n'}
    66
    67    reality = src.telephone.text(a_set)
    68    my_expectation = f'I got: {a_set}'
    69    assert reality == my_expectation
    70
    71
    72def test_passing_a_dictionary():
    
  • I change the call in reality for test_passing_a_dictionary

    72def test_passing_a_dictionary():
    73    a_dictionary = {
    74        'key0': 'value0',
    75        'keyN': [0, 1, 2, 'n'],
    76    }
    77    # reality = text(a_dictionary)
    78    reality = src.telephone.text(a_dictionary)
    79    my_expectation = f'I got: {a_dictionary}'
    80    assert reality == my_expectation
    81
    82
    83def test_passing_a_class():
    

    the test is still green because when src.telephone.text is called, Python follows this path

    src.telephone.text
    src
    └── telephone.py
        └── def text(the_input):
            └── return f'I got: {the input}'
    

    then uses the string representation of the object in the curly braces { }

    a_dictionary = {
        'key0': 'value0',
        'keyN': [0, 1, 2, 'n'],
    }
    
    text(a_dictionary)
        text(the_input)
            the_input = {
                'key0': 'value0',
                'keyN': [0, 1, 2, 'n'],
            }
            return f'I got: {the_input}'
            return  "I got: {'key0': 'value0', 'keyN': [0, 1, 2, 'n']}"
    
  • I remove the commented line

    72def test_passing_a_dictionary():
    73    a_dictionary = {
    74        'key0': 'value0',
    75        'keyN': [0, 1, 2, 'n'],
    76    }
    77
    78    reality = src.telephone.text(a_dictionary)
    79    my_expectation = f'I got: {a_dictionary}'
    80    assert reality == my_expectation
    81
    82
    83def test_passing_a_class():
    
  • I change the calls in the assertions of test_passing_a_class

     83def test_passing_a_class():
     84    # assert text(object) == "I got: <class 'object'>"
     85    reality = src.telephone.text(object)
     86    my_expectation = "I got: <class 'object'>"
     87    assert reality == my_expectation
     88
     89    # assert text(bool) == "I got: <class 'bool'>"
     90    reality = src.telephone.text(bool)
     91    my_expectation = "I got: <class 'bool'>"
     92    assert reality == my_expectation
     93
     94    # assert text(int) == "I got: <class 'int'>"
     95    reality = src.telephone.text(int)
     96    my_expectation = "I got: <class 'int'>"
     97    assert reality == my_expectation
     98
     99    # assert text(float) == "I got: <class 'float'>"
    100    reality = src.telephone.text(float)
    101    my_expectation = "I got: <class 'float'>"
    102    assert reality == my_expectation
    103
    104    # assert text(str) == "I got: <class 'str'>"
    105    reality = src.telephone.text(str)
    106    my_expectation = "I got: <class 'str'>"
    107    assert reality == my_expectation
    108
    109    # assert text(tuple) == "I got: <class 'tuple'>"
    110    reality = src.telephone.text(tuple)
    111    my_expectation = "I got: <class 'tuple'>"
    112    assert reality == my_expectation
    113
    114    # assert text(list) == "I got: <class 'list'>"
    115    reality = src.telephone.text(list)
    116    my_expectation = "I got: <class 'list'>"
    117    assert reality == my_expectation
    118
    119    # assert text(set) == "I got: <class 'set'>"
    120    reality = src.telephone.text(set)
    121    my_expectation = "I got: <class 'set'>"
    122    assert reality == my_expectation
    123
    124    # assert text(dict) == "I got: <class 'dict'>"
    125    reality = src.telephone.text(dict)
    126    my_expectation = "I got: <class 'dict'>"
    127    assert reality == my_expectation
    128
    129
    130# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

  • I remove the text function from test_telephone.py

     1import src.telephone
     2
     3
     4def test_passing_none():
     5    reality = src.telephone.text(None)
     6    my_expectation = 'I got: None'
     7    assert reality == my_expectation
     8
     9
    10def test_passing_booleans():
    

    all the tests are still green because all the calls to the text function that was in test_telephone.py are now to the text function in telephone.py in the src folder. When src.telephone.text is called Python follows this path

    src.telephone.text
    src
    └── telephone.py
        └── def text(the_input):
            └── return f'I got: {the input}'
    
  • I add a git commit message in the other terminal

    git commit -am \
    'separate solution from tests'
    

    the terminal shows a summary of the changes then goes back to the command line.

I can write solutions in a different module from the tests.


test_telephone

Now that the solution is separate from the tests, I can write the program that makes the tests pass without looking at test_telephone.py.


RED: make it fail


  • I close test_telephone.py

  • I delete the text in telephone.py and the terminal shows 9 failures. I start with the last AttributeError

    FAILED ...::test_passing_none -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_booleans -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_an_integer -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_float -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_string -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_tuple -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_list -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_set -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_dictionary -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    FAILED ...::test_passing_a_class -
        AttributeError: module 'src.telephone'
                        has no attribute 'text'
    =================== 10 failed in A.BCs ===================
    

    Can you make the tests pass without looking at how I solve it below? You can come back to compare solutions when you are done or if you get stuck.


GREEN: make it pass


  • I add the name to telephone.py

    1text
    

    the terminal is my friend, and shows NameError

    NameError: name 'text' is not defined
    
  • I point it to None (the simplest object) to define it

    1# text
    2text = None
    

    the terminal is my friend, and shows TypeError

    TypeError: 'NoneType' object is not callable
    

    because text points to None and I cannot call None like a function.

  • I make text a function to make it callable

    1# text
    2# text = None
    3def text():
    4    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: text() takes 0 positional arguments
               but 1 was given
    

    because this function definition does not allow any inputs, the parentheses are empty.

  • I make the function take input

    1# text
    2# text = None
    3# def text():
    4def text(value):
    5    return None
    

    the terminal is my friend, and shows AssertionError

    E       assert None == "I got: <class 'object'>"
    
  • I copy the string from the terminal and paste it in the return statement to match the expectation of the test

    1# text
    2# text = None
    3# def text():
    4def text(value):
    5    # return None
    6    return "I got: <class 'object'>"
    

    the terminal is my friend, and shows AssertionError

    E       assert "I got: <class 'object'>"
                == "I got: <class 'bool'>"
    
  • I change the return statement to see the difference between the input and the expected output (remember the identity function?)

    1# text
    2# text = None
    3# def text():
    4def text(value):
    5    # return None
    6    # return "I got: <class 'object'>"
    7    return value
    

    the test summary info shows that every test has AssertionError

    FAILED ...::test_passing_none -
        AssertionError: assert None == 'I got: None'
    FAILED ...::test_passing_booleans -
        AssertionError: assert False == 'I got: False'
    FAILED ...::test_passing_an_integer -
        AssertionError: assert 1234 == 'I got: 1234'
    FAILED ...::test_passing_a_float -
        AssertionError: assert 5.678 == 'I got: 5.678'
    FAILED ...::test_passing_a_string -
        AssertionError: assert 'hi' == 'I got: hi'
    FAILED ...::test_passing_a_tuple -
        assert (0, 1, 2, 'n') == "I got: (0, 1, 2, 'n')"
    FAILED ...::test_passing_a_list -
        assert [0, 1, 2, 'n'] == "I got: [0, 1, 2, 'n']"
    FAILED ...::test_passing_a_set -
        assert {0, 1, 2, 'n'} == "I got: {0, 1, 2, 'n'}"
    FAILED ...::test_passing_a_dictionary -
        assert {'key0': 'value0', 'keyN': [0, 1, 2, 'n']}
    == "I got: {'key0': 'value0',...
    FAILED ...::test_passing_a_class -
        assert <class 'object'> == "I got: <class 'object'>"
    

    they all expect the input (value) as part of the message

  • I add a return statement with an f-string

    1# text
    2# text = None
    3# def text():
    4def text(value):
    5    # return None
    6    # return "I got: <class 'object'>"
    7    # return value
    8    return f'I got: {value}'
    

    and all the tests are passing! I am a programmer!!

  • I remove the commented lines

    1def text(value):
    2    return f'I got: {value}'
    
  • I add a git commit message in the other terminal

    git commit --all --message 'test telephone'
    

    the terminal shows a summary of the changes then goes back to the command line.


close the project

  • I close telephone.py

  • I click in the terminal where the tests are running

  • I use q on the keyboard to leave the tests. The terminal goes back to the command line.

  • I change directory to the parent of telephone

    cd ..
    

    the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory.


review

I can use the unittest library to write tests with the methods of the unittest.TestCase class or I can write them with bare assert statements.


code from the chapter

Do you want to see all the CODE I typed in this chapter?


what is next?

You now know:


rate pumping python

If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too.