how to make a person


This is an exercise in making dictionaries with functions, two important objects to know in Python.

Imagine I want to make a contact list or database of people. I can use a function to represent filling out information for a person, for example

  • First Name

  • Last Name (Surname)

  • Sex

  • Year of Birth


preview

I have these tests by the end of the chapter

 1import datetime
 2import random
 3import src.person
 4import unittest
 5
 6
 7def pick_one(*choices):
 8    return random.choice(choices)
 9
10
11def get_random_name():
12    return pick_one(
13        'jane', 'joe', 'john', 'person',
14        'doe', 'smith', 'blow', 'public',
15    )
16
17
18def calculate_age(year_of_birth):
19    return (
20        datetime.datetime.now().year
21      - year_of_birth
22    )
23
24
25def get_random_year_of_birth():
26    this_year = datetime.datetime.now().year
27    return random.randint(
28        this_year-120, this_year
29    )
30
31
32class TestPerson(unittest.TestCase):
33
34    def test_factory_w_keyword_arguments(self):
35        year_of_birth = get_random_year_of_birth()
36
37        a_person = dict(
38            first_name=get_random_name(),
39            last_name=get_random_name(),
40            sex=pick_one('F', 'M'),
41        )
42
43        reality = src.person.factory(
44            **a_person,
45            year_of_birth=year_of_birth,
46        )
47        my_expectation = dict(
48            **a_person,
49            age=calculate_age(year_of_birth),
50        )
51        self.assertEqual(reality, my_expectation)
52
53    def test_factory_w_optional_arguments(self):
54        first_name = get_random_name()
55        year_of_birth = get_random_year_of_birth()
56
57        reality = src.person.factory(
58            first_name=first_name,
59            year_of_birth=year_of_birth,
60        )
61        my_expectation = dict(
62            first_name=first_name,
63            last_name='doe',
64            sex='M',
65            age=calculate_age(year_of_birth),
66        )
67        self.assertEqual(reality, my_expectation)
68
69    def test_factory_person_says_hello(self):
70        first_name = get_random_name()
71        last_name = get_random_name()
72        sex = pick_one('F', 'M')
73
74        year_of_birth = get_random_year_of_birth()
75        age = calculate_age(year_of_birth)
76
77        a_random_person = src.person.factory(
78            first_name=first_name,
79            last_name=last_name,
80            sex=sex,
81            year_of_birth=year_of_birth,
82        )
83
84        reality = src.person.say_hello(a_random_person)
85        my_expectation = (
86            f'Hi, my name is {first_name} {last_name}'
87            f' and I am {age}'
88        )
89        self.assertEqual(reality, my_expectation)
90
91
92# Exceptions seen
93# AssertionError
94# NameError
95# AttributeError
96# TypeError
97# SyntaxError

start the project

  • I name this project person

  • I open a terminal

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

    uv init person
    

    the terminal shows

    Initialized project `person` at `.../pumping_python/person`
    

    then goes back to the command line.

  • I change directory to the project

    cd person
    

    the terminal shows I am in the person folder

    .../pumping_python/person
    
  • I make a directory for the source code

    mkdir src
    

    the terminal goes back to the command line.

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

    mv main.py src/person.py
    
    Move-Item main.py src/person.py
    

    the terminal goes back to the command line.

  • 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 make a Python file for the tests in the tests directory

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

    the terminal goes back to the command line.

  • I open test_person.py

  • I add the first failing test to test_person.py

    1import unittest
    2
    3
    4class TestPerson(unittest.TestCase):
    5
    6    def test_failure(self):
    7        self.assertFalse(True)
    
  • I go back to the terminal to make a requirements file for the Python packages I need

    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 -am 'setup project'
    

    the terminal shows

    [main (root-commit) a0b12c3] setup project
     9 files changed, 148 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 src/person.py
     create mode 100644 tests/__init__.py
     create mode 100644 tests/test_person.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 ========================
    ______________________ TestPerson.test_failure ________________________
    
    self = <tests.test_person.TestPerson testMethod=test_failure>
    
        def test_failure(self):
    >       self.assertFalse(True)
    E       AssertionError: True is not false
    
    tests/test_person.py:7: AssertionError
    ================ short test summary info =================
    FAILED tests/test_person.py::TestPerson::test_failure - AssertionError: True is not false
    =================== 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

    fix those errors and try to run uv run pytest-watcher . --now again

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

     4class TestPerson(unittest.TestCase):
     5
     6    def test_failure(self):
     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_factory_w_keyword_arguments


RED: make it fail


  • I change test_failure to test_factory_w_keyword_arguments

     4class TestPerson(unittest.TestCase):
     5
     6    def test_factory_w_keyword_arguments(self):
     7        reality = src.person.factory()
     8        my_expectation = None
     9        self.assertEqual(reality, my_expectation)
    10
    11
    12# Exceptions seen
    13# AssertionError
    

    the terminal is my friend, and shows NameError

    NameError: name 'src' is not defined
    

    because there is no definition for src in test_person.py

  • I add NameError to the list of Exceptions seen

    12# Exceptions seen
    13# AssertionError
    14# NameError
    

GREEN: make it pass


  • I add an import statement for the person module at the top of test_person.py

    1import src.person
    2import unittest
    
  • I add AttributeError to the list of Exceptions seen

    13# Exceptions seen
    14# AssertionError
    15# NameError
    16# AttributeError
    
  • I use the Explorer to open person.py from the src folder

  • I delete the text, then add a function to person.py

    1def factory():
    2    return None
    

    the test passes.


REFACTOR: make it better


  • I want the function to take a keyword argument called first_name. I add it to the test in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10        )
    11        my_expectation = None
    12        self.assertEqual(reality, my_expectation)
    13
    14
    15# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'first_name'
    

    because the function definition for src.person.factory does not allow calling it with inputs (the parentheses are empty) and the test sends 'first_name' as input.

  • I add TypeError to the list of Exceptions seen

    15# Exceptions seen
    16# AssertionError
    17# NameError
    18# AttributeError
    19# TypeError
    
  • I add first_name as an input parameter to the function in person.py

    1def factory(first_name):
    2    return None
    

    the test passes.


  • I want the function to take a keyword argument called last_name. I add it to the test in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10            last_name='last_name',
    11        )
    12        my_expectation = None
    13        self.assertEqual(reality, my_expectation)
    14
    15
    16# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'last_name'.
               Did you mean 'first_name'?
    

    because the test called the factory function with a keyword argument (last_name) that is not in the function definition

  • I add last_name to the function definition in person.py

    1def factory(first_name, last_name):
    2    return None
    

    the test passes.


  • I want the function to take a keyword argument called sex. I add it to the test in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10            last_name='last_name',
    11            sex='M',
    12        )
    13        my_expectation = None
    14        self.assertEqual(reality, my_expectation)
    15
    16
    17# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'sex'
    

    because the test called the factory function with a keyword argument (sex) that is not in the function definition

  • I add sex as an input parameter to the factory function in person.py

    1def factory(
    2        first_name, last_name,
    3        sex,
    4    ):
    5    return None
    

    the test passes.


  • I want the function to take a keyword argument for year_of_birth. I add it to the test in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10            last_name='last_name',
    11            sex='M',
    12            year_of_birth=2000,
    13        )
    14        my_expectation = None
    15        self.assertEqual(reality, my_expectation)
    16
    17
    18# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'year_of_birth'
    

    because the test called the factory function with a keyword argument (year_of_birth) that is not in the function definition

  • I add the name to the function definition in person.py

    1def factory(
    2        first_name, last_name,
    3        sex, year_of_birth,
    4    ):
    5    return None
    

    the test passes.


  • I want the factory function to return a dictionary (any key-value pairs in curly braces { } separated by a comma) as output when it is called. I change my_expectation in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10            last_name='last_name',
    11            sex='M',
    12            year_of_birth=2000,
    13        )
    14        my_expectation = dict()
    15        self.assertEqual(reality, my_expectation)
    16
    17
    18# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != {}
    

    because the function returns None and the assertion expects {}

  • I change None to a dictionary in the return statement in person.py

    1def factory(
    2        first_name, last_name,
    3        sex, year_of_birth,
    4    ):
    5    # return None
    6    return {}
    

    the test passes because {} and dict() are two ways to make the empty dictionary.


  • I add a key called first_name to the dictionary for my_expectation, with the same value as what is given in the call to the factory function in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            first_name='first_name',
    10            last_name='last_name',
    11            sex='M',
    12            year_of_birth=2000,
    13        )
    14        my_expectation = dict(
    15            first_name='first_name',
    16        )
    17        self.assertEqual(reality, my_expectation)
    18
    19
    20# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: {} != {'first_name': 'first_name'}
    

    because the function returns the empty dictionary and the assertion expects one with first_name as the key

  • I change the return statement to give the test what it wants, in person.py

    1def factory(
    2        first_name, last_name,
    3        sex, year_of_birth,
    4    ):
    5    # return None
    6    # return {}
    7    return {'first_name': 'first_name'}
    

    the test passes.

  • I change the value of first_name to 'jane' to use a real name for reality and my_expectation, in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        reality = src.person.factory(
     9            # first_name='first_name',
    10            first_name='jane',
    11            last_name='last_name',
    12            sex='M',
    13            year_of_birth=2000,
    14        )
    15        my_expectation = dict(
    16            # first_name='first_name',
    17            first_name='jane',
    18        )
    19        self.assertEqual(reality, my_expectation)
    20
    21
    22# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: {'first_name': 'first_name'}
                 != {'first_name': 'jane'}
    

    because I changed the value for first_name in my_expectation

  • I change the value for the first_name key in person.py

    1def factory(
    2        first_name, last_name,
    3        sex, year_of_birth,
    4    ):
    5    # return None
    6    # return {}
    7    # return {'first_name': 'first_name'}
    8    return {'first_name': 'jane'}
    

    the test passes.

  • I typed the value for first_name two times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of 'jane' from test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9
    10        reality = src.person.factory(
    
  • I use the variable to remove repetition of 'jane'

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9
    10        reality = src.person.factory(
    11            # first_name='first_name',
    12            # first_name='jane',
    13            first_name=first_name,
    14            last_name='last_name',
    15            sex='M',
    16            year_of_birth=2000,
    17        )
    18        my_expectation = dict(
    19            # first_name='first_name',
    20            # first_name='jane',
    21            first_name=first_name,
    22        )
    23        self.assertEqual(reality, my_expectation)
    24
    25
    26# Exceptions seen
    

    the test is still green. I now only need to change the value of first_name in one place in the test.


  • I add a key called last_name to the dictionary, with the same value as what is given in the call to the factory function in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9
    10        reality = src.person.factory(
    11            # first_name='first_name',
    12            # first_name='jane',
    13            first_name=first_name,
    14            last_name='last_name',
    15            sex='M',
    16            year_of_birth=2000,
    17        )
    18        my_expectation = dict(
    19            # first_name='first_name',
    20            # first_name='jane',
    21            first_name=first_name,
    22            last_name='last_name',
    23        )
    24        self.assertEqual(reality, my_expectation)
    25
    26
    27# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane'}
     != {'first_name': 'jane', 'last_name': 'last_name'}
    
  • I change the return statement in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return None
     6    # return {}
     7    # return {'first_name': 'first_name'}
     8    # return {'first_name': 'jane'}
     9    return {
    10        'first_name': 'jane',
    11        'last_name': 'last_name',
    12    }
    

    the test passes.

  • I change the value of last_name to 'doe' to use a real name for reality and my_expectation, in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9
    10        reality = src.person.factory(
    11            # first_name='first_name',
    12            # first_name='jane',
    13            first_name=first_name,
    14            # last_name='last_name',
    15            last_name='doe',
    16            sex='M',
    17            year_of_birth=2000,
    18        )
    19        my_expectation = dict(
    20            # first_name='first_name',
    21            # first_name='jane',
    22            first_name=first_name,
    23            # last_name='last_name',
    24            last_name='doe',
    25        )
    26        self.assertEqual(reality, my_expectation)
    27
    28
    29# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'last_name'}
     != {'first_name': 'jane', 'last_name': 'doe'}
    

    because I changed the value for last_name in my_expectation

  • I change the value for the last_name key in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return None
     6    # return {}
     7    # return {'first_name': 'first_name'}
     8    # return {'first_name': 'jane'}
     9    return {
    10        'first_name': 'jane',
    11        # 'last_name': 'last_name',
    12        'last_name': 'doe',
    13    }
    

    the test passes.

  • I typed the value for last_name two times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of 'doe' from test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10
    11        reality = src.person.factory(
    
  • I use the variable to remove repetition of 'doe'

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10
    11        reality = src.person.factory(
    12            # first_name='first_name',
    13            # first_name='jane',
    14            first_name=first_name,
    15            # last_name='last_name',
    16            # last_name='doe',
    17            last_name=last_name,
    18            sex='M',
    19            year_of_birth=2000,
    20        )
    21        my_expectation = dict(
    22            # first_name='first_name',
    23            # first_name='jane',
    24            first_name=first_name,
    25            # last_name='last_name',
    26            # last_name='doe',
    27            last_name=last_name,
    28        )
    29        self.assertEqual(reality, my_expectation)
    30
    31
    32# Exceptions seen
    

    the test is still green. I now only need to change the value of first_name in one place in the test.


  • I add a key called sex to the dictionary, with the same value as what is given in the call to the factory function in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10
    11        reality = src.person.factory(
    12            # first_name='first_name',
    13            # first_name='jane',
    14            first_name=first_name,
    15            # last_name='last_name',
    16            # last_name='doe',
    17            last_name=last_name,
    18            sex='M',
    19            year_of_birth=2000,
    20        )
    21        my_expectation = dict(
    22            # first_name='first_name',
    23            # first_name='jane',
    24            first_name=first_name,
    25            # last_name='last_name',
    26            # last_name='doe',
    27            last_name=last_name,
    28            sex='M',
    29        )
    30        self.assertEqual(reality, my_expectation)
    31
    32
    33# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: {'first_name': 'jane', 'last_name': 'doe'}
                 != {'first_name': 'jane', 'last_name': 'doe',
                     'sex': 'M'}
    
  • I add a new key to the return statement in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return None
     6    # return {}
     7    # return {'first_name': 'first_name'}
     8    # return {'first_name': 'jane'}
     9    return {
    10        'first_name': 'jane',
    11        # 'last_name': 'last_name',
    12        'last_name': 'doe',
    13        'sex': 'M',
    14    }
    

    the test passes.

  • I change the value of sex to 'F' for reality and my_expectation, in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10
    11        reality = src.person.factory(
    12            # first_name='first_name',
    13            # first_name='jane',
    14            first_name=first_name,
    15            # last_name='last_name',
    16            # last_name='doe',
    17            last_name=last_name,
    18            # sex='M',
    19            sex='F',
    20            year_of_birth=2000,
    21        )
    22        my_expectation = dict(
    23            # first_name='first_name',
    24            # first_name='jane',
    25            first_name=first_name,
    26            # last_name='last_name',
    27            # last_name='doe',
    28            last_name=last_name,
    29            # sex='M',
    30            sex='F',
    31        )
    32        self.assertEqual(reality, my_expectation)
    33
    34
    35# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'}
     != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'}
    

    because I changed the value for sex in my_expectation

  • I change the value for the sex key in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return None
     6    # return {}
     7    # return {'first_name': 'first_name'}
     8    # return {'first_name': 'jane'}
     9    return {
    10        'first_name': 'jane',
    11        # 'last_name': 'last_name',
    12        'last_name': 'doe',
    13        # 'sex': 'M',
    14        'sex': 'F',
    15    }
    

    the test passes.

  • I typed the value for sex two times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of 'F' from test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11
    12        reality = src.person.factory(
    
  • I use the variable to remove repetition of 'F'

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11
    12        reality = src.person.factory(
    13            # first_name='first_name',
    14            # first_name='jane',
    15            first_name=first_name,
    16            # last_name='last_name',
    17            # last_name='doe',
    18            last_name=last_name,
    19            # sex='M',
    20            # sex='F',
    21            sex=sex,
    22            year_of_birth=2000,
    23        )
    24        my_expectation = dict(
    25            # first_name='first_name',
    26            # first_name='jane',
    27            first_name=first_name,
    28            # last_name='last_name',
    29            # last_name='doe',
    30            last_name=last_name,
    31            # sex='M',
    32            # sex='F',
    33            sex=sex,
    34        )
    35        self.assertEqual(reality, my_expectation)
    36
    37
    38# Exceptions seen
    

    the test is still green. I now only need to change the value of sex in one place in the test.


  • I want the factory function to return the age of the person it makes. I add a key to my_expectation

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11
    12        reality = src.person.factory(
    13            # first_name='first_name',
    14            # first_name='jane',
    15            first_name=first_name,
    16            # last_name='last_name',
    17            # last_name='doe',
    18            last_name=last_name,
    19            # sex='M',
    20            # sex='F',
    21            sex=sex,
    22            year_of_birth=2000,
    23        )
    24        my_expectation = dict(
    25            # first_name='first_name',
    26            # first_name='jane',
    27            first_name=first_name,
    28            # last_name='last_name',
    29            # last_name='doe',
    30            last_name=last_name,
    31            # sex='M',
    32            # sex='F',
    33            sex=sex,
    34            age=2026-2000,
    35        )
    36        self.assertEqual(reality, my_expectation)
    37
    38
    39# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'}
     != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': 26}
    
  • I add a new key to the return statement in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return None
     6    # return {}
     7    # return {'first_name': 'first_name'}
     8    # return {'first_name': 'jane'}
     9    return {
    10        'first_name': 'jane',
    11        # 'last_name': 'last_name',
    12        'last_name': 'doe',
    13        # 'sex': 'M',
    14        'sex': 'F',
    15        'age': 26,
    16    }
    

    the test passes.

  • I change 2000 to 1996 for reality and my_expectation, in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11
    12        reality = src.person.factory(
    13            # first_name='first_name',
    14            # first_name='jane',
    15            first_name=first_name,
    16            # last_name='last_name',
    17            # last_name='doe',
    18            last_name=last_name,
    19            # sex='M',
    20            # sex='F',
    21            sex=sex,
    22            # year_of_birth=2000,
    23            year_of_birth=1996,
    24        )
    25        my_expectation = dict(
    26            # first_name='first_name',
    27            # first_name='jane',
    28            first_name=first_name,
    29            # last_name='last_name',
    30            # last_name='doe',
    31            last_name=last_name,
    32            # sex='M',
    33            # sex='F',
    34            sex=sex,
    35            # age=2026-2000,
    36            age=2026-1996,
    37        )
    38        self.assertEqual(reality, my_expectation)
    39
    40
    41# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': 26}
     != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': 30}
    

    because I changed 2000 in my_expectation

  • I change the value for the age key in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    # return None
     6    # return {}
     7    # return {'first_name': 'first_name'}
     8    # return {'first_name': 'jane'}
     9    return {
    10        'first_name': 'jane',
    11        # 'last_name': 'last_name',
    12        'last_name': 'doe',
    13        # 'sex': 'M',
    14        'sex': 'F',
    15        # 'age': 26,
    16        'age': 30,
    17    }
    

    the test passes.

  • I remove the commented lines

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    return {
     6        'first_name': 'jane',
     7        'last_name': 'doe',
     8        'sex': 'F',
     9        'age': 30,
    10    }
    
  • I typed the year of birth two times in the test, which means I have to make a change in two places every time I want a different value for it. I add a variable to use to remove the repetition of the year of birth from test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11        year_of_birth = 1996
    12
    13        reality = src.person.factory(
    
  • I use the variable to remove repetition of the year of birth

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11        year_of_birth = 1996
    12
    13        reality = src.person.factory(
    14            # first_name='first_name',
    15            # first_name='jane',
    16            first_name=first_name,
    17            # last_name='last_name',
    18            # last_name='doe',
    19            last_name=last_name,
    20            # sex='M',
    21            # sex='F',
    22            sex=sex,
    23            # year_of_birth=2000,
    24            # year_of_birth=1996,
    25            year_of_birth=year_of_birth,
    26        )
    27        my_expectation = dict(
    28            # first_name='first_name',
    29            # first_name='jane',
    30            first_name=first_name,
    31            # last_name='last_name',
    32            # last_name='doe',
    33            last_name=last_name,
    34            # sex='M',
    35            # sex='F',
    36            sex=sex,
    37            # age=2026-2000,
    38            # age=2026-1996,
    39            age=2026-year_of_birth,
    40        )
    41        self.assertEqual(reality, my_expectation)
    42
    43
    44# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10        sex = 'F'
    11        year_of_birth = 1996
    12
    13        reality = src.person.factory(
    14            first_name=first_name,
    15            last_name=last_name,
    16            sex=sex,
    17            year_of_birth=year_of_birth,
    18        )
    19        my_expectation = dict(
    20            first_name=first_name,
    21            last_name=last_name,
    22            sex=sex,
    23            age=2026-year_of_birth,
    24        )
    25        self.assertEqual(reality, my_expectation)
    26
    27
    28# Exceptions seen
    

    I now only need to change the value of sex in one place in the test.

  • I open a new terminal then change directories to person

    cd person
    
  • I add a git commit message in the other terminal

    git commit -am 'add test_factory_w_keyword_arguments'
    

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


test factory with random year_of_birth

There is a problem with the calculation for the age, it will be wrong if this program is run after 2026.

I want the value of the age to be a calculation based on the current year so that it will always be correct (at least most of the time).

I can do that with the datetime module from The Python Standard Library which is used for dates and times.


RED: make it fail



GREEN: make it pass


I add an import statement for the datetime module at the top of test_person.py

1import datetime
2import src.person
3import unittest

the test passes.

import datetime brings in an object (everything in Python is an object) for the datetime module so I can use it in test_person.py.


REFACTOR: make it better


I want to use random values in the test to make sure the factory function can handle different values and always calculates the right age.

  • I use a random integer (a whole number without decimals) for the year_of_birth variable

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        last_name = 'doe'
    12        sex = 'F'
    13        # year_of_birth = 1996
    14        year_of_birth = random.randint(
    15            datetime.datetime.now().year-120,
    16            datetime.datetime.now().year
    17        )
    18
    19        reality = src.person.factory(
    20            first_name=first_name,
    21            last_name=last_name,
    22            sex=sex,
    23            year_of_birth=year_of_birth,
    24        )
    

    the terminal is my friend, and shows NameError

    NameError: name 'random' is not defined.
               Did you forget to import 'random'?
    
    • random is the random module

    • random.randint() is a call to the randint method from the random module. Okay, this one does not use the same name again

    • datetime.datetime.now().year gives me this year

    • datetime.datetime.now().year-120 gives me this year minus 120

    • random.randint(datetime.datetime.now().year-120, datetime.datetime.now().year) gives me a random number from 120 years ago, up to and including the current year

  • I add an import statement for the random module at the top of test_person.py

    1import datetime
    2import random
    3import src.person
    4import unittest
    
  • I add a calculation for the age with the today method to the return statement in person.py

     1def factory(
     2        first_name, last_name,
     3        sex, year_of_birth,
     4    ):
     5    return {
     6        'first_name': 'jane',
     7        'last_name': 'doe',
     8        'sex': 'F',
     9        # 'age': 30,
    10        'age': (
    11            datetime.datetime.today().year
    12          - year_of_birth
    13        ),
    14    }
    

    the terminal is my friend, and shows NameError

    NameError: name 'datetime' is not defined.
               Did you forget to import 'datetime'?
    

    because datetime is not defined in this file

  • I add an import statement for the datetime module at the top of person.py

    1import datetime
    2
    3
    4def factory(
    5        first_name, last_name,
    6        sex, year_of_birth,
    7    ):
    

    the test passes.

    import datetime brings in an object (everything in Python is an object) for the datetime module so I can use it in person.py.

  • I add a variable

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        last_name = 'doe'
    12        sex = 'F'
    13        # year_of_birth = 1996
    14        this_year = datetime.datetime.now().year
    15        year_of_birth = random.randint(
    16            datetime.datetime.now().year-120,
    17            datetime.datetime.now().year
    18        )
    
  • I use the variable to remove repetition of datetime.datetime.now().year from the test

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        last_name = 'doe'
    12        sex = 'F'
    13        # year_of_birth = 1996
    14        this_year = datetime.datetime.now().year
    15        year_of_birth = random.randint(
    16            # datetime.datetime.now().year-120,
    17            # datetime.datetime.now().year
    18            this_year-120, this_year
    19        )
    20
    21        reality = src.person.factory(
    22            first_name=first_name,
    23            last_name=last_name,
    24            sex=sex,
    25            year_of_birth=year_of_birth,
    26        )
    27        my_expectation = dict(
    28            first_name=first_name,
    29            last_name=last_name,
    30            sex=sex,
    31            # age=2026-year_of_birth,
    32            # age=(
    33            #     datetime.datetime.today().year
    34            #   - year_of_birth
    35            # ),
    36            age=this_year-year_of_birth,
    37        )
    38        self.assertEqual(reality, my_expectation)
    39
    40
    41# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am 'use random values for age'
    

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


test factory with random sex


RED: make it fail


  • I go back to the terminal that is running the tests

  • I add randomness to the sex variable in test_person.py

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        last_name = 'doe'
    12        # sex = 'F'
    13        sex = random.choice(('F', 'M'))
    14        # year_of_birth = 1996
    15        this_year = datetime.datetime.now().year
    16        year_of_birth = random.randint(
    17            # datetime.datetime.now().year-120,
    18            # datetime.datetime.now().year
    19            this_year-120, this_year
    20        )
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if sex is randomly 'F'.

    If sex is randomly 'M', the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': X}
     != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M',
         'age': X}
    

    where X is the random age


GREEN: make it pass


  • I add the sex input parameter instead of a value that does not change, to the return statement in person.py

     4def factory(
     5        first_name, last_name,
     6        sex, year_of_birth,
     7    ):
     8    return {
     9        'first_name': 'jane',
    10        'last_name': 'doe',
    11        # 'sex': 'F',
    12        'sex': sex,
    13        # 'age': 30,
    14        'age': (
    15            datetime.datetime.today().year
    16          - year_of_birth
    17        ),
    18    }
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures

  • I add a git commit message in the other terminal

    git commit -am 'use random values for sex'
    

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


test factory with random last name


RED: make it fail


  • I go back to the terminal that is running the tests

  • I use random.choice for the last_name variable in test_person.py

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        # last_name = 'doe'
    12        last_name = random.choice((
    13            'doe', 'smith', 'blow', 'public',
    14        ))
    15        # sex = 'F'
    16        sex = random.choice(('F', 'M'))
    17        # year_of_birth = 1996
    18        this_year = datetime.datetime.now().year
    19        year_of_birth = random.randint(
    20            # datetime.datetime.now().year-120,
    21            # datetime.datetime.now().year
    22            this_year-120, this_year
    23        )
    
  • I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if last_name is 'doe'.

    If last_name is NOT doe, the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': 'doe', 'sex': Y,
         'age': X}
     != {'first_name': 'jane', 'last_name': Z,     'sex': Y,
         'age': X}
    

    where Z is the random last name, X is the random age, and Y is the random sex.


GREEN: make it pass


  • I use the last_name input parameter as the value for the 'last_name' key in the return statement in person.py

     4def factory(
     5        first_name, last_name,
     6        sex, year_of_birth,
     7    ):
     8    return {
     9        'first_name': 'jane',
    10        # 'last_name': 'doe',
    11        'last_name': last_name,
    12        # 'sex': 'F',
    13        'sex': sex,
    14        # 'age': 30,
    15        'age': (
    16            datetime.datetime.today().year
    17          - year_of_birth
    18        ),
    19    }
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures

  • I add a git commit message in the other terminal

    git commit -am 'use random values for last_name'
    

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


test factory with random first name


RED: make it fail


  • I go back to the terminal that is running the tests

  • I add randomness to the first_name variable in test_person.py

     9    def test_factory_w_keyword_arguments(self):
    10        # first_name = 'jane'
    11        first_name = random.choice((
    12            'jane', 'joe', 'john', 'person',
    13        ))
    14        # last_name = 'doe'
    15        last_name = random.choice((
    16            'doe', 'smith', 'blow', 'public',
    17        ))
    18        # sex = 'F'
    19        sex = random.choice(('F', 'M'))
    20        # year_of_birth = 1996
    21        this_year = datetime.datetime.now().year
    22        year_of_birth = random.randint(
    23            # datetime.datetime.now().year-120,
    24            # datetime.datetime.now().year
    25            this_year-120, this_year
    26        )
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if first_name is 'jane'.

    If first_name is not 'jane' the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': 'jane', 'last_name': Z, 'sex': Y, 'age': X}
     != {'first_name': A,      'last_name': Z, 'sex': Y, 'age': X}
    

    where A is the random first name, X is the random age, Y is the random sex, and Z is the random last name


GREEN: make it pass


I add the first_name input parameter instead of a value that does not change, to the return statement in person.py

 4def factory(
 5        first_name, last_name,
 6        sex, year_of_birth,
 7    ):
 8    return {
 9        # 'first_name': 'jane',
10        'first_name': first_name,
11        # 'last_name': 'doe',
12        'last_name': last_name,
13        # 'sex': 'F',
14        'sex': sex,
15        # 'age': 30,
16        'age': (
17            datetime.datetime.today().year
18          - year_of_birth
19        ),
20    }

I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures


REFACTOR: make it better


  • I remove the commented lines

     4def factory(
     5        first_name, last_name,
     6        sex, year_of_birth,
     7    ):
     8    return {
     9        'first_name': first_name,
    10        'last_name': last_name,
    11        'sex': sex,
    12        'age': (
    13            datetime.datetime.today().year
    14          - year_of_birth
    15        ),
    16    }
    
  • I add a git commit message in the other terminal

    git commit -am 'use random values for first_name'
    

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


extract pick_one function


  • I go back to the terminal that is running the tests

  • I add a function for the calls to the random.choice method

     1import datetime
     2import random
     3import src.person
     4import unittest
     5
     6
     7def pick_one(choices):
     8    return random.choice(choices)
     9
    10
    11class TestPerson(unittest.TestCase):
    12
    13    def test_factory_w_keyword_arguments(self):
    
  • I use the new function to remove repetition of random.choice from the test

    13    def test_factory_w_keyword_arguments(self):
    14        # first_name = 'jane'
    15        # first_name = random.choice((
    16        #     'jane', 'joe', 'john', 'person',
    17        # ))
    18        first_name = pick_one(
    19            'jane', 'joe', 'john', 'person',
    20        )
    21        # last_name = 'doe'
    22        # last_name = random.choice((
    23        #     'doe', 'smith', 'blow', 'public',
    24        # ))
    25        last_name = pick_one(
    26            'doe', 'smith', 'blow', 'public',
    27        )
    28        # sex = 'F'
    29        # sex = random.choice(('F', 'M'))
    30        sex = pick_one('F', 'M')
    31        # year_of_birth = 1996
    32        this_year = datetime.datetime.now().year
    33        year_of_birth = random.randint(
    34            # datetime.datetime.now().year-120,
    35            # datetime.datetime.now().year
    36            this_year-120, this_year
    37        )
    

    the terminal is my friend, and shows TypeError

    TypeError: pick_one() takes 1 positional argument
               but 4 were given
    

    because the function definition only takes one input and the test sends four for first_name, four for last_name and two for sex.

    I want the function to be able to take any number of arguments I send, without it knowing how many I will send.

  • I change the definition of the pick_one function with a starred expression like I did in test_w_unknown_arguments so that it can take any number of positional arguments

    7def pick_one(*choices):
    8    return random.choice(choices)
    

    the test is green again, because Python reads the positional arguments as a tuple in the function since I used a starred expression (*choices)

  • I add a git commit message in the other terminal

    git commit -am 'extract pick_one function'
    

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


test factory with a dictionary

The difference between the call to the factory function and the expected dictionary in the test is that one has a year of birth and the other does a calculation with the year of birth. The other things are the same.


RED: make it fail


  • I go back to the terminal that is running the tests

  • I add a dictionary to use to remove the repeating parts

    13    def test_factory_w_keyword_arguments(self):
    14        # first_name = 'jane'
    15        # first_name = random.choice((
    16        #     'jane', 'joe', 'john', 'person',
    17        # ))
    18        first_name = pick_one(
    19            'jane', 'joe', 'john', 'person',
    20        )
    21        # last_name = 'doe'
    22        # last_name = random.choice((
    23        #     'doe', 'smith', 'blow', 'public',
    24        # ))
    25        last_name = pick_one(
    26            'doe', 'smith', 'blow', 'public',
    27        )
    28        # sex = 'F'
    29        # sex = random.choice(('F', 'M'))
    30        sex = pick_one('F', 'M')
    31        # year_of_birth = 1996
    32        this_year = datetime.datetime.now().year
    33        year_of_birth = random.randint(
    34            # datetime.datetime.now().year-120,
    35            # datetime.datetime.now().year
    36            this_year-120, this_year
    37        )
    38
    39        a_person = dict(
    40            first_name=first_name,
    41            last_name=last_name,
    42            sex=sex,
    43        )
    44
    45        reality = src.person.factory(
    46            first_name=first_name,
    47            last_name=last_name,
    48            sex=sex,
    49            year_of_birth=year_of_birth,
    50        )
    51        my_expectation = dict(
    52            first_name=first_name,
    53            last_name=last_name,
    54            sex=sex,
    55            # age=2026-year_of_birth,
    56            # age=(
    57            #     datetime.datetime.today().year
    58            #   - year_of_birth
    59            # ),
    60            age=this_year-year_of_birth,
    61        )
    62        self.assertEqual(reality, my_expectation)
    63
    64
    65# Exceptions seen
    
  • I use the new variable to remove the repeating parts

    39        a_person = dict(
    40            first_name=first_name,
    41            last_name=last_name,
    42            sex=sex,
    43        )
    44
    45        reality = src.person.factory(
    46            # first_name=first_name,
    47            # last_name=last_name,
    48            # sex=sex,
    49            a_person,
    50            year_of_birth=year_of_birth,
    51        )
    52        my_expectation = dict(
    53            # first_name=first_name,
    54            # last_name=last_name,
    55            # sex=sex,
    56            a_person,
    57            # age=2026-year_of_birth,
    58            # age=(
    59            #     datetime.datetime.today().year
    60            #   - year_of_birth
    61            # ),
    62            age=this_year-year_of_birth,
    63        )
    64        self.assertEqual(reality, my_expectation)
    65
    66
    67# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() missing 2 required
               positional arguments: 'last_name' and 'sex'
    

    because the factory function now takes a_person as the first positional argument (first_name), and wants the other required arguments.

    I want the function to take the key-value pairs of the dictionary (a_person) as keyword arguments.


GREEN: make it pass


I use a double starred expression for the dictionary like I did in test_w_unknown_arguments

45        reality = src.person.factory(
46            # first_name=first_name,
47            # last_name=last_name,
48            # sex=sex,
49            **a_person,
50            year_of_birth=year_of_birth,
51        )
52        my_expectation = dict(
53            # first_name=first_name,
54            # last_name=last_name,
55            # sex=sex,
56            **a_person,
57            # age=2026-year_of_birth,
58            # age=(
59            #     datetime.datetime.today().year
60            #   - year_of_birth
61            # ),
62            age=this_year-year_of_birth,
63        )
64        self.assertEqual(reality, my_expectation)
65
66
67# Exceptions seen

the test is green again, because Python sends the dictionary as keyword arguments since I used a double starred expression (*choices).


REFACTOR: make it better


  • I use the values of first_name, last_name and the sex variables in the a_person dictionary because they are now only used once, by a_person

    13    def test_factory_w_keyword_arguments(self):
    14        # first_name = 'jane'
    15        # first_name = random.choice((
    16        #     'jane', 'joe', 'john', 'person',
    17        # ))
    18        # first_name = pick_one(
    19        #     'jane', 'joe', 'john', 'person',
    20        # )
    21        # last_name = 'doe'
    22        # last_name = random.choice((
    23        #     'doe', 'smith', 'blow', 'public',
    24        # ))
    25        # last_name = pick_one(
    26        #     'doe', 'smith', 'blow', 'public',
    27        # )
    28        # sex = 'F'
    29        # sex = random.choice(('F', 'M'))
    30        # sex = pick_one('F', 'M')
    31        # year_of_birth = 1996
    32        this_year = datetime.datetime.now().year
    33        year_of_birth = random.randint(
    34            # datetime.datetime.now().year-120,
    35            # datetime.datetime.now().year
    36            this_year-120, this_year
    37        )
    38
    39        a_person = dict(
    40            # first_name=first_name,
    41            # last_name=last_name,
    42            # sex=sex,
    43            first_name=pick_one(
    44                'jane', 'joe', 'john', 'person',
    45            ),
    46            last_name=pick_one(
    47                'doe', 'smith', 'blow', 'public',
    48            ),
    49            sex=pick_one('F', 'M'),
    50        )
    

    still green.

  • I make a function with a tuple of all the names for the test to have more random names to pick from

     7def pick_one(*choices):
     8    return random.choice(choices)
     9
    10
    11def get_random_name():
    12    return pick_one(
    13        'jane', 'joe', 'john', 'person',
    14        'doe', 'smith', 'blow', 'public',
    15    )
    16
    17
    18class TestPerson(unittest.TestCase):
    19
    20    def test_factory_w_keyword_arguments(self):
    
  • I use the function in the a_person dictionary

    46        a_person = dict(
    47            # first_name=first_name,
    48            # last_name=last_name,
    49            # sex=sex,
    50            # first_name=pick_one(
    51            #     'jane', 'joe', 'john', 'person',
    52            # ),
    53            # last_name=pick_one(
    54            #     'doe', 'smith', 'blow', 'public',
    55            # ),
    56            first_name=get_random_name(),
    57            last_name=get_random_name(),
    58            sex=pick_one('F', 'M'),
    59        )
    

    green.

  • I remove the commented lines

    20    def test_factory_w_keyword_arguments(self):
    21        this_year = datetime.datetime.now().year
    22        year_of_birth = random.randint(
    23            this_year-120, this_year
    24        )
    25
    26        a_person = dict(
    27            first_name=get_random_name(),
    28            last_name=get_random_name(),
    29            sex=pick_one('F', 'M'),
    30        )
    31
    32        reality = src.person.factory(
    33            **a_person,
    34            year_of_birth=year_of_birth,
    35        )
    36        my_expectation = dict(
    37            **a_person,
    38            age=this_year-year_of_birth,
    39        )
    40        self.assertEqual(reality, my_expectation)
    41
    42
    43# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am 'extract a_person dictionary'
    

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


test_factory_w_optional_arguments

I want to see what happens when I try to make a person without a value for the last_name argument.


RED: make it fail


  • I go back to the terminal that is running the tests

  • I make a copy of test_factory_w_keyword_arguments and paste it below in test_person.py

    32        reality = src.person.factory(
    33            **a_person,
    34            year_of_birth=year_of_birth,
    35        )
    36        my_expectation = dict(
    37            **a_person,
    38            age=this_year-year_of_birth,
    39        )
    40        self.assertEqual(reality, my_expectation)
    41
    42    def test_factory_w_keyword_arguments(self):
    43        a_person = dict(
    44            first_name=get_random_name(),
    45            last_name=get_random_name(),
    46            sex=pick_one('F', 'M'),
    47        )
    48
    49        this_year = datetime.datetime.now().year
    50        year_of_birth = random.randint(
    51            this_year-120, this_year
    52        )
    53
    54        reality = src.person.factory(
    55            **a_person,
    56            year_of_birth=year_of_birth,
    57        )
    58        my_expectation = dict(
    59            **a_person,
    60            age=this_year-year_of_birth,
    61        )
    62        self.assertEqual(reality, my_expectation)
    63
    64
    65# Exceptions seen
    
  • I change the name of the new test to test_factory_w_optional_arguments

    32        reality = src.person.factory(
    33            **a_person,
    34            year_of_birth=year_of_birth,
    35        )
    36        my_expectation = dict(
    37            **a_person,
    38            age=this_year-year_of_birth,
    39        )
    40        self.assertEqual(reality, my_expectation)
    41
    42    def test_factory_w_optional_arguments(self):
    43        a_person = dict(
    44            first_name=get_random_name(),
    45            last_name=get_random_name(),
    46            sex=pick_one('F', 'M'),
    47        )
    
  • I comment out the last_name key-value pair in the a_person dictionary

    42    def test_factory_w_optional_arguments(self):
    43        this_year = datetime.datetime.now().year
    44        year_of_birth = random.randint(
    45            this_year-120, this_year
    46        )
    47
    48        a_person = dict(
    49            first_name=get_random_name(),
    50            # last_name=get_random_name(),
    51            sex=pick_one('F', 'M'),
    52        )
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() missing 1 required
               positional argument: 'last_name'
    

    because this test no longer gives a value for last_name when it calls the factory function, I have to make it a choice.


GREEN: make it pass



REFACTOR: make it better


  • I comment out the sex key in test_factory_w_optional_arguments to see what happens when I call the factory function without it, in test_person.py

    42    def test_factory_w_optional_arguments(self):
    43        this_year = datetime.datetime.now().year
    44        year_of_birth = random.randint(
    45            this_year-120, this_year
    46        )
    47
    48        a_person = dict(
    49            first_name=get_random_name(),
    50            # last_name=get_random_name(),
    51            # sex=pick_one('F', 'M'),
    52        )
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': Z, 'last_name': Y,
         'sex': None, 'age': X}
     != {'first_name': Z, 'last_name': Y, 'age': X}
    

    the factory function returns a dictionary with a 'sex' key, and the assertion expects a dictionary without that key

  • I add a key-value pair for sex to my_expectation in test_factory_w_optional_arguments

    54        reality = src.person.factory(
    55            **a_person,
    56            year_of_birth=year_of_birth,
    57        )
    58        my_expectation = dict(
    59            **a_person,
    60            last_name='doe',
    61            sex='M',
    62            age=this_year-year_of_birth,
    63        )
    64        self.assertEqual(reality, my_expectation)
    65
    66
    67# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': Y, 'last_name': 'doe',
         'sex': None, 'age': X}
     != {'first_name': Y, 'last_name': 'doe',
         'sex': 'M', 'age': X}
    

    because the factory function returns a dictionary with a value of None for sex and the assertion expects 'M'

  • I change the default value for sex in the factory function in person.py

     4def factory(
     5        first_name, last_name='doe',
     6        sex='M', year_of_birth=None,
     7    ):
     8    return {
     9        'first_name': first_name,
    10        'last_name': last_name,
    11        'sex': sex,
    12        'age': datetime.datetime.now().year-year_of_birth,
    13    }
    

    the test passes because the default value for the sex parameter of the function is 'M'. This means that

    src.person.factory(
        first_name=first_name,
        year_of_birth=year_of_birth,
    )
    

    is the same as

    src.person.factory(
        first_name=first_name,
        year_of_birth=year_of_birth,
        last_name='doe',
        sex='M',
    )
    

    because a function uses the default value for a parameter when it is called without the parameter.


  • I no longer need the a_person dictionary in test_factory_w_optional_arguments because it only has one key, I can use a variable for first_name instead

    42    def test_factory_w_optional_arguments(self):
    43        first_name = get_random_name()
    44        this_year = datetime.datetime.now().year
    45        year_of_birth = random.randint(
    46            this_year-120, this_year
    47        )
    
  • I use the variable for reality and my_expectation

    55        reality = src.person.factory(
    56            **a_person,
    57            first_name=first_name,
    58            year_of_birth=year_of_birth,
    59        )
    60        my_expectation = dict(
    61            **a_person,
    62            first_name=first_name,
    63            last_name='doe',
    64            sex='M',
    65            age=this_year-year_of_birth,
    66        )
    67        self.assertEqual(reality, my_expectation)
    68
    69
    70# Exceptions seen
    

    the terminal is my friend, and shows TypeError

    TypeError: src.person.factory() got multiple values
               for keyword argument 'first_name'
    

    because the a_person dictionary has a key called first_name, the call to src.person.factory gets called with the same keyword argument twice.

  • I comment out **a_person,

    Tip

    I can comment out a line in Visual Studio Code with ctrl/command+/ anywhere on the line

    55        reality = src.person.factory(
    56            # **a_person,
    57            first_name=first_name,
    58            year_of_birth=year_of_birth,
    59        )
    60        my_expectation = dict(
    61            # **a_person,
    62            first_name=first_name,
    63            last_name='doe',
    64            sex='M',
    65            age=this_year-year_of_birth,
    66        )
    67        self.assertEqual(reality, my_expectation)
    68
    69
    70# Exceptions seen
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures

  • I remove the commented lines and the a_person dictionary

    42    def test_factory_w_optional_arguments(self):
    43        first_name = get_random_name()
    44        this_year = datetime.datetime.now().year
    45        year_of_birth = random.randint(
    46            this_year-120, this_year
    47        )
    48
    49        reality = src.person.factory(
    50            first_name=first_name,
    51            year_of_birth=year_of_birth,
    52        )
    53        my_expectation = dict(
    54            first_name=first_name,
    55            last_name='doe',
    56            sex='M',
    57            age=this_year-year_of_birth,
    58        )
    59        self.assertEqual(reality, my_expectation)
    60
    61
    62# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am \
    'add test_factory_w_optional_arguments'
    

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


test_factory_person_says_hello

I have a function that takes in first_name, last_name, sex and year_of_birth for a person and returns a dictionary with first_name, last_name, sex and age (based on the year_of_birth) as keys.

What if I want the person to say hello, How would I do that? I can write a function that takes in a person (dictionary) and returns a message (string).


RED: make it fail


  • I add a new test to test_person.py

    49        reality = src.person.factory(
    50            first_name=first_name,
    51            year_of_birth=year_of_birth,
    52        )
    53        my_expectation = dict(
    54            first_name=first_name,
    55            last_name='doe',
    56            sex='M',
    57            age=this_year-year_of_birth,
    58        )
    59        self.assertEqual(reality, my_expectation)
    60
    61    def test_factory_person_says_hello(self):
    62        joe = src.person.factory(
    63            first_name='joe',
    64            last_name='blow',
    65            year_of_birth=1996,
    66        )
    67
    68        reality = src.person.say_hello(joe)
    69        my_expectation = None
    70        self.assertEqual(reality, my_expectation)
    71
    72
    73# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.person'
                    has no attribute 'hello'
    

    because person.py does not have a function named say_hello, yet.


GREEN: make it pass


  • I add the function to person.py

     4def factory(
     5        first_name, last_name='doe',
     6        sex='M', year_of_birth=None,
     7    ):
     8    return {
     9        'first_name': first_name,
    10        'last_name': last_name,
    11        'sex': sex,
    12        'age': (
    13            datetime.datetime.today().year
    14          - year_of_birth
    15        ),
    16    }
    17
    18
    19def say_hello():
    20    return None
    

    the terminal is my friend, and shows TypeError

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

    because the definition for say_hello does not allow inputs and the test called the function with a positional argument (person)

  • I add a name to the definition

    19def say_hello(person):
    20    return None
    

    the test passes.


REFACTOR: make it better


I want the say_hello function to return a string for the person (dictionary) I give as input

  • I change my_expectation to an f-string in test_factory_person_says_hello in test_person.py

    62    def test_factory_person_says_hello(self):
    63        joe = src.person.factory(
    64            first_name='joe',
    65            last_name='blow',
    66            year_of_birth=1996,
    67        )
    68
    69        reality = src.person.say_hello(joe)
    70        # my_expectation = None
    71        my_expectation = (
    72            'Hi, my name is joe blow and I am'
    73            f' {datetime.datetime.now().year-1996}'
    74        )
    75        self.assertEqual(reality, my_expectation)
    76
    77
    78# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        None != 'Hi, my name is joe blow and I am 30'
    
  • I copy (ctrl/command+c) the value from the terminal and paste it (ctrl/command+v) in the return statement in person.py

    19def say_hello(person):
    20    # return None
    21    return 'Hi, my name is joe blow and I am 30'
    

    the test passes.

  • I add an assertion for another person to test_factory_person_says_hello in test_person.py

    68        reality = src.person.say_hello(joe)
    69        # my_expectation = None
    70        my_expectation = (
    71            'Hi, my name is joe blow and I am'
    72            f' {datetime.datetime.now().year-1996}'
    73        )
    74        self.assertEqual(reality, my_expectation)
    75
    76        jane = src.person.factory(
    77            first_name='jane',
    78            sex='F',
    79            year_of_birth=1991,
    80        )
    81
    82        reality = src.person.say_hello(jane)
    83        my_expectation = (
    84            'Hi, my name is jane doe and I am'
    85            f' {datetime.datetime.now().year-1991}'
    86        )
    87        self.assertEqual(reality, my_expectation)
    88
    89
    90# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
          'Hi, my name is joe blow and I am 30'
       != 'Hi, my name is jane doe and I am 35'
    

    I have to make sure the say_hello function uses the values of the person dictionary to make the message. I can do that with the get method of dictionaries.

  • I change the string to an f-string with the value for the first_name in person.py

    19def say_hello(person):
    20    first_name = person.get('first_name')
    21
    22    # return None
    23    # return 'Hi, my name is joe blow and I am 30'
    24    return (
    25        f'Hi, my name is {first_name}'
    26        ' blow and I am 30'
    27    )
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        'Hi, my name is jane blow and I am 30'
     != 'Hi, my name is jane doe and I am 35'
    
    • the first names are the same because person.get('first_name') uses the get method of the dictionary to get the value of the 'first_name' key

    • the last names and ages are different

  • I use the get method of the dictionary to get the value for the last_name key, then add it to the return statement

    19def say_hello(person):
    20    first_name = person.get('first_name')
    21    last_name = person.get('last_name')
    22
    23    # return None
    24    # return 'Hi, my name is joe blow and I am 30'
    25    return (
    26        # f'Hi, my name is {first_name}'
    27        # ' blow and I am 30'
    28        f'Hi, my name is {first_name} {last_name}'
    29        ' and I am 30'
    30    )
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        'Hi, my name is jane doe and I am 30'
     != 'Hi, my name is jane doe and I am 35'
    

    the age is the only thing that is different

  • I use the get method of the dictionary to get the value for the age key , then add it to the return statement

    19def say_hello(person):
    20    first_name = person.get('first_name')
    21    last_name = person.get('last_name')
    22    age = person.get('age')
    23
    24    # return None
    25    # return 'Hi, my name is joe blow and I am 30'
    26    return (
    27        # f'Hi, my name is {first_name}'
    28        # ' blow and I am 30'
    29        f'Hi, my name is {first_name} {last_name}'
    30        # ' and I am 30'
    31        f' and I am {age}'
    32    )
    

    the test passes.

  • I remove the commented lines

    19def say_hello(person):
    20    first_name = person.get('first_name')
    21    last_name = person.get('last_name')
    22    age = person.get('age')
    23
    24    return (
    25        f'Hi, my name is {first_name} {last_name}'
    26        f' and I am {age}'
    27    )
    
  • I add an assertion for a new person to test_factory_person_says_hello in test_person.py

     76        jane = src.person.factory(
     77            first_name='jane',
     78            sex='F',
     79            year_of_birth=1991,
     80        )
     81
     82        reality = src.person.say_hello(jane)
     83        my_expectation = (
     84            'Hi, my name is jane doe and I am'
     85            f' {datetime.datetime.now().year-1991}'
     86        )
     87        self.assertEqual(reality, my_expectation)
     88
     89        john = src.person.factory(
     90            first_name='john',
     91            last_name='smith',
     92            year_of_birth=1580,
     93        )
     94
     95        reality = src.person.say_hello(john)
     96        my_expectation = (
     97            'Hi, my name is jane doe and I am'
     98            f' {datetime.datetime.now().year-1991}'
     99        )
    100        self.assertEqual(reality, my_expectation)
    101
    102
    103# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        'Hi, my name is john smith and I am 446'
     != 'Hi, my name is jane doe and I am 35'
    
  • I change my_expectation to match reality for john

     89        john = src.person.factory(
     90            first_name='john',
     91            last_name='smith',
     92            year_of_birth=1580,
     93        )
     94
     95        reality = src.person.say_hello(john)
     96        my_expectation = (
     97            # 'Hi, my name is jane doe and I am'
     98            # f' {datetime.datetime.now().year-1991}'
     99            'Hi, my name is john smith and I am'
    100            f' {datetime.datetime.now().year-1580}'
    101        )
    102        self.assertEqual(reality, my_expectation)
    103
    104
    105# Exceptions seen
    

    the test passes.

  • I add an assertion for one more person

     95        reality = src.person.say_hello(john)
     96        my_expectation = (
     97            # 'Hi, my name is jane doe and I am'
     98            # f' {datetime.datetime.now().year-1991}'
     99            'Hi, my name is john smith and I am'
    100            f' {datetime.datetime.now().year-1580}'
    101        )
    102        self.assertEqual(reality, my_expectation)
    103
    104        a_person = src.person.factory(
    105            first_name='person',
    106            last_name='public',
    107            year_of_birth=2000,
    108            sex='F',
    109        )
    110
    111        reality = src.person.say_hello(a_person)
    112        my_expectation = (
    113            'Hi, my name is john smith and I am'
    114            f' {datetime.datetime.now().year-1580}'
    115        )
    116        self.assertEqual(reality, my_expectation)
    117
    118
    119# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        'Hi, my name is person public and I am 26'
     != 'Hi, my name is john smith and I am 446'
    
  • I change my_expectation to match reality for a_person

    104        a_person = src.person.factory(
    105            first_name='person',
    106            last_name='public',
    107            year_of_birth=2000,
    108            sex='F',
    109        )
    110
    111        reality = src.person.say_hello(a_person)
    112        my_expectation = (
    113            # 'Hi, my name is john smith and I am'
    114            # f' {datetime.datetime.now().year-1580}'
    115            'Hi, my name is person public and I am'
    116            f' {datetime.datetime.now().year-2000}'
    117        )
    118        self.assertEqual(reality, my_expectation)
    119
    120
    121# Exceptions seen
    

    the test passes.


  • I add variables to use them to remove repetition of 'person', 'public', 2000 and the age calculation from the last assertion

     95        reality = src.person.say_hello(john)
     96        my_expectation = (
     97            # 'Hi, my name is jane doe and I am'
     98            # f' {datetime.datetime.now().year-1991}'
     99            'Hi, my name is john smith and I am'
    100            f' {datetime.datetime.now().year-1580}'
    101        )
    102        self.assertEqual(reality, my_expectation)
    103
    104        first_name = 'person'
    105        last_name = 'public'
    106        year_of_birth = 2000
    107        age = (
    108            datetime.datetime.now().year
    109          - year_of_birth
    110        )
    111
    112        a_person = src.person.factory(
    113            first_name='person',
    114            last_name='public',
    115            year_of_birth=2000,
    116            sex='F',
    117        )
    
  • I use the variables to remove repetition of 'person', 'public', 2000 and the age calculation

    104        first_name = 'person'
    105        last_name = 'public'
    106        year_of_birth = 2000
    107        age = (
    108            datetime.datetime.now().year
    109          - year_of_birth
    110        )
    111
    112        a_person = src.person.factory(
    113            # first_name='person',
    114            # last_name='public',
    115            # year_of_birth=2000,
    116            first_name=first_name,
    117            last_name=last_name,
    118            year_of_birth=year_of_birth,
    119            sex='F',
    120        )
    121
    122        reality = src.person.say_hello(a_person)
    123        my_expectation = (
    124            # 'Hi, my name is john smith and I am'
    125            # f' {datetime.datetime.now().year-1580}'
    126            # 'Hi, my name is person public and I am'
    127            # f' {datetime.datetime.now().year-2000}'
    128            f'Hi, my name is {first_name} {last_name}'
    129            f' and I am {age}'
    130        )
    131        self.assertEqual(reality, my_expectation)
    132
    133
    134# Exceptions seen
    

    the test is still green.

  • I add variables to use them to remove repetition of 'john', 'smith', 1580 and the age calculation from the assertion before the one for a_person

     82        reality = src.person.say_hello(jane)
     83        my_expectation = (
     84            'Hi, my name is jane doe and I am'
     85            f' {datetime.datetime.now().year-1991}'
     86        )
     87        self.assertEqual(reality, my_expectation)
     88
     89        first_name = 'john'
     90        last_name = 'smith'
     91        year_of_birth = 1580
     92        age = (
     93            datetime.datetime.now().year
     94          - year_of_birth
     95        )
     96
     97        john = src.person.factory(
     98            first_name='john',
     99            last_name='smith',
    100            year_of_birth=1580,
    101        )
    
  • I use the variables to remove repetition of 'john', 'smith', 1580 and the age calculation

    68        reality = src.person.say_hello(joe)
    69        # my_expectation = None
    70        my_expectation = (
    71            'Hi, my name is joe blow and I am'
    72            f' {datetime.datetime.now().year-1996}'
    73        )
    74        self.assertEqual(reality, my_expectation)
    75
    76        first_name = 'jane'
    77        last_name = 'doe'
    78        year_of_birth = 1991
    79        age = (
    80            datetime.datetime.now().year
    81          - year_of_birth
    82        )
    83
    84        jane = src.person.factory(
    85            first_name='jane',
    86            sex='F',
    87            year_of_birth=1991,
    88        )
    

    still green.

  • I add the same variable names to use them to remove repetition of 'jane', 1991 and the age calculation from the second assertion

    68        reality = src.person.say_hello(joe)
    69        # my_expectation = None
    70        my_expectation = (
    71            'Hi, my name is joe blow and I am'
    72            f' {datetime.datetime.now().year-1996}'
    73        )
    74        self.assertEqual(reality, my_expectation)
    75
    76        first_name = 'jane'
    77        last_name = 'doe'
    78        year_of_birth = 1991
    79        age = (
    80            datetime.datetime.now().year
    81          - year_of_birth
    82        )
    83
    84        jane = src.person.factory(
    85            first_name='jane',
    86            sex='F',
    87            year_of_birth=1991,
    88        )
    
  • I use the variables to remove repetition of 'jane', 1991 and the age calculation

     76        first_name = 'jane'
     77        last_name = 'doe'
     78        year_of_birth = 1991
     79        age = (
     80            datetime.datetime.now().year
     81          - year_of_birth
     82        )
     83
     84        jane = src.person.factory(
     85            # first_name='jane',
     86            sex='F',
     87            # year_of_birth=1991,
     88            first_name=first_name,
     89            year_of_birth=year_of_birth,
     90        )
     91
     92        reality = src.person.say_hello(jane)
     93        my_expectation = (
     94            # 'Hi, my name is jane doe and I am'
     95            # f' {datetime.datetime.now().year-1991}'
     96            f'Hi, my name is {first_name} {last_name}'
     97            f' and I am {age}'
     98        )
     99        self.assertEqual(reality, my_expectation)
    100
    101        first_name = 'john'
    102        last_name = 'smith'
    103        year_of_birth = 1580
    104        age = (
    105            datetime.datetime.now().year
    106          - year_of_birth
    107        )
    

    green.

  • I add the variable names to use them to remove repetition of 'joe', 'blow', 1996 and the age calculation from the first assertion

    61    def test_factory_person_says_hello(self):
    62        first_name = 'joe'
    63        last_name = 'blow'
    64        year_of_birth = 1996
    65        age = (
    66            datetime.datetime.now().year
    67          - year_of_birth
    68        )
    69
    70        joe = src.person.factory(
    71            first_name='joe',
    72            last_name='blow',
    73            year_of_birth=1996,
    74        )
    
  • I use the variables to remove repetition of 'joe', 'blow', 1996 and the age calculation

    61    def test_factory_person_says_hello(self):
    62        first_name = 'joe'
    63        last_name = 'blow'
    64        year_of_birth = 1996
    65        age = (
    66            datetime.datetime.now().year
    67          - year_of_birth
    68        )
    69
    70        joe = src.person.factory(
    71            # first_name='joe',
    72            # last_name='blow',
    73            # year_of_birth=1996,
    74            first_name=first_name,
    75            last_name=last_name,
    76            year_of_birth=year_of_birth,
    77        )
    78
    79        reality = src.person.say_hello(joe)
    80        # my_expectation = None
    81        my_expectation = (
    82            # 'Hi, my name is joe blow and I am'
    83            # f' {datetime.datetime.now().year-1996}'
    84            f'Hi, my name is {first_name} {last_name}'
    85            f' and I am {age}'
    86        )
    87        self.assertEqual(reality, my_expectation)
    88
    89        first_name = 'jane'
    90        last_name = 'doe'
    91        year_of_birth = 1991
    92        age = (
    93            datetime.datetime.now().year
    94          - year_of_birth
    95        )
    

    the test is still green.

  • I remove the commented lines

     61    def test_factory_person_says_hello(self):
     62        first_name = 'joe'
     63        last_name = 'blow'
     64        year_of_birth = 1996
     65        age = (
     66            datetime.datetime.now().year
     67          - year_of_birth
     68        )
     69
     70        joe = src.person.factory(
     71            first_name=first_name,
     72            last_name=last_name,
     73            year_of_birth=year_of_birth,
     74        )
     75
     76        reality = src.person.say_hello(joe)
     77        my_expectation = (
     78            f'Hi, my name is {first_name} {last_name}'
     79            f' and I am {age}'
     80        )
     81        self.assertEqual(reality, my_expectation)
     82
     83        first_name = 'jane'
     84        last_name = 'doe'
     85        year_of_birth = 1991
     86        age = (
     87            datetime.datetime.now().year
     88          - year_of_birth
     89        )
     90
     91        jane = src.person.factory(
     92            sex='F',
     93            first_name=first_name,
     94            year_of_birth=year_of_birth,
     95        )
     96
     97        reality = src.person.say_hello(jane)
     98        my_expectation = (
     99            f'Hi, my name is {first_name} {last_name}'
    100            f' and I am {age}'
    101        )
    102        self.assertEqual(reality, my_expectation)
    103
    104        first_name = 'john'
    105        last_name = 'smith'
    106        year_of_birth = 1580
    107        age = (
    108            datetime.datetime.now().year
    109          - year_of_birth
    110        )
    111
    112        john = src.person.factory(
    113            first_name=first_name,
    114            last_name=last_name,
    115            year_of_birth=year_of_birth,
    116        )
    117
    118        reality = src.person.say_hello(john)
    119        my_expectation = (
    120            f'Hi, my name is {first_name} {last_name}'
    121            f' and I am {age}'
    122        )
    123        self.assertEqual(reality, my_expectation)
    124
    125        first_name = 'person'
    126        last_name = 'public'
    127        year_of_birth = 2000
    128        age = (
    129            datetime.datetime.now().year
    130          - year_of_birth
    131        )
    132
    133        a_person = src.person.factory(
    134            first_name=first_name,
    135            last_name=last_name,
    136            year_of_birth=year_of_birth,
    137            sex='F',
    138        )
    139
    140        reality = src.person.say_hello(a_person)
    141        my_expectation = (
    142            f'Hi, my name is {first_name} {last_name}'
    143            f' and I am {age}'
    144        )
    145        self.assertEqual(reality, my_expectation)
    146
    147
    148# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am \
    'add test_factory_person_says_hello'
    

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


extract calculate_age function

Each assertion in every test has a calculation for the age

  • I go back to the terminal that is running the tests

  • I add a function

    11def get_random_name():
    12    return pick_one(
    13        'jane', 'joe', 'john', 'person',
    14        'doe', 'smith', 'blow', 'public',
    15    )
    16
    17
    18def calculate_age(year_of_birth):
    19    return (
    20        datetime.datetime.now().year
    21      - year_of_birth
    22    )
    23
    24
    25class TestPerson(unittest.TestCase):
    26
    27    def test_factory_w_keyword_arguments(self):
    
  • I use the function to remove repetition of the age calculation from my_expectation in test_factory_w_keyword_arguments

    39        reality = src.person.factory(
    40            **a_person,
    41            year_of_birth=year_of_birth,
    42        )
    43        my_expectation = dict(
    44            **a_person,
    45            # age=this_year-year_of_birth,
    46            age=calculate_age(year_of_birth),
    47        )
    48        self.assertEqual(reality, my_expectation)
    49
    50    def test_factory_w_optional_arguments(self):
    

    the test is still green.

  • I remove the commented line

    27    def test_factory_w_keyword_arguments(self):
    28        this_year = datetime.datetime.now().year
    29        year_of_birth = random.randint(
    30            this_year-120, this_year
    31        )
    32
    33        a_person = dict(
    34            first_name=get_random_name(),
    35            last_name=get_random_name(),
    36            sex=pick_one('F', 'M'),
    37        )
    38
    39        reality = src.person.factory(
    40            **a_person,
    41            year_of_birth=year_of_birth,
    42        )
    43        my_expectation = dict(
    44            **a_person,
    45            age=calculate_age(year_of_birth),
    46        )
    47        self.assertEqual(reality, my_expectation)
    48
    49    def test_factory_w_optional_arguments(self):
    
  • I use the function to remove repetition of the age calculation from my_expectation in test_factory_w_optional_arguments

    56        reality = src.person.factory(
    57            first_name=first_name,
    58            year_of_birth=year_of_birth,
    59        )
    60        my_expectation = dict(
    61            first_name=first_name,
    62            last_name='doe',
    63            sex='M',
    64            # age=this_year-year_of_birth,
    65            age=calculate_age(year_of_birth),
    66        )
    67        self.assertEqual(reality, my_expectation)
    68
    69    def test_factory_person_says_hello(self):
    

    the test is still green.

  • I remove the commented line

    49    def test_factory_w_optional_arguments(self):
    50        first_name = get_random_name()
    51        this_year = datetime.datetime.now().year
    52        year_of_birth = random.randint(
    53            this_year-120, this_year
    54        )
    55
    56        reality = src.person.factory(
    57            first_name=first_name,
    58            year_of_birth=year_of_birth,
    59        )
    60        my_expectation = dict(
    61            first_name=first_name,
    62            last_name='doe',
    63            sex='M',
    64            age=calculate_age(year_of_birth),
    65        )
    66        self.assertEqual(reality, my_expectation)
    67
    68    def test_factory_person_says_hello(self):
    
  • I use the function to remove repetition of the age calculation from my_expectation for joe in test_factory_person_says_hello

    68    def test_factory_person_says_hello(self):
    69        first_name = 'joe'
    70        last_name = 'blow'
    71        year_of_birth = 1996
    72        # age = (
    73        #     datetime.datetime.now().year
    74        #   - year_of_birth
    75        # )
    76
    77        joe = src.person.factory(
    78            first_name=first_name,
    79            last_name=last_name,
    80            year_of_birth=year_of_birth,
    81        )
    82
    83        reality = src.person.say_hello(joe)
    84        my_expectation = (
    85            f'Hi, my name is {first_name} {last_name}'
    86            # f' and I am {age}'
    87            f' and I am {calculate_age(year_of_birth)}'
    88        )
    89        self.assertEqual(reality, my_expectation)
    

    the test is still green.

  • I use the function to remove repetition of the age calculation from my_expectation for jane in test_factory_person_says_hello

     83        reality = src.person.say_hello(joe)
     84        my_expectation = (
     85            f'Hi, my name is {first_name} {last_name}'
     86            # f' and I am {age}'
     87            f' and I am {calculate_age(year_of_birth)}'
     88        )
     89        self.assertEqual(reality, my_expectation)
     90
     91        first_name = 'jane'
     92        last_name = 'doe'
     93        year_of_birth = 1991
     94        # age = (
     95        #     datetime.datetime.now().year
     96        #   - year_of_birth
     97        # )
     98
     99        jane = src.person.factory(
    100            sex='F',
    101            first_name=first_name,
    102            year_of_birth=year_of_birth,
    103        )
    104
    105        reality = src.person.say_hello(jane)
    106        my_expectation = (
    107            f'Hi, my name is {first_name} {last_name}'
    108            # f' and I am {age}'
    109            f' and I am {calculate_age(year_of_birth)}'
    110        )
    111        self.assertEqual(reality, my_expectation)
    

    still green.

  • I use the function to remove repetition of the age calculation from my_expectation for john in test_factory_person_says_hello

    105        reality = src.person.say_hello(jane)
    106        my_expectation = (
    107            f'Hi, my name is {first_name} {last_name}'
    108            # f' and I am {age}'
    109            f' and I am {calculate_age(year_of_birth)}'
    110        )
    111        self.assertEqual(reality, my_expectation)
    112
    113        first_name = 'john'
    114        last_name = 'smith'
    115        year_of_birth = 1580
    116        # age = (
    117        #     datetime.datetime.now().year
    118        #   - year_of_birth
    119        # )
    120
    121        john = src.person.factory(
    122            first_name=first_name,
    123            last_name=last_name,
    124            year_of_birth=year_of_birth,
    125        )
    126
    127        reality = src.person.say_hello(john)
    128        my_expectation = (
    129            f'Hi, my name is {first_name} {last_name}'
    130            # f' and I am {age}'
    131            f' and I am {calculate_age(year_of_birth)}'
    132        )
    133        self.assertEqual(reality, my_expectation)
    

    green.

  • I use the function to remove repetition of the age calculation from my_expectation for a_person in test_factory_person_says_hello

    127        reality = src.person.say_hello(john)
    128        my_expectation = (
    129            f'Hi, my name is {first_name} {last_name}'
    130            # f' and I am {age}'
    131            f' and I am {calculate_age(year_of_birth)}'
    132        )
    133        self.assertEqual(reality, my_expectation)
    134
    135        first_name = 'person'
    136        last_name = 'public'
    137        year_of_birth = 2000
    138        # age = (
    139        #     datetime.datetime.now().year
    140        #   - year_of_birth
    141        # )
    142
    143        a_person = src.person.factory(
    144            first_name=first_name,
    145            last_name=last_name,
    146            year_of_birth=year_of_birth,
    147            sex='F',
    148        )
    149
    150        reality = src.person.say_hello(a_person)
    151        my_expectation = (
    152            f'Hi, my name is {first_name} {last_name}'
    153            # f' and I am {age}'
    154            f' and I am {calculate_age(year_of_birth)}'
    155        )
    156        self.assertEqual(reality, my_expectation)
    157
    158
    159# Exceptions seen
    

    the test is still green.

  • I remove the commented lines

     68    def test_factory_person_says_hello(self):
     69        first_name = 'joe'
     70        last_name = 'blow'
     71        year_of_birth = 1996
     72
     73        joe = src.person.factory(
     74            first_name=first_name,
     75            last_name=last_name,
     76            year_of_birth=year_of_birth,
     77        )
     78
     79        reality = src.person.say_hello(joe)
     80        my_expectation = (
     81            f'Hi, my name is {first_name} {last_name}'
     82            f' and I am {calculate_age(year_of_birth)}'
     83        )
     84        self.assertEqual(reality, my_expectation)
     85
     86        first_name = 'jane'
     87        last_name = 'doe'
     88        year_of_birth = 1991
     89
     90        jane = src.person.factory(
     91            sex='F',
     92            first_name=first_name,
     93            year_of_birth=year_of_birth,
     94        )
     95
     96        reality = src.person.say_hello(jane)
     97        my_expectation = (
     98            f'Hi, my name is {first_name} {last_name}'
     99            f' and I am {calculate_age(year_of_birth)}'
    100        )
    101        self.assertEqual(reality, my_expectation)
    102
    103        first_name = 'john'
    104        last_name = 'smith'
    105        year_of_birth = 1580
    106
    107        john = src.person.factory(
    108            first_name=first_name,
    109            last_name=last_name,
    110            year_of_birth=year_of_birth,
    111        )
    112
    113        reality = src.person.say_hello(john)
    114        my_expectation = (
    115            f'Hi, my name is {first_name} {last_name}'
    116            f' and I am {calculate_age(year_of_birth)}'
    117        )
    118        self.assertEqual(reality, my_expectation)
    119
    120        first_name = 'person'
    121        last_name = 'public'
    122        year_of_birth = 2000
    123
    124        a_person = src.person.factory(
    125            first_name=first_name,
    126            last_name=last_name,
    127            year_of_birth=year_of_birth,
    128            sex='F',
    129        )
    130
    131        reality = src.person.say_hello(a_person)
    132        my_expectation = (
    133            f'Hi, my name is {first_name} {last_name}'
    134            f' and I am {calculate_age(year_of_birth)}'
    135        )
    136        self.assertEqual(reality, my_expectation)
    137
    138
    139# Exceptions seen
    

    the test is still green.

  • I add a git commit message in the other terminal

    git commit -am 'extract calculate_age function'
    

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


test_factory_person_says_hello with random values

I want to use random values to test_factory_person_says_hello

  • I go back to the terminal that is running the tests

  • I add random values to use for the first_name, last_name, sex, year_of_birth and age variables

    68    def test_factory_person_says_hello(self):
    69        first_name = get_random_name()
    70        last_name = get_random_name()
    71        sex = pick_one('F', 'M')
    72
    73        this_year = datetime.datetime.now().year
    74        year_of_birth = random.randint(
    75            this_year-120, this_year
    76        )
    77        age = calculate_age(year_of_birth)
    78
    79        first_name = 'joe'
    80        last_name = 'blow'
    81        year_of_birth = 1996
    
  • I comment out the first_name, last_name and year_of_birth variables for joe in the first assertion, so it uses the ones with random values

    68    def test_factory_person_says_hello(self):
    69        first_name = get_random_name()
    70        last_name = get_random_name()
    71        sex = pick_one('F', 'M')
    72
    73        this_year = datetime.datetime.now().year
    74        year_of_birth = random.randint(
    75            this_year-120, this_year
    76        )
    77        age = calculate_age(year_of_birth)
    78
    79        # first_name = 'joe'
    80        # last_name = 'blow'
    81        # year_of_birth = 1996
    82
    83        joe = src.person.factory(
    84            first_name=first_name,
    85            last_name=last_name,
    86            year_of_birth=year_of_birth,
    87        )
    

    the test is still green.

  • I add a random value for the sex parameter with the sex variable in the call to src.person.factory for joe

    83        joe = src.person.factory(
    84            first_name=first_name,
    85            last_name=last_name,
    86            year_of_birth=year_of_birth,
    87            sex=sex,
    88        )
    89
    90        reality = src.person.say_hello(joe)
    91        my_expectation = (
    92            f'Hi, my name is {first_name} {last_name}'
    93            f' and I am {calculate_age(year_of_birth)}'
    94        )
    95        self.assertEqual(reality, my_expectation)
    

    still green.

  • I use the age variable in my_expectation for joe

    90        reality = src.person.say_hello(joe)
    91        my_expectation = (
    92            f'Hi, my name is {first_name} {last_name}'
    93            # f' and I am {calculate_age(year_of_birth)}'
    94            f' and I am {age}'
    95        )
    96        self.assertEqual(reality, my_expectation)
    

    green.

  • I comment out the first_name, last_name and year_of_birth variables for jane in the second assertion, so it uses the ones with random values

     89        reality = src.person.say_hello(joe)
     90        my_expectation = (
     91            f'Hi, my name is {first_name} {last_name}'
     92            # f' and I am {calculate_age(year_of_birth)}'
     93            f' and I am {age}'
     94        )
     95        self.assertEqual(reality, my_expectation)
     96
     97        # first_name = 'jane'
     98        # last_name = 'doe'
     99        # year_of_birth = 1991
    100
    101        jane = src.person.factory(
    102            sex='F',
    103            first_name=first_name,
    104            year_of_birth=year_of_birth,
    105        )
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes if the last_name is randomly 'doe'.

    If the last_name is not 'doe', the terminal is my friend, and shows AssertionError

    AssertionError:
        'Hi, my name is X doe and I am Z'
     != 'Hi, my name is X Y and I am Z'
    

    because I did not need to give a value for the last_name parameter in the call to src.person.factory since the default value for the last_name parameter of the function is 'doe'. This means that

    src.person.factory(
        sex='F',
        first_name=first_name,
        year_of_birth=year_of_birth,
    )
    

    is the same as

    src.person.factory(
        sex='F',
        first_name=first_name,
        year_of_birth=year_of_birth,
        last_name='doe'
    )
    

    because a function uses the default value for a parameter when it is called without the parameter.

  • I add last_name to the call to src.person.factory for jane to use the variable

    102        jane = src.person.factory(
    103            sex='F',
    104            first_name=first_name,
    105            last_name=last_name,
    106            year_of_birth=year_of_birth,
    107        )
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes with no more random failures.

  • I change the value for the sex parameter to the sex variable so it uses a random value in the call to src.person.factory for jane

    102        jane = src.person.factory(
    103            # sex='F',
    104            first_name=first_name,
    105            last_name=last_name,
    106            year_of_birth=year_of_birth,
    107            sex=sex,
    108        )
    

    the test is still green.

  • I use the age variable in my_expectation for jane

    110        reality = src.person.say_hello(jane)
    111        my_expectation = (
    112            f'Hi, my name is {first_name} {last_name}'
    113            # f' and I am {calculate_age(year_of_birth)}'
    114            f' and I am {age}'
    115        )
    116        self.assertEqual(reality, my_expectation)
    

    still green.

  • I comment out the first_name, last_name and year_of_birth variables for john in the third assertion, so it uses the ones with random values

    110        reality = src.person.say_hello(jane)
    111        my_expectation = (
    112            f'Hi, my name is {first_name} {last_name}'
    113            # f' and I am {calculate_age(year_of_birth)}'
    114            f' and I am {age}'
    115        )
    116        self.assertEqual(reality, my_expectation)
    117
    118        # first_name = 'john'
    119        # last_name = 'smith'
    120        # year_of_birth = 1580
    121
    122        john = src.person.factory(
    123            first_name=first_name,
    124            last_name=last_name,
    125            year_of_birth=year_of_birth,
    126        )
    

    green.

  • I add a random value for the sex parameter with the sex variable in the call to src.person.factory for john

    122        john = src.person.factory(
    123            first_name=first_name,
    124            last_name=last_name,
    125            year_of_birth=year_of_birth,
    126            sex=sex,
    127        )
    

    still green.

  • I use the age variable in my_expectation for john

    129        reality = src.person.say_hello(john)
    130        my_expectation = (
    131            f'Hi, my name is {first_name} {last_name}'
    132            # f' and I am {calculate_age(year_of_birth)}'
    133            f' and I am {age}'
    134        )
    135        self.assertEqual(reality, my_expectation)
    

    the test is still green.

  • I comment out the first_name, last_name and year_of_birth variables for a_person in the last assertion, so it uses the ones with random values

    129        reality = src.person.say_hello(john)
    130        my_expectation = (
    131            f'Hi, my name is {first_name} {last_name}'
    132            # f' and I am {calculate_age(year_of_birth)}'
    133            f' and I am {age}'
    134        )
    135        self.assertEqual(reality, my_expectation)
    136
    137        # first_name = 'person'
    138        # last_name = 'public'
    139        # year_of_birth = 2000
    140
    141        a_person = src.person.factory(
    142            first_name=first_name,
    143            last_name=last_name,
    144            year_of_birth=year_of_birth,
    145            sex='F',
    146        )
    

    still green.

  • I add a random value for the sex parameter with the sex variable in the call to src.person.factory for a_person

    141        a_person = src.person.factory(
    142            first_name=first_name,
    143            last_name=last_name,
    144            year_of_birth=year_of_birth,
    145            # sex='F',
    146            sex=sex,
    147        )
    

    green.

  • I use the age variable in my_expectation for a_person

    149    reality = src.person.say_hello(a_person)
    150    my_expectation = (
    151        f'Hi, my name is {first_name} {last_name}'
    152        # f' and I am {calculate_age(year_of_birth)}'
    153        f' and I am {age}'
    154    )
    155    self.assertEqual(reality, my_expectation)
    

    still green.

  • I add a random person for the four people I made in test_factory_person_says_hello because they all now all use the same random values. The assertions are also the same for each case because they use the first_name, last_name and age variables that are sent in the call to src.person.factory

    149        reality = src.person.say_hello(a_person)
    150        my_expectation = (
    151            f'Hi, my name is {first_name} {last_name}'
    152            # f' and I am {calculate_age(year_of_birth)}'
    153            f' and I am {age}'
    154        )
    155        self.assertEqual(reality, my_expectation)
    156
    157        a_random_person = src.person.factory(
    158            first_name=first_name,
    159            last_name=last_name,
    160            sex=sex,
    161            year_of_birth=year_of_birth,
    162        )
    163
    164        reality = src.person.say_hello(a_random_person)
    165        my_expectation = ''
    166        self.assertEqual(reality, my_expectation)
    167
    168
    169# Exceptions seen
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times, and each time the terminal shows AssertionError

    AssertionError: 'Hi, my name is Z Y and I am X' != ''
    

    where X is a random age, Z is a random last name and Y is a random first name

  • I change my_expectation to match reality for a_random_person

    157        a_random_person = src.person.factory(
    158            first_name=first_name,
    159            last_name=last_name,
    160            sex=sex,
    161            year_of_birth=year_of_birth,
    162        )
    163
    164        reality = src.person.say_hello(a_random_person)
    165        my_expectation = (
    166            f'Hi, my name is {first_name} {last_name}'
    167            f' and I am {age}'
    168        )
    169        self.assertEqual(reality, my_expectation)
    170
    171
    172# Exceptions seen
    

    the test passes.

  • I remove the commented lines and joe, jane, john and person from test_factory_person_says_hello because they are all the same as a_random_person

    68    def test_factory_person_says_hello(self):
    69        first_name = get_random_name()
    70        last_name = get_random_name()
    71        sex = pick_one('F', 'M')
    72
    73        this_year = datetime.datetime.now().year
    74        year_of_birth = random.randint(
    75            this_year-120, this_year
    76        )
    77        age = calculate_age(year_of_birth)
    78
    79        a_random_person = src.person.factory(
    80            first_name=first_name,
    81            last_name=last_name,
    82            sex=sex,
    83            year_of_birth=year_of_birth,
    84        )
    85
    86        reality = src.person.say_hello(a_random_person)
    87        my_expectation = (
    88            f'Hi, my name is {first_name} {last_name}'
    89            f' and I am {age}'
    90        )
    91        self.assertEqual(reality, my_expectation)
    92
    93
    94# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am \
    'test_factory_person_says_hello with random values'
    

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


extract get_random_year_of_birth function

I make the this_year and year_of_birth variables the same way in all three tests.

  • I go back to the terminal that is running the tests

  • I add a function to use to replace the repetition of making the values for the this_year and year_of_birth variables in test_person.py

    18def calculate_age(year_of_birth):
    19    return (
    20        datetime.datetime.now().year
    21      - year_of_birth
    22    )
    23
    24
    25def get_random_year_of_birth():
    26    this_year = datetime.datetime.now().year
    27    return random.randint(
    28        this_year-120, this_year
    29    )
    30
    31
    32class TestPerson(unittest.TestCase):
    33
    34    def test_factory_w_keyword_arguments(self):
    
  • I use the get_random_year_of_birth function to replace the repetition of making the values for the this_year and year_of_birth variables in test_factory_w_keyword_arguments

    34    def test_factory_w_keyword_arguments(self):
    35        # this_year = datetime.datetime.now().year
    36        # year_of_birth = random.randint(
    37        #     this_year-120, this_year
    38        # )
    39        year_of_birth = get_random_year_of_birth()
    

    the test is still green.

  • I remove the commented lines

    34    def test_factory_w_keyword_arguments(self):
    35        year_of_birth = get_random_year_of_birth()
    36
    37        a_person = dict(
    38            first_name=get_random_name(),
    39            last_name=get_random_name(),
    40            sex=pick_one('F', 'M'),
    41        )
    42
    43        reality = src.person.factory(
    44            **a_person,
    45            year_of_birth=year_of_birth,
    46        )
    47        my_expectation = dict(
    48            **a_person,
    49            age=calculate_age(year_of_birth),
    50        )
    51        self.assertEqual(reality, my_expectation)
    52
    53    def test_factory_w_optional_arguments(self):
    
  • I use the get_random_year_of_birth function to replace the repetition of making the values for the this_year and year_of_birth variables in test_factory_w_optional_arguments

    53    def test_factory_w_optional_arguments(self):
    54        first_name = get_random_name()
    55        # this_year = datetime.datetime.now().year
    56        # year_of_birth = random.randint(
    57        #     this_year-120, this_year
    58        # )
    59        year_of_birth = get_random_year_of_birth()
    

    the test is still green.

  • I remove the commented lines

    53    def test_factory_w_optional_arguments(self):
    54        first_name = get_random_name()
    55        year_of_birth = get_random_year_of_birth()
    56
    57        reality = src.person.factory(
    58            first_name=first_name,
    59            year_of_birth=year_of_birth,
    60        )
    61        my_expectation = dict(
    62            first_name=first_name,
    63            last_name='doe',
    64            sex='M',
    65            age=calculate_age(year_of_birth),
    66        )
    67        self.assertEqual(reality, my_expectation)
    68
    69    def test_factory_person_says_hello(self):
    
  • I use the get_random_year_of_birth function to replace the repetition of making the values for the this_year and year_of_birth variables in test_factory_person_says_hello

    69    def test_factory_person_says_hello(self):
    70        first_name = get_random_name()
    71        last_name = get_random_name()
    72        sex = pick_one('F', 'M')
    73
    74        # this_year = datetime.datetime.now().year
    75        # year_of_birth = random.randint(
    76        #     this_year-120, this_year
    77        # )
    78        year_of_birth = get_random_year_of_birth()
    79        age = calculate_age(year_of_birth)
    

    the test is still green.

  • I remove the commented lines

    69    def test_factory_person_says_hello(self):
    70        first_name = get_random_name()
    71        last_name = get_random_name()
    72        sex = pick_one('F', 'M')
    73
    74        year_of_birth = get_random_year_of_birth()
    75        age = calculate_age(year_of_birth)
    76
    77        a_random_person = src.person.factory(
    78            first_name=first_name,
    79            last_name=last_name,
    80            sex=sex,
    81            year_of_birth=year_of_birth,
    82        )
    83
    84        reality = src.person.say_hello(a_random_person)
    85        my_expectation = (
    86            f'Hi, my name is {first_name} {last_name}'
    87            f' and I am {age}'
    88        )
    89        self.assertEqual(reality, my_expectation)
    90
    91
    92# Exceptions seen
    93# AssertionError
    94# NameError
    95# AttributeError
    96# TypeError
    97# SyntaxError
    
  • I add a git commit message in the other terminal

    git commit -am \
    'extract get_random_year_of_birth function'
    

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


test_person_tests

I want to write the solution without looking at the tests


RED: make it fail


  • I go back to the terminal that is running the tests

  • I close test_person.py

  • then I delete all the text in person.py, the terminal is my friend, and shows AttributeError

    FAILED ...test_factory_person_says_hello -
        AttributeError: module 'src.person' has
                        no attribute 'factory'
    FAILED ...test_factory_w_keyword_arguments -
        AttributeError: module 'src.person' has
                        no attribute 'factory'
    FAILED ...test_factory_w_optional_arguments -
        AttributeError: module 'src.person' has
                        no attribute 'factory'
    

    because there is nothing in person.py with the name factory


GREEN: make it pass


  • I add the name

    1factory
    

    the terminal is my friend, and shows NameError

    NameError: name 'factory' is not defined
    

    because I have not told Python what factory means

  • I point it to None to define it

    1# factory
    2factory = None
    

    the terminal is my friend, and shows TypeError

    TypeError: 'NoneType' object is not callable
    

    because factory points to None and I cannot call None like a function

  • I make factory a function to make it callable

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

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'first_name'
    

    because the test called the factory function with a keyword argument (first_name) that is not in the function definition

  • I add first_name to the function definition

    1# factory
    2# factory = None
    3# def factory():
    4def factory(first_name):
    5    return None
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'year_of_birth'
    

    because the test called the factory function with a keyword argument (year_of_birth) that is not in the function definition

  • I add year_of_birth to the function definition

    1# factory
    2# factory = None
    3# def factory():
    4# def factory(first_name):
    5def factory(first_name, year_of_birth):
    6    return None
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != {'first_name': Y,
                             'last_name': 'doe',
                             'sex': 'M', 'age': X}
    

    because the assertion expects a dictionary and the factory function returns None

  • I copy and paste the dictionary from the terminal to make the function return a dictionary instead of None

     1# factory
     2# factory = None
     3# def factory():
     4# def factory(first_name):
     5def factory(first_name, year_of_birth):
     6    # return None
     7    return {
     8        'first_name': 'john',
     9        'last_name': 'doe',
    10        'sex': 'M',
    11        'age': 55,
    12    }
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and the terminal shows AssertionError

    AssertionError:
        {'first_name': A, 'last_name': 'doe',
         'sex': 'M', 'age': Y}
     != {'first_name': Z, 'last_name': 'doe',
         'sex': 'M', 'age': X}
    

    the values of the age and first_name keys change randomly

  • I use the first_name input parameter in the return statement

     1# factory
     2# factory = None
     3# def factory():
     4# def factory(first_name):
     5def factory(first_name, year_of_birth):
     6    # return None
     7    return {
     8        # 'first_name': 'john',
     9        'first_name': first_name,
    10        'last_name': 'doe',
    11        'sex': 'M',
    12        'age': 55,
    13    }
    

    the first name matches and if the ages are different, the terminal shows AssertionError

    AssertionError:
        {'first_name': Z, 'last_name': 'doe',
         'sex': 'M', 'age': Y}
     != {'first_name': Z, 'last_name': 'doe',
         'sex': 'M', 'age': X}
    

    and TypeError

    TypeError: factory() got
               an unexpected keyword argument 'last_name'.
               Did you mean 'first_name'?
    
  • I use the year_of_birth input parameter in the return statement for the value of age

     1# factory
     2# factory = None
     3# def factory():
     4# def factory(first_name):
     5def factory(first_name, year_of_birth):
     6    # return None
     7    return {
     8        # 'first_name': 'john',
     9        'first_name': first_name,
    10        'last_name': 'doe',
    11        'sex': 'M',
    12        # 'age': 55,
    13        'age': year_of_birth,
    14    }
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': Y, 'last_name': 'doe',
         'sex': 'M', 'age': ABCD}
     != {'first_name': Y, 'last_name': 'doe',
         'sex': 'M', 'age': X}
    

    because the factory function returned 4 digits (a year) as the value for the age key, and the assertion expects the difference between that value and the current year

  • I add an import statement for the datetime module at the top of the file

    1import datetime
    2
    3
    4
    5# factory
    6# factory = None
    7# def factory():
    8# def factory(first_name):
    9def factory(first_name, year_of_birth):
    

    the terminal still shows AssertionError

  • I use the datetime module to get the current year, then use it for the age calculation

     9def factory(first_name, year_of_birth):
    10    # return None
    11    return {
    12        # 'first_name': 'john',
    13        'first_name': first_name,
    14        'last_name': 'doe',
    15        'sex': 'M',
    16        # 'age': 55,
    17        # 'age': year_of_birth,
    18        'age': (
    19            datetime.datetime.today().year
    20          - year_of_birth
    21        ),
    22    }
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'last_name'.
               Did you mean 'first_name'?
    

    because the test called the factory function with a keyword argument (last_name) that is not in the function definition

  • I add a new input parameter to the function

     9# def factory(first_name, year_of_birth):
    10def factory(
    11        first_name, year_of_birth,
    12        last_name,
    13    ):
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() missing 1 required
               positional argument: 'last_name'
    

    because the test called the function with another argument and Python took that argument as a positional argument for last_name

  • I add a default value for last_name so Python does not take it is a positional argument when a name is not given

    10def factory(
    11        first_name, year_of_birth,
    12        # last_name,
    13        last_name=None,
    14    ):
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() got
               an unexpected keyword argument 'sex'
    

    because the test called the factory function with a keyword argument (sex) that is not in the function definition

  • I add the name to the definition of the function

    10def factory(
    11        first_name, year_of_birth,
    12        # last_name,
    13        # last_name=None,
    14        last_name=None, sex,
    15    ):
    

    the terminal is my friend, and shows SyntaxError

    SyntaxError: parameter without a default follows
                 parameter with a default
    

    because parameters without default values must come before parameters with default values

  • I add a default value for sex to make it optional

    10def factory(
    11        first_name, year_of_birth,
    12        # last_name,
    13        # last_name=None,
    14        # last_name=None, sex,
    15        last_name=None, sex=None,
    16    ):
    

    the terminal is my friend, and shows AssertionError first_name and age match. If the last names are different, the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': C, 'last_name': B, 'sex': Z, 'age': X}
     != {'first_name': C, 'last_name': A, 'sex': Y, 'age': X}
    

    and AttributeError

    AttributeError: module 'src.person'
                    has no attribute 'say_hello'
    
  • I use the sex input parameter in the return statement

    10def factory(
    11        first_name, year_of_birth,
    12        # last_name,
    13        # last_name=None,
    14        # last_name=None, sex,
    15        last_name=None, sex=None,
    16    ):
    17    # return None
    18    return {
    19        # 'first_name': 'john',
    20        'first_name': first_name,
    21        'last_name': 'doe',
    22        # 'sex': 'M',
    23        'sex': sex,
    24        # 'age': 55,
    25        # 'age': year_of_birth,
    26        'age': (
    27            datetime.datetime.today().year
    28          - year_of_birth
    29        ),
    30    }
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': Y, 'last_name': 'doe',
         'sex': None, 'age': X}
     != {'first_name': Y, 'last_name': 'doe',
         'sex': 'M', 'age': X}
    

    because the assertion expects 'M' as the value of sex and the function returns None which is its default value

  • I change the default value of sex to 'M'

    10def factory(
    11    first_name, year_of_birth,
    12    # last_name,
    13    # last_name=None,
    14    # last_name=None, sex,
    15    # last_name=None, sex=None,
    16    last_name=None, sex='M',
    17):
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and if the last_name is not 'doe', the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': A, 'last_name': 'doe', 'sex': Y, 'age': X}
     != {'first_name': A, 'last_name': Z, 'sex': Y, 'age': X}
    

    because the last_name value is different between the two dictionaries. It still shows AttributeError

    AttributeError: module 'src.person'
                    has no attribute 'say_hello'
    
  • I use the last_name input parameter in the return statement

     4def factory(
     5        first_name, year_of_birth,
     6        # last_name,
     7        # last_name=None,
     8        # last_name=None, sex,
     9        # last_name=None, sex=None,
    10        last_name=None, sex='M',
    11    ):
    12    # return None
    13    return {
    14        # 'first_name': 'john',
    15        'first_name': first_name,
    16        # 'last_name': 'doe',
    17        'last_name': last_name,
    18        # 'sex': 'M',
    19        'sex': sex,
    20        # 'age': 55,
    21        # 'age': year_of_birth,
    22        'age': (
    23            datetime.datetime.today().year
    24          - year_of_birth
    25        ),
    26    }
    

    the terminal is my friend, and shows AssertionError

    AssertionError:
        {'first_name': Z, 'last_name': None,
         'sex': Y, 'age': X}
     != {'first_name': Z, 'last_name': 'doe',
         'sex': Y, 'age': X}
    

    because the assertion expects 'doe' as the value of last_name and the function returns None which is its default value

  • I change the default value for last_name to match the expectation

    10def factory(
    11        first_name, year_of_birth,
    12        # last_name,
    13        # last_name=None,
    14        # last_name=None, sex,
    15        # last_name=None, sex=None,
    16        # last_name=None, sex='M',
    17        last_name='doe', sex='M',
    18    ):
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.person'
                    has no attribute 'say_hello'
    

    because I do not have a definition for say_hello

  • I add say_hello

     1import datetime
     2
     3
     4say_hello
     5
     6
     7# factory
     8# factory = None
     9# def factory():
    10# def factory(first_name):
    11# def factory(first_name, year_of_birth):
    12def factory(
    

    the terminal is my friend, and shows NameError

    NameError: name 'say_hello' is not defined
    
  • I point it to None to define it

    1import datetime
    2
    3
    4# say_hello
    5say_hello = None
    6
    7
    8# factory
    

    the terminal is my friend, and shows TypeError

    TypeError: 'NoneType' object is not callable
    

    because say_hello points to None and I cannot call None like a function

  • I make say_hello a function to make it callable

     1import datetime
     2
     3
     4# say_hello
     5# say_hello = None
     6def say_hello():
     7    return None
     8
     9
    10# factory
    

    the terminal is my friend, and shows TypeError

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

    because the function definition for say_hello does not allow calling it with inputs (the parentheses are empty) and the test sends input.

  • I add a name to the function definition

    4# say_hello
    5# say_hello = None
    6# def say_hello():
    7def say_hello(argument):
    8    return None
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != 'Hi, my name is Z Y and I am Z'
    

    because the test expects a string and the say_hello function returns None

  • I copy (ctrl/command+c) the string from the terminal and paste it (ctrl/command+v) to replace the return statement

     4# say_hello
     5# say_hello = None
     6# def say_hello():
     7def say_hello(argument):
     8    # return None
     9    return 'Hi, my name is jade doe and I am 66'
    10
    11
    12# factory
    

    I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times, and the terminal shows AssertionError

    AssertionError:
        'Hi, my name is Z Y and I am X'
     != 'Hi, my name is A B and I am C'
    

    the names and ages change

  • I return the input to compare it with what the test expects

     4# say_hello
     5# say_hello = None
     6# def say_hello():
     7def say_hello(argument):
     8    # return None
     9    return argument
    10    return 'Hi, my name is jade doe and I am 66'
    

    the terminal shows AssertionError

    AssertionError:
        {'first_name': A, 'last_name': Z, 'sex': Y, 'age': X}
    != 'Hi, my name is A Z and I am X'
    

    the test sends a dictionary as input and expects a string as output, and the string uses the values of the first_name, last_name and age keys from the dictionary it receives

  • I return an f-string with the values of the first_name, last_name and age keys from the dictionary

     4# say_hello
     5# say_hello = None
     6# def say_hello():
     7def say_hello(argument):
     8    # return None
     9    return (
    10        f'Hi, my name is {argument.get("first_name")}'
    11        f' {argument.get("last_name")}'
    12        f' and I am {argument.get("age")}'
    13    )
    14    return argument
    15    return 'Hi, my name is jade doe and I am 66'
    

    the test passes. Okay!


REFACTOR: make it better


  • I use the Rename Symbol feature of the Integrated Development Environment (IDE) to change argument to make it clearer

     4# say_hello
     5# say_hello = None
     6# def say_hello():
     7def say_hello(a_dictionary):
     8    # return None
     9    return (
    10        f'Hi, my name is {a_dictionary.get("first_name")}'
    11        f' {a_dictionary.get("last_name")}'
    12        f' and I am {a_dictionary.get("age")}'
    13    )
    14    return a_dictionary
    15    return 'Hi, my name is jade doe and I am 66'
    
  • I remove the commented lines and other return statements

     1import datetime
     2
     3
     4def say_hello(a_dictionary):
     5    return (
     6        f'Hi, my name is {a_dictionary.get("first_name")}'
     7        f' {a_dictionary.get("last_name")}'
     8        f' and I am {a_dictionary.get("age")}'
     9    )
    10
    11
    12def factory(
    13        first_name, year_of_birth,
    14        last_name='doe', sex='M',
    15    ):
    16    return {
    17        'first_name': first_name,
    18        'last_name': last_name,
    19        'sex': sex,
    20        'age': (
    21            datetime.datetime.today().year
    22          - year_of_birth
    23        ),
    24    }
    

    This factory function only has two parameters with default values (last_name and sex)

    def factory(
            first_name, year_of_birth,
            last_name='doe', sex='M',
        ):
    

    the first solution had three parameters with default values (last_name, sex and year_of_birth)

    def factory(
            first_name, last_name='doe',
            sex='M', year_of_birth=None,
        ):
    

    I can learn new things from repetition.

  • I add a git commit message in the other terminal

    git commit -am \
    'refactor factory and say_hello functions'
    

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


close the project

  • I close person.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 person

    cd ..
    

    the terminal shows

    ...\pumping_python
    

    I am back in the pumping_python directory


review

I ran tests to make

I also saw these Exceptions


code from the chapter

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


what is next?

you know:

Would you like to see another way to make a person?


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.