how to pass values

I want to be able to send things from tests to the program I am testing and compare what I think will happen with the results I get. This helps me see what is the same, and what is different, the difference helps me know what to change to get what I want.

I use the identity function to show how input is passed from a test to a function in a module


preview

Here are the tests I have by the end of the chapter

 1import src.telephone
 2import unittest
 3
 4
 5class TestTelephone(unittest.TestCase):
 6
 7    def test_passing_a_string(self):
 8        self.assertEqual(
 9            src.telephone.text("hello"),
10            "I received: hello"
11        )
12        self.assertEqual(
13            src.telephone.text("yes"),
14            "I received: yes"
15        )
16
17    def test_passing_a_class(self):
18        self.assertEqual(
19            src.telephone.text(object),
20            "I received: <class 'object'>"
21        )
22
23    def test_passing_none(self):
24        self.assertEqual(
25            src.telephone.text(None),
26            "I received: None"
27        )
28
29    def test_passing_a_boolean(self):
30        self.assertEqual(
31            src.telephone.text(True),
32            "I received: True"
33        )
34        self.assertEqual(
35            src.telephone.text(False),
36            "I received: False"
37        )
38
39    def test_passing_an_integer(self):
40        self.assertEqual(
41            src.telephone.text(1234),
42            "I received: 1234"
43        )
44
45    def test_passing_a_float(self):
46        self.assertEqual(
47            src.telephone.text(1.234),
48            "I received: 1.234"
49        )
50
51    def test_passing_a_tuple(self):
52        self.assertEqual(
53            src.telephone.text((1, 2, 3, "n")),
54            "I received: (1, 2, 3, 'n')"
55        )
56
57    def test_passing_a_list(self):
58        self.assertEqual(
59            src.telephone.text([1, 2, 3, "n"]),
60            "I received: [1, 2, 3, 'n']"
61        )
62
63    def test_passing_a_dictionary(self):
64        self.assertEqual(
65            src.telephone.text({
66                'key1': 'value1',
67                'keyN': [1, 2, 3, 'n'],
68            }),
69            "I received: {'key1': 'value1', 'keyN': [0, 1, 2, 'n']}"
70        )
71
72
73# Exceptions seen
74# AssertionError
75# NameError
76# AttributeError
77# TypeError

start the project

  • I name this project telephone

  • I open a terminal

  • then I make a directory for the project

    mkdir telephone
    

    the terminal goes back to the command line

    .../pumping_python
    
  • I change directory to the project

    cd telephone
    

    the terminal shows I am now in the telephone folder

    .../pumping_python/telephone
    
  • I make a folder for the source code

    mkdir src
    

    the terminal goes back to the command line

    .../pumping_python/telephone
    
  • I use touch to make an empty file for the program in the src folder

    touch src/telephone.py
    

    on Windows without Windows Subsystem for Linux use New-Item src/telephone.py instead of touch src/telephone.py

    New-Item src/telephone.py
    

    the terminal goes back to the command line

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

    mkdir tests
    

    the terminal goes back to the command line

  • I use touch to make an empty file in the tests folder to tell Python that it is a Python package

    Attention

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

    touch tests/__init__.py
    

    on Windows without Windows Subsystem for Linux use New-Item tests/__init__.py instead of touch tests/__init__.py

    New-Item tests/__init__.py
    

    the terminal goes back to the command line

  • I make an empty file for the actual test

    touch tests/test_telephone.py
    

    on Windows without Windows Subsystem for Linux use New-Item tests/test_telephone.py instead of touch tests/test_telephone.py

    New-Item tests/test_telephone.py
    

    the terminal goes back to the command line

  • I open test_telephone.py in the editor of the Integrated Development Environment (IDE)

    Tip

    I can open a file from the terminal in Visual Studio Code by typing code and the name of the file with

    code tests/test_telephone.py
    

    test_telephone.py opens up in the editor

  • I add the first failing test to test_telephone.py

    1import unittest
    2
    3
    4class TestTelephone(unittest.TestCase):
    5
    6    def test_failure(self):
    7        self.assertFalse(True)
    
  • I make a virtual environment in the terminal

    python3 -m venv .venv
    

    on Windows without Windows Subsystem for Linux use python3 -m venv .venv instead of python3 -m venv .venv

    python -m venv .venv
    

    the terminal takes some time then goes back to the command line

  • I activate the virtual environment

    source .venv/bin/activate
    

    on Windows without Windows Subsystem for Linux use .venv/bin/activate.ps1 instead of source .venv/bin/activate

    .venv/scripts/activate.ps1
    

    the terminal shows

    (.venv) .../pumping_python/telephone
    
  • I upgrade the Python package manager (pip) to the latest version

    python3 -m pip install --upgrade pip
    

    the terminal shows pip being uninstalled then installs the latest version or shows that it is already the latest version

  • I make a requirements.txt file for the Python programs my project needs

    echo "pytest-watch" > requirements.txt
    

    the terminal goes back to the command line

  • I use pip to use the requirements file to install pytest-watch

    python3 -m pip install --requirement requirements.txt
    

    on Windows without Windows Subsystem for Linux use python -m pip install --requirement requirements.txt instead of python3 -m pip install --requirement requirements.txt

    python -m pip install --requirement requirements.txt
    

    the terminal shows pip downloads and installs the Python programs that pytest-watch needs to run

  • I use pytest-watch to run the test

    pytest-watch
    

    the terminal shows

    ================================ FAILURES ================================
    ______________________ TestTelephone.test_failure ________________________
    
    self = <tests.test_telephone.TestTelephone testMethod=test_failure>
    
        def test_failure(self):
    >       self.assertFalse(True)
    E       AssertionError: True is not false
    
    tests/test_telephone.py:7: AssertionError
    ======================== short test summary info =========================
    FAILED tests/test_telephone.py::TestTelephone::test_failure - AssertionError: True is not false
    =========================== 1 failed in X.YZs ============================
    
  • I hold ctrl (Windows/Linux) or option or command (MacOS) on the keyboard and use the mouse to click on tests/test_telephone.py:7 to open it in the editor

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

     7        self.assertFalse(True)
     8
     9
    10# Exceptions seen
    11# AssertionError
    
  • then I change True to False in the assertion

    7        self.assertFalse(False)
    

    the test passes


test_passing_a_string

RED: make it fail

I change test_failure to test_passing_a_string

 1import unittest
 2
 3
 4class TestTelephone(unittest.TestCase):
 5
 6    def test_passing_a_string(self):
 7        self.assertEqual(
 8            src.telephone.text("hello"),
 9            "I received: hello"
10        )
11
12
13# Exceptions seen

the terminal shows NameError

NameError: name 'src' is not defined

there is no definition for src in test_telephone.py

GREEN: make it pass

  • I add the error to the list of Exceptions seen

    13# Exceptions seen
    14# AssertionError
    15# NameError
    
  • then I add an import statement for the telephone module which is in the src folder, at the top of the file

    1import src.telephone
    2import unittest
    3
    4
    5class TestTelephone(unittest.TestCase):
    

    the terminal shows AttributeError

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

    there is no definition for text in telephone.py in the src folder

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

    14# Exceptions seen
    15# AssertionError
    16# NameError
    17# AttributeError
    
  • I open telephone.py from the src folder in the editor, then add a name

    1text
    

    the terminal shows NameError

    NameError: name 'text' is not defined
    

    the name is in the file but I have not told Python what it means

  • I point text to None

    1text = None
    

    the terminal shows TypeError

    TypeError: 'NoneType' object is not callable
    

    I cannot call None the way I can call a function

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

    14# Exceptions seen
    15# AssertionError
    16# NameError
    17# AttributeError
    18# TypeError
    
  • I change text to a function in telephone.py to make it callable

    1def text():
    2    return None
    

    the terminal shows TypeError

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

    src.telephone.text was called with "hello" as input but the definition of the function does not take any input - the parentheses are empty

  • I make the function take input and call it the_input

    1def text(the_input):
    2    return None
    

    the terminal shows AssertionError

    AssertionError: None != 'I received: hello'
    

    the test expects 'I received: hello' and the text function returns None

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

    1def text(the_input):
    2    return 'I received: hello'
    

    the test passes!

REFACTOR: make it better

The problem with this solution is that the text function does not care about the input it gets, it always returns 'I received: hello' when called. I want it to return the value it gets as part of the message.

RED: make it fail

I add a new assertion to test_passing_a_string in test_telephone.py

 7    def test_passing_a_string(self):
 8        self.assertEqual(
 9            src.telephone.text("hello"),
10            "I received: hello"
11        )
12        self.assertEqual(
13            src.telephone.text("yes"),
14            "I received: yes"
15        )
16
17
18# Exceptions seen

the terminal shows AssertionError

AssertionError: 'I received: hello' != 'I received: yes'

the text function always returns 'I received: hello', the test expects 'I received: yes'

GREEN: make it pass

I change the return statement in telephone.py to match

1def text(the_input):
2    return 'I received: yes'

the terminal shows AssertionError

AssertionError: 'I received: yes' != 'I received: hello'

it did not work, my change broke the test that was passing before. The return statement has to use the input

string interpolation

I use an f-string which lets me add any values I want to a string

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

the test passes.

This is called string interpolation, I can use it to put values in strings.

A string is any characters inside quotes e.g.

  • 'single quotes'

  • '''triple single quotes'''

  • "double quotes"

  • """triple double quotes"""


test_passing_a_class

RED: make it fail

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

12        self.assertEqual(
13            src.telephone.text("yes"),
14            "I received: yes"
15        )
16
17    def test_passing_a_class(self):
18        self.assertEqual(
19            src.telephone.text(object),
20            "I received: object"
21        )
22
23
24# Exceptions seen

the terminal shows AssertionError

AssertionError: "I received: <class 'object'>" != 'I received: object'

object is the mother class that all Python classes come from

GREEN: make it pass

I change the expectation in the test to match the result

17    def test_passing_a_class(self):
18        self.assertEqual(
19            src.telephone.text(object),
20            "I received: <class 'object'>"
21        )

the test passes

REFACTOR: make it better

  • I add another assertion with the TestTelephone class to test_passing_a_class in test_telephone.py

    17    def test_passing_a_class(self):
    18        self.assertEqual(
    19            src.telephone.text(object),
    20            "I received: <class 'object'>"
    21        )
    22        self.assertEqual(
    23            src.telephone.text(TestTelephone),
    24            "I received: <class 'object'>"
    25        )
    

    the terminal shows AssertionError

    AssertionError: "I received: <class 'tests.test_telephone.TestTelephone'>" != "I received: <class 'object'>"
    

    even though they are both classes, object and TestTelephone are different

  • I change the expectation

    17    def test_passing_a_class(self):
    18        self.assertEqual(
    19            src.telephone.text(object),
    20            "I received: <class 'object'>"
    21        )
    22        self.assertEqual(
    23            src.telephone.text(TestTelephone),
    24            "I received: <class 'tests.test_telephone.TestTelephone'>"
    25        )
    

    the test passes


test_passing_none

RED: make it fail

I add a new failing test for None in test_telephone.py

22        self.assertEqual(
23            src.telephone.text(TestTelephone),
24            "I received: <class 'tests.test_telephone.TestTelephone'>"
25        )
26
27    def test_passing_none(self):
28        self.assertEqual(
29            src.telephone.text(None),
30            "I received: 'None'"
31        )

the terminal shows AssertionError

AssertionError: 'I received: None' != "I received: 'None'"

GREEN: make it pass

I remove the quotes from around None in the expectation

27    def test_passing_none(self):
28        self.assertEqual(
29            src.telephone.text(None),
30            "I received: None"
31        )
32
33
34# Exceptions seen

the test passes.


test_passing_a_boolean

RED: make it fail

I add a test for booleans, first with an assertion for True

27    def test_passing_none(self):
28        self.assertEqual(
29            src.telephone.text(None),
30            "I received: None"
31        )
32
33    def test_passing_a_boolean(self):
34        self.assertEqual(
35            src.telephone.text(True),
36            "I received: 'True'"
37        )

the terminal shows AssertionError

AssertionError: "I received: True" != "I received: 'True'"

GREEN: make it pass

  • I change the expectation

    33    def test_passing_a_boolean(self):
    34        self.assertEqual(
    35            src.telephone.text(True),
    36            "I received: True"
    37        )
    

    the test passes

  • I add an assertion for False

    33    def test_passing_a_boolean(self):
    34        self.assertEqual(
    35            src.telephone.text(True),
    36            "I received: True"
    37        )
    38        self.assertEqual(
    39            src.telephone.text(False),
    40            "I received: 'False'"
    41        )
    

    the terminal shows AssertionError

    AssertionError: "I received: False" != "I received: 'False'"
    
  • I change the expectation

    33    def test_passing_a_boolean(self):
    34        self.assertEqual(
    35            src.telephone.text(True),
    36            "I received: True"
    37        )
    38        self.assertEqual(
    39            src.telephone.text(False),
    40            "I received: False"
    41        )
    42
    43
    44# Exceptions seen
    

    the test passes.


test_passing_an_integer

RED: make it fail

I add a test for an integer (a whole number)

38        self.assertEqual(
39            src.telephone.text(False),
40            "I received: False"
41        )
42
43    def test_passing_an_integer(self):
44        self.assertEqual(
45            src.telephone.text(1234),
46            "I received: '1234'"
47        )

the terminal shows AssertionError

AssertionError: 'I received: 1234' != "I received: '1234'"

GREEN: make it pass

I remove the quotes from the expectation

43    def test_passing_an_integer(self):
44        self.assertEqual(
45            src.telephone.text(1234),
46            "I received: 1234"
47        )
48
49
50# Exceptions seen

the test passes.


test_passing_a_float

RED: make it fail

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

43    def test_passing_an_integer(self):
44        self.assertEqual(
45            src.telephone.text(1234),
46            "I received: 1234"
47        )
48
49    def test_passing_a_float(self):
50        self.assertEqual(
51            src.telephone.text(1.234),
52            "I received: '1.234'"
53        )

the terminal shows AssertionError

AssertionError: 'I received: 1.234' != "I received: '1.234'"

GREEN: make it pass

I remove the quotes from the number

49    def test_passing_a_float(self):
50        self.assertEqual(
51            src.telephone.text(1.234),
52            "I received: 1.234"
53        )
54
55
56# Exceptions seen

the test passes.


test_passing_a_tuple

RED: make it fail

I add a test for a tuple (things in parentheses (()), separated by a comma)

49    def test_passing_a_float(self):
50        self.assertEqual(
51            src.telephone.text(1.234),
52            "I received: 1.234"
53        )
54
55    def test_passing_a_tuple(self):
56        self.assertEqual(
57            src.telephone.text((1, 2, 3, "n")),
58            "I received: '(1, 2, 3, n)'"
59        )

the terminal shows AssertionError

AssertionError: "I received: (1, 2, 3, 'n')" != "I received: '(1, 2, 3, n)'"

GREEN: make it pass

I change the expectation

55    def test_passing_a_tuple(self):
56        self.assertEqual(
57            src.telephone.text((1, 2, 3, "n")),
58            "I received: (1, 2, 3, 'n')"
59        )

the test passes.


test_passing_a_list

RED: make it fail

I add a test for a list (things in square brackets ([]), separated by a comma)

55    def test_passing_a_tuple(self):
56        self.assertEqual(
57            src.telephone.text((1, 2, 3, "n")),
58            "I received: (1, 2, 3, 'n')"
59        )
60
61    def test_passing_a_list(self):
62        self.assertEqual(
63            src.telephone.text([1, 2, 3, "n"]),
64            "I received: '[1, 2, 3, n]'"
65        )

the terminal shows AssertionError

AssertionError: "I received: [1, 2, 3, 'n']" != "I received: '[1, 2, 3, n]'"

GREEN: make it pass

I change the expectation to match reality

61    def test_passing_a_list(self):
62        self.assertEqual(
63            src.telephone.text([1, 2, 3, "n"]),
64            "I received: [1, 2, 3, 'n']"
65        )

the test passes.


test_passing_a_dictionary

RED: make it fail

I add a test for a dictionary (key-value pairs in curly braces ({}), separated by a comma)

61    def test_passing_a_list(self):
62        self.assertEqual(
63            src.telephone.text([1, 2, 3, "n"]),
64            "I received: [1, 2, 3, 'n']"
65        )
66
67    def test_passing_a_dictionary(self):
68        self.assertEqual(
69            src.telephone.text({
70                "key1": "value1",
71                "keyN": [0, 1, 2, "n"],
72            }),
73            "I received: '{key1: value1, keyN: [0, 1, 2, 'n']}'"
74        )

the terminal shows AssertionError

AssertionError: "I received: {'key1': 'value1', 'keyN': [0, 1, 2, 'n']}" != "I received: '{key1: value1, keyN: [0, 1, 2, 'n']}'"

GREEN: make it pass

I change the expectation

67    def test_passing_a_dictionary(self):
68        self.assertEqual(
69            src.telephone.text({
70                "key1": "value1",
71                "keyN": [0, 1, 2, "n"],
72            }),
73            "I received: {'key1': 'value1', 'keyN': [0, 1, 2, 'n']}"
74        )
75
76
77# Exceptions seen

the terminal shows all tests are passing.


test_telephone

Time to write the program that makes the tests pass without looking at test_telephone.py

RED: make it fail

  • I close test_telephone.py

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

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

GREEN: make it pass

  • I add the name to telephone.py

    1text
    

    the terminal shows NameError

    NameError: name 'text' is not defined
    

    I point it to None

    1text = None
    

    the terminal shows TypeError

    TypeError: 'NoneType' object is not callable
    

    I make text a function

    1def text():
    2    return None
    

    the terminal shows TypeError

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

    I make the function take input

    1def text(the_input):
    2    return None
    

    the terminal shows AssertionError

    AssertionError: None != 'I received: None'
    
  • I copy the string from the terminal and paste it in the return statement to match the expectation of the test

    1def text(the_input):
    2    return 'I received: None'
    

    the terminal shows AssertionError

    AssertionError: 'I received: None' != 'I received: 1234'
    
  • I add a return statement to see the difference between the input and the expected output

    1def text(the_input):
    2    return the_input
    3    return 'I received: None'
    

    the test summary info shows that every test has AssertionError

    AssertionError: True != 'I received: True'
    AssertionError: <class 'object'> != "I received: <class 'object'>"
    AssertionError: {'key1': 'value1', 'keyN': [0, 1, 2, 'n']} != "I received: {'key1': 'value1', 'keyN': [0, 1, 2, 'n']}"
    AssertionError: 1.234 != 'I received: 1.234'
    AssertionError: [1, 2, 3, 'n'] != "I received: [1, 2, 3, 'n']"
    AssertionError: "hello" != 'I received: hello'
    AssertionError: (1, 2, 3, 'n') != "I received: (1, 2, 3, 'n')"
    AssertionError: 1234 != 'I received: 1234'
    AssertionError: None != 'I received: None'
    

    they all expect the input as part of the message

  • I remove the first return statement then make the second one use an f-string

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

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


close the project

  • I close the file(s) I have open in the editor(s)

  • I click in the terminal and exit the tests with ctrl+c on the keyboard

  • I deactivate the virtual environment

    deactivate
    

    the terminal goes back to the command line, (.venv) is no longer on the left side

    .../pumping_python/telephone
    
  • I change directory to the parent of telephone

    cd ..
    

    the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory


review

Here are the tests I ran to see what happens when I pass Python basic data structures from a test to a program and place them in an f-string which is one way to do string interpolation

I also saw the following Exceptions


code from the chapter

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


what is next?

you have covered a bit so far and know

Would you like to use some of the assert methods from AssertionError to test Python’s data structures?


please leave a review