how to make a person


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

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 src.person
  3import unittest
  4import random
  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 get_current_year():
 19    return datetime.datetime.now().year
 20
 21
 22def calculate_age(year_of_birth):
 23    return (
 24        get_current_year()
 25      - year_of_birth
 26    )
 27
 28
 29def get_random_year_of_birth():
 30    this_year = get_current_year()
 31    return random.randint(
 32        this_year-120, this_year
 33    )
 34
 35
 36class TestPerson(unittest.TestCase):
 37
 38    def test_factory_w_keyword_arguments(self):
 39        a_person = dict(
 40            first_name=get_random_name(),
 41            last_name=get_random_name(),
 42            sex=pick_one('F', 'M'),
 43        )
 44        year_of_birth = get_random_year_of_birth()
 45
 46        reality = src.person.factory(
 47            **a_person,
 48            year_of_birth=year_of_birth,
 49        )
 50        my_expectation = dict(
 51            a_person,
 52            age=calculate_age(year_of_birth),
 53        )
 54        self.assertEqual(reality, my_expectation)
 55
 56    def test_factory_w_optional_arguments(self):
 57        first_name = get_random_name()
 58        year_of_birth = get_random_year_of_birth()
 59
 60        reality = src.person.factory(
 61            first_name=first_name,
 62            year_of_birth=year_of_birth,
 63        )
 64        my_expectation = dict(
 65            first_name=first_name,
 66            last_name='doe',
 67            sex='M',
 68            age=calculate_age(year_of_birth),
 69        )
 70        self.assertEqual(reality, my_expectation)
 71
 72    def test_factory_person_says_hello(self):
 73        first_name = 'joe'
 74        last_name = 'blow'
 75        year_of_birth = get_random_year_of_birth()
 76        age = calculate_age(year_of_birth)
 77
 78        joe = src.person.factory(
 79            first_name=first_name,
 80            last_name=last_name,
 81            year_of_birth=year_of_birth,
 82        )
 83
 84        reality = src.person.say_hello(joe)
 85        my_expectation = (
 86            f'Hi, my name is {first_name}'
 87            f' {last_name} and I am {age}'
 88        )
 89        self.assertEqual(reality, my_expectation)
 90
 91        first_name = 'jane'
 92        year_of_birth = get_random_year_of_birth()
 93        age = calculate_age(year_of_birth)
 94
 95        jane = src.person.factory(
 96            first_name=first_name,
 97            sex='F',
 98            year_of_birth=year_of_birth
 99        )
100
101        reality = src.person.say_hello(jane)
102        my_expectation = (
103            f'Hi, my name is {first_name}'
104            f' doe and I am {age}'
105        )
106        self.assertEqual(reality, my_expectation)
107
108        first_name = 'john'
109        last_name = 'smith'
110        year_of_birth = get_random_year_of_birth()
111        age = calculate_age(year_of_birth)
112
113        john = src.person.factory(
114            first_name=first_name,
115            last_name=last_name,
116            year_of_birth=year_of_birth,
117        )
118
119        reality = src.person.say_hello(john)
120        my_expectation = (
121            f'Hi, my name is {first_name}'
122            f' {last_name} and I am {age}'
123        )
124        self.assertEqual(reality, my_expectation)
125
126        first_name = 'mary'
127        last_name = 'public'
128        year_of_birth = get_random_year_of_birth()
129        age = calculate_age(year_of_birth)
130
131        mary = src.person.factory(
132            first_name=first_name,
133            last_name=last_name,
134            year_of_birth=year_of_birth,
135            sex='F',
136        )
137
138        reality = src.person.say_hello(mary)
139        my_expectation = (
140            f'Hi, my name is {first_name}'
141            f' {last_name} and I am {age}'
142        )
143        self.assertEqual(reality, my_expectation)
144
145
146# Exceptions seen
147# AssertionError
148# NameError
149# AttributeError
150# TypeError
151# 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 use mkdir to make a folder named src

    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" > requirements.txt
    

    the terminal goes back to the command line.

  • I add pytest-watcher to the requirements file

    echo "pytest-watcher" >> requirements.txt
    

    the terminal goes back to the command line.

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

    uv add --requirement requirements.txt
    

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

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

    git add .
    

    the terminal goes back to the command line.

  • I add a git commit message

    git commit -am 'setup project'
    

    the terminal shows

    [main (root-commit) a0b12c3] setup project
     9 files changed, 145 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
    
  • 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 open person.py from the src folder

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

    1def factory():
    2    return None
    

    the test passes because when src.person.factory() is called, Python checks person.py in the src folder for a function definition with the name factory and finds it.


REFACTOR: make it better


  • I want the function to take an argument called first_name. I add it to the call from the test in test_person.py

     7    def test_factory_w_keyword_arguments(self):
     8        # reality = src.person.factory()
     9        reality = src.person.factory(
    10            first_name='first_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 'first_name'
    

    because the function definition for factory in person.py in the src folder, 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

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

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

    the test passes.


  • I want the function to take an 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        reality = src.person.factory(
    10            first_name='first_name',
    11            last_name='last_name',
    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 '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

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

    the test passes.


  • I want the function to take an 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        reality = src.person.factory(
    10            first_name='first_name',
    11            last_name='last_name',
    12            sex='M',
    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 '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

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

    the test passes.


  • I want the function to take an 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        reality = src.person.factory(
    10            first_name='first_name',
    11            last_name='last_name',
    12            sex='M',
    13            year_of_birth=2000,
    14        )
    15        my_expectation = None
    16        self.assertEqual(reality, my_expectation)
    17
    18
    19# 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

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

    the test passes.


  • I want the factory function to return a dictionary (any key-value pairs in curly braces { } separated by commas) 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        reality = src.person.factory(
    10            first_name='first_name',
    11            last_name='last_name',
    12            sex='M',
    13            year_of_birth=2000,
    14        )
    15        # my_expectation = None
    16        my_expectation = dict()
    17        self.assertEqual(reality, my_expectation)
    18
    19
    20# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: None != {}
    

    because this happens when the test runs

    reality        = src.person.factory(
                         first_name='first_name',
                         last_name='last_name',
                         sex='M',
                         year_of_birth=2000,
                     )
                     # the factory function returns
                     None
    
    my_expectation = dict()
                     # the dict constructor returns
                     {}
    
    self.assertEqual(reality, my_expectation)
    self.assertEqual(None   , {}            )
    

    which raises AssertionError since the function returns None and the assertion expects {}.

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

    1# def factory():
    2# def factory(first_name):
    3# def factory(first_name, last_name):
    4def factory(
    5        first_name, last_name,
    6        sex, year_of_birth,
    7    ):
    8    # return None
    9    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        reality = src.person.factory(
    10            first_name='first_name',
    11            last_name='last_name',
    12            sex='M',
    13            year_of_birth=2000,
    14        )
    15        # my_expectation = None
    16        # my_expectation = dict()
    17        my_expectation = dict(
    18            first_name='first_name',
    19        )
    20        self.assertEqual(reality, my_expectation)
    21
    22
    23# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

    because this happens when the test runs

    reality        = src.person.factory(
                         first_name='first_name',
                         last_name='last_name',
                         sex='M',
                         year_of_birth=2000,
                     )
                     {} # the factory function returns {}
    
    my_expectation = dict(first_name='first_name')
                     {'first_name': 'first_name'}
    
    self.assertEqual(reality, my_expectation              )
    # is the same as
    self.assertEqual({}     , {'first_name': 'first_name'})
    

    which raises AssertionError since 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

     1# def factory():
     2# def factory(first_name):
     3# def factory(first_name, last_name):
     4def factory(
     5        first_name, last_name,
     6        sex, year_of_birth,
     7    ):
     8    # return None
     9    # return {}
    10    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        reality = src.person.factory(
    10            # first_name='first_name',
    11            first_name='jane',
    12            last_name='last_name',
    13            sex='M',
    14            year_of_birth=2000,
    15        )
    16        # my_expectation = None
    17        # my_expectation = dict()
    18        my_expectation = dict(
    19            # first_name='first_name',
    20            first_name='jane',
    21        )
    22        self.assertEqual(reality, my_expectation)
    23
    24
    25# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

    because this happens when the test runs

    reality        = src.person.factory(
                         first_name='jane',
                         last_name='last_name',
                         sex='M',
                         year_of_birth=2000,
                     )
                     # the factory function returns
                     {'first_name': 'first_name'}
    
    my_expectation = dict(first_name='jane')
                     # the dict constructor returns
                     {'first_name': 'jane'}
    
    self.assertEqual(reality, my_expectation)
    # is the same as
    self.assertEqual(
        {'first_name': 'first_name'},
        {'first_name': 'jane'}
    )
    

    which raises AssertionError since I changed the value for first_name to 'jane' in my_expectation and the function returns a dictionary with a different value ('first_name').

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

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

    the test passes.

  • I typed the value for first_name two times in the test, which means I had to make a change in two places when I wanted a different value for it. I add a variable for 'jane' in 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' from the test

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9
    10        # reality = src.person.factory()
    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            sex='M',
    17            year_of_birth=2000,
    18        )
    19        # my_expectation = None
    20        # my_expectation = dict()
    21        my_expectation = dict(
    22            # first_name='first_name',
    23            # first_name='jane',
    24            first_name=first_name,
    25        )
    26        self.assertEqual(reality, my_expectation)
    27
    28
    29# 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 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        first_name = 'jane'
     9
    10        # reality = src.person.factory()
    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            sex='M',
    17            year_of_birth=2000,
    18        )
    19        # my_expectation = None
    20        # my_expectation = dict()
    21        my_expectation = dict(
    22            # first_name='first_name',
    23            # first_name='jane',
    24            first_name=first_name,
    25            last_name='last_name',
    26        )
    27        self.assertEqual(reality, my_expectation)
    28
    29
    30# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

    because this happens when the test runs

    first_name = 'jane'
    
    reality        = src.person.factory(
                         first_name=first_name,
                         last_name='last_name',
                         sex='M',
                         year_of_birth=2000,
                     )
                     # the factory function returns
                     {'first_name': 'jane'}
    
    my_expectation = dict(
                         first_name=first_name,
                         last_name='last_name',
                     )
                     # the dict constructor returns
                     {
                         'first_name': 'jane',
                         'last_name': 'last_name'
                     }
    
    self.assertEqual(reality, my_expectation)
    # is the same as
    self.assertEqual(
        {'first_name': 'jane'},
        {'first_name': 'jane', 'last_name': 'last_name'}
    )
    
  • I change the return statement in person.py

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

    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        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            sex='M',
    18            year_of_birth=2000,
    19        )
    20        # my_expectation = None
    21        # my_expectation = dict()
    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        )
    29        self.assertEqual(reality, my_expectation)
    30
    31
    32# 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 this happens when the test runs

    first_name = 'jane'
    
    reality        = src.person.factory(
                         first_name=first_name,
                         last_name='doe',
                         sex='M',
                         year_of_birth=2000,
                     )
                     # the factory function returns
                     {
                         'first_name': 'jane',
                         'last_name': 'last_name'
                     }
    
    my_expectation = dict(
                         first_name=first_name,
                         last_name='doe',
                     )
                     # the dict constructor returns
                     {
                         'first_name': 'jane',
                         'last_name': 'doe'
                     }
    
    self.assertEqual(reality, my_expectation)
    # is the same as
    self.assertEqual(
        {'first_name': 'jane', 'last_name': 'last_name'},
        {'first_name': 'jane', 'last_name': 'doe'}
    )
    

    which raises AssertionError since I changed the value for last_name to 'doe' in my_expectation and the function returns a dictionary with a different value ('last_name').

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

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

    the test passes.

  • I typed the value for last_name two times in the test, which means I had to make a change in two places when I wanted a different value for it. I add a variable for 'doe' 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()
    
  • I use the variable to remove repetition of 'doe' from the test

     7    def test_factory_w_keyword_arguments(self):
     8        first_name = 'jane'
     9        last_name = 'doe'
    10
    11        # reality = src.person.factory()
    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            year_of_birth=2000,
    21        )
    22        # my_expectation = None
    23        # my_expectation = dict()
    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        )
    32        self.assertEqual(reality, my_expectation)
    33
    34
    35# Exceptions seen
    

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


  • I add a key called sex 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        first_name = 'jane'
     9        last_name = 'doe'
    10
    11        # reality = src.person.factory()
    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            year_of_birth=2000,
    21        )
    22        # my_expectation = None
    23        # my_expectation = dict()
    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        )
    33        self.assertEqual(reality, my_expectation)
    34
    35
    36# 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'}
    

    because this happens when the test runs

    first_name = 'jane'
    last_name = 'doe'
    
    reality        = src.person.factory(
                         first_name=first_name,
                         last_name=last_name,
                         sex='M',
                         year_of_birth=2000,
                     )
                     # the factory function returns
                     {
                         'first_name': 'jane',
                         'last_name': 'doe'
                     }
    
    my_expectation = dict(
                         first_name=first_name,
                         last_name=last_name,
                         sex='M'
                     )
                     # the dict constructor returns
                     {
                         'first_name': 'jane',
                         'last_name': 'doe',
                         'sex': 'M'
                     }
    
    self.assertEqual(reality, my_expectation)
    # is the same as
    self.assertEqual(
        {'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

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

    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        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            year_of_birth=2000,
    22        )
    23        # my_expectation = None
    24        # my_expectation = dict()
    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        )
    35        self.assertEqual(reality, my_expectation)
    36
    37
    38# 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 this happens when the test runs

    first_name = 'jane'
    last_name = 'doe'
    
    reality        = src.person.factory(
                         first_name=first_name,
                         last_name=last_name,
                         sex='F',
                         year_of_birth=2000,
                     )
                     # the factory function returns
                     {
                         'first_name': 'jane',
                         'last_name': 'doe',
                         'sex': 'M'
                     }
    
    my_expectation = dict(
                         first_name=first_name,
                         last_name=last_name,
                         sex='F'
                     )
                     # the dict constructor returns
                     {
                         'first_name': 'jane',
                         'last_name': 'doe',
                         'sex': 'F'
                     }
    
    self.assertEqual(reality, my_expectation)
    # is the same as
    self.assertEqual(
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'},
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'}
    )
    

    which raises AssertionError since I changed the value for sex to 'F' in my_expectation and the function returns a dictionary with a different value ('M').

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

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

    the test passes.

  • I typed the value for sex two times in the test, which means I had to make a change in two places when I wanted a different value for it. I add a variable for 'F' 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()
    
  • 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        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        )
    25        # my_expectation = None
    26        # my_expectation = dict()
    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        )
    38        self.assertEqual(reality, my_expectation)
    39
    40
    41# 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        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        )
    25        # my_expectation = None
    26        # my_expectation = dict()
    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        )
    39        self.assertEqual(reality, my_expectation)
    40
    41
    42# 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}
    

    because this happens when the test runs

    first_name = 'jane'
    last_name = 'doe'
    sex = 'F'
    
    reality        = src.person.factory(
                         first_name=first_name,
                         last_name=last_name,
                         sex=sex,
                         year_of_birth=2000,
                     )
                     # the factory function returns
                     {
                         'first_name': 'jane',
                         'last_name': 'last_name',
                         'sex': 'F'
                     }
    
    my_expectation = dict(
                         first_name=first_name,
                         last_name=last_name,
                         sex=sex,
                         age=2026-2000,
                     )
                     # the dict constructor returns
                     {
                         'first_name': 'jane',
                         'last_name': 'doe',
                         'sex': 'F',
                         'age': 26
                     }
    
    self.assertEqual(reality, my_expectation)
    self.assertEqual(
        {'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

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

    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        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        )
    26        # my_expectation = None
    27        # my_expectation = dict()
    28        my_expectation = dict(
    29            # first_name='first_name',
    30            # first_name='jane',
    31            first_name=first_name,
    32            # last_name='last_name',
    33            # last_name='doe',
    34            last_name=last_name,
    35            # sex='M',
    36            # sex='F',
    37            sex=sex,
    38            # age=2026-2000,
    39            age=2026-1996,
    40        )
    41        self.assertEqual(reality, my_expectation)
    42
    43
    44# 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 this happens when the test runs

    first_name = 'jane'
    last_name = 'doe'
    sex = 'F'
    
    reality        = src.person.factory(
                         first_name=first_name,
                         last_name=last_name,
                         sex=sex,
                         year_of_birth=1996,
                     )
                     # the factory function returns
                     {
                         'first_name': 'jane',
                         'last_name': 'doe',
                         'sex': 'F'
                         'age': 26
                     }
    
    my_expectation = dict(
                         first_name=first_name,
                         last_name=last_name,
                         sex=sex,
                         age=2026-1996
                     )
                     # the dict constructor returns
                     {
                         'first_name': 'jane',
                         'last_name': 'doe',
                         'sex': 'F',
                         'age': 30
                     }
    
    self.assertEqual(reality, my_expectation)
    self.assertEqual(
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': 26},
        {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F',
         'age': 30}
    )
    

    which raises AssertionError since I changed the calculation for age to 2026-1996 in my_expectation and the function returns a dictionary with a different value (26).

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

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

    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 had to make a change in two places when I wanted a different value for it. I add a variable for 1996 in 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 1996 from the test

     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        reality = src.person.factory(
    15            # first_name='first_name',
    16            # first_name='jane',
    17            first_name=first_name,
    18            # last_name='last_name',
    19            # last_name='doe',
    20            last_name=last_name,
    21            # sex='M',
    22            # sex='F',
    23            sex=sex,
    24            # year_of_birth=2000,
    25            # year_of_birth=1996,
    26            year_of_birth=year_of_birth,
    27        )
    28        # my_expectation = None
    29        # my_expectation = dict()
    30        my_expectation = dict(
    31            # first_name='first_name',
    32            # first_name='jane',
    33            first_name=first_name,
    34            # last_name='last_name',
    35            # last_name='doe',
    36            last_name=last_name,
    37            # sex='M',
    38            # sex='F',
    39            sex=sex,
    40            # age=2026-2000,
    41            # age=2026-1996,
    42            age=2026-year_of_birth,
    43        )
    44        self.assertEqual(reality, my_expectation)
    45
    46
    47# Exceptions seen
    

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

  • 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 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 current year

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


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

  • I change 2026 in my_expectation to use a method from the datetime module

     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            age=(
    25                datetime.datetime.today().year
    26              - year_of_birth
    27            ),
    28        )
    29        self.assertEqual(reality, my_expectation)
    30
    31
    32# Exceptions seen
    

    the terminal is my friend, and shows NameError

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

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.

  • I add a git commit message in the other terminal

    git commit -am \
    'use current year for age calculation'
    

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

  • 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. This means I can assume there is a datetime.py file on the computer that came with Python

    import datetime    # import object for datetime.py
    
  • datetime.datetime is a reference to the datetime object of the datetime module. Wait a minute, that is the same name again. Do I have to remember all this?

    datetime.datetime  # use datetime object from datetime.py
    
  • datetime.datetime.now() is a call to the now method of the datetime.datetime object from the datetime module, it returns a datetime.datetime object. Oh boy!

    datetime.datetime.now()   # returns datetime.datetime object
    

    I can also use the today method to get the same value

  • datetime.datetime.today() is a call to the today method of the datetime.datetime object from the datetime module, it also returns a datetime.datetime object

    datetime.datetime.today() # returns datetime.datetime object
    
  • datetime.datetime objects have a year attribute that gives me the value of the current year which means I can do this to get the value of the current year

    result = datetime.datetime.now()
    this_year = result.year
    

    which is the same as

    this_year = datetime.datetime.now().year
    

    or

    result = datetime.datetime.today()
    this_year = result.year
    

    which is the same as

    this_year = datetime.datetime.today().year
    

    that was a lot of words, they become clearer in the chapters on classes.


test factory with random year_of_birth

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

I can do that with the random module from The Python Standard Library which is used to make fake random numbers.


RED: make it fail


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

     1import datetime
     2import src.person
     3import unittest
     4
     5
     6class TestPerson(unittest.TestCase):
     7
     8    def test_factory_w_keyword_arguments(self):
     9        first_name = 'jane'
    10        last_name = 'doe'
    11        sex = 'F'
    12        # year_of_birth = 1996
    13        year_of_birth = random.randint(
    14            datetime.datetime.now().year-120,
    15            datetime.datetime.now().year
    16        )
    17
    18        reality = src.person.factory(
    19            first_name=first_name,
    20            last_name=last_name,
    21            sex=sex,
    22            year_of_birth=year_of_birth,
    23        )
    

    the terminal is my friend, and shows NameError

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

GREEN: make it pass


  • 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
    

    the terminal is my friend, and shows AssertionError

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

    where X is a random age

    • import random brings in an object (everything in Python is an object) for the random module so I can use it in test_person.py. This means I can assume there is a random.py file on the computer that came with Python

      import random    # import object for random.py
      
    • random.randint is a method of the random module. Okay, this one does not use the same name again.

    • datetime.datetime.now().year gives me the value of the current year

    • datetime.datetime.now().year-120 gives me the value of the current year minus 120 (I assume there are no people older than 120, yet)

    • random.randint(datetime.datetime.now().year-120, datetime.datetime.now().year) is a call to the randint method of the random module with datetime.datetime.now().year-120 and datetime.datetime.now().year as positional arguments. I can assume there is some definition in random.py that looks like this

      def randint(first_number, second_number):
          return random number
                 between first_number and second_number
                 including second_number
      

      it returns a random number from 120 years ago, up to and including the current year

    • anytime I use ctrl/command+s (Windows & Linux/MacOS) to save the file, the test runs again and I get a new random value for the age key

  • 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 because import datetime brings in an object (everything in Python is an object) for the datetime module so I can use it in person.py.


REFACTOR: make it better


  • I add a variable for datetime.datetime.now().year in test_person.py

     1import datetime
     2import src.person
     3import unittest
     4import random
     5
     6
     7class TestPerson(unittest.TestCase):
     8
     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        last_name = 'doe'
    12        sex = 'F'
    13
    14        this_year = datetime.datetime.now().year
    15        # year_of_birth = 1996
    16        year_of_birth = random.randint(
    17            datetime.datetime.now().year-120,
    18            datetime.datetime.now().year
    19        )
    
  • 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
    14        this_year = datetime.datetime.now().year
    15        # year_of_birth = 1996
    16        year_of_birth = random.randint(
    17            # datetime.datetime.now().year-120,
    18            # datetime.datetime.now().year
    19            this_year-120, this_year
    20        )
    21
    22        reality = src.person.factory(
    23            first_name=first_name,
    24            last_name=last_name,
    25            sex=sex,
    26            year_of_birth=year_of_birth,
    27        )
    28        my_expectation = dict(
    29            first_name=first_name,
    30            last_name=last_name,
    31            sex=sex,
    32            # age=2026-year_of_birth,
    33            age=(
    34                # datetime.datetime.today().year
    35                this_year
    36              - year_of_birth
    37            ),
    38        )
    39        self.assertEqual(reality, my_expectation)
    40
    41
    42# Exceptions seen
    
  • I remove the commented lines

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        last_name = 'doe'
    12        sex = 'F'
    13
    14        this_year = datetime.datetime.now().year
    15        year_of_birth = random.randint(
    16            this_year-120, this_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        )
    25        my_expectation = dict(
    26            first_name=first_name,
    27            last_name=last_name,
    28            sex=sex,
    29            age=this_year-year_of_birth,
    30        )
    31        self.assertEqual(reality, my_expectation)
    32
    33
    34# Exceptions seen
    
  • I add a git commit message in the other terminal

    git commit -am 'use random values for year_of_birth'
    

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


test factory with random sex

I want to use random values in the test to make sure the factory function can handle different values for sex.


RED: make it fail


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

  • 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
    15        this_year = datetime.datetime.now().year
    
  • I use ctrl/command+s (Windows & Linux/MacOS) to run the test a few times

    • if the value of sex is 'F', the test passes

    • if the value of sex is '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

    • random is the random module

    • random.choice is a method of the random module

    • ('F', 'M') is a tuple with two strings

    • random.choice(('F', 'M')) is a call to the choice method of the random module with ('F', 'M') as input. I can assume there is some definition in random.py that looks like this

      def choice(collection):
          return random object from collection
      

      it randomly returns 'F' or 'M' every time the test runs.

    • I can also use random.choice('FM') to get the same result as random.choice(('F', 'M')) because a string like a tuple is iterable and random.choice expects an iterable as input


GREEN: make it pass


I use the sex input parameter as the value for the 'sex' key instead of a value that does not change, 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        # 'sex': 'F',
12        'sex': sex,
13        # 'age': 30,
14        'age': (
15            datetime.datetime.today().year
16          - year_of_birth
17        ),
18    }

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


REFACTOR: make it better


  • I remove the commented line in test_person.py

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = 'jane'
    11        last_name = 'doe'
    12        sex = random.choice(('F', 'M'))
    
  • 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

I want to use random values in the test to make sure the factory function can handle different values for last_name.


RED: make it fail


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

  • 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 = random.choice(('F', 'M'))
    
  • I use ctrl/command+s (Windows & Linux/MacOS) to run the test a few times

    • if the value of last_name is 'doe', the test passes

    • if the value of 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, Y is the random sex and X is the random age

    • random.choice(('doe', 'smith', 'blow', 'public',)) is a call to the choice method of the random module with ('doe', 'smith', 'blow', 'public',) as input. It randomly returns 'doe' or 'smith' or 'blow' or 'public' every time the test runs.


GREEN: make it pass


I use the last_name input parameter as the value for the 'last_name' key instead of a value that does not change 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/command+s (Windows & Linux/MacOS) to run the test a few times and it passes with no random failures


REFACTOR: make it better


  • I remove the commented line from test_person.py

     9def test_factory_w_keyword_arguments(self):
    10    first_name = 'jane'
    11    last_name = random.choice((
    12        'doe', 'smith', 'blow', 'public',
    13    ))
    14    sex = random.choice(('F', 'M'))
    
  • 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

I want to use random values in the test to make sure the factory function can handle different values for first_name.


RED: make it fail


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

  • 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 = random.choice((
    15            'doe', 'smith', 'blow', 'public',
    16        ))
    17        sex = random.choice(('F', 'M'))
    
  • I use ctrl/command+s (Windows & Linux/MacOS) to run the test a few times

    • if the value of first_name is 'jane', the test passes

    • if the value of 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, Z is the random last name, Y is the random sex, and X is the random age

    • random.choice(('jane', 'joe', 'john', 'person',)) is a call to the choice method of the random module with ('jane', 'joe', 'john', 'person',) as input. It randomly returns 'jane' or 'joe' or 'john' or 'person' every time the test runs.


GREEN: make it pass


I use the first_name input parameter as the value for the 'first_name' key instead of a value that does not change, in the return statement in person.py

 4  def 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/command+s (Windows & Linux/MacOS) to run the test a few times and it passes with no random failures.


REFACTOR: make it better


  • I remove the commented lines

     1import datetime
     2
     3
     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 remove the commented line from test_person.py

     9    def test_factory_w_keyword_arguments(self):
    10        first_name = random.choice((
    11            'jane', 'joe', 'john', 'person',
    12        ))
    13        last_name = random.choice((
    14            'doe', 'smith', 'blow', 'public',
    15        ))
    16        sex = random.choice(('F', 'M'))
    
  • 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

The first_name, last_name and sex variables all call the random.choice method. I can add a function for the calls.


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

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

     1import datetime
     2import src.person
     3import unittest
     4import random
     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 for the first_name variable

    13    def test_factory_w_keyword_arguments(self):
    14        # first_name = random.choice((
    15        first_name = pick_one((
    16            'jane', 'joe', 'john', 'person',
    17        ))
    18        last_name = random.choice((
    19            'doe', 'smith', 'blow', 'public',
    20        ))
    21        sex = random.choice(('F', 'M'))
    

    the test is still green. So far, this is exactly the same as random.choice, why would I make a function that is exactly the same?

  • Each call to the random.choice passes a tuple (an iterable). I want the function to be able to take any number of arguments I send, without knowing how many I will send

    13    def test_factory_w_keyword_arguments(self):
    14        # first_name = random.choice((
    15        # first_name = pick_one((
    16        first_name = pick_one(
    17            'jane', 'joe', 'john', 'person',
    18        # ))
    19        )
    20        last_name = random.choice((
    21            'doe', 'smith', 'blow', 'public',
    22        ))
    23        sex = random.choice(('F', 'M'))
    

    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.

  • I add a starred expressions like I did in test_unknown_number_of_arguments so that the pick_one function can take any number of positional arguments

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

    the test is green again, because

    first_name = pick_one('jane', 'joe', 'john', 'person')
                     pick_one(*choices)
                     random.choice(('jane', 'joe', 'john', 'person'))
                 # randomly return
                 # 'jane' or 'joe' or 'john' or 'person'
    

    Python reads the positional arguments as a tuple in the function since I used a starred expressions (*choices).

  • I use the new function for the last_name variable

    13    def test_factory_w_keyword_arguments(self):
    14        # first_name = random.choice((
    15        # first_name = pick_one((
    16        first_name = pick_one(
    17            'jane', 'joe', 'john', 'person',
    18        # ))
    19        )
    20        # last_name = random.choice((
    21        last_name = pick_one(
    22            'doe', 'smith', 'blow', 'public',
    23        # ))
    24        )
    25        sex = random.choice(('F', 'M'))
    

    the test is still green, because

    last_name = pick_one('doe', 'smith', 'blow', 'public')
                    pick_one(*choices)
                    random.choice(('doe', 'smith', 'blow', 'public'))
                # randomly return
                # 'doe' or 'smith' or 'blow' or 'public'
    

    Python reads the positional arguments as a tuple in the function since I used a starred expressions (*choices).

  • I use the new function for the sex variable

    13    def test_factory_w_keyword_arguments(self):
    14        # first_name = random.choice((
    15        # first_name = pick_one((
    16        first_name = pick_one(
    17            'jane', 'joe', 'john', 'person',
    18        # ))
    19        )
    20        # last_name = random.choice((
    21        last_name = pick_one(
    22            'doe', 'smith', 'blow', 'public',
    23        # ))
    24        )
    25        # sex = random.choice(('F', 'M'))
    26        sex = pick_one('F', 'M')
    

    still green, because

    sex = pick_one('F', 'M'')
              pick_one(*choices)
              random.choice(('F', 'M'))
          # randomly return 'F' or 'M'
    

    Python reads the positional arguments as a tuple in the function since I used a starred expressions (*choices).

  • I remove the commented lines

    13    def test_factory_w_keyword_arguments(self):
    14        first_name = pick_one(
    15            'jane', 'joe', 'john', 'person',
    16        )
    17        last_name = pick_one(
    18            'doe', 'smith', 'blow', 'public',
    19        )
    20        sex = pick_one('F', 'M')
    
  • 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 (reality) and the dictionary for my_expectation in the test is - year_of_birth and age. One takes in a value for year_of_birth and the other uses year_of_birth to calculate the age, the other things are the same.

I can use a dictionary to remove the parts that are the same.


RED: make it fail


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

  • I add a dictionary for first_name, last_name and year_of_birth

    13    def test_factory_w_keyword_arguments(self):
    14        first_name = pick_one(
    15            'jane', 'joe', 'john', 'person',
    16        )
    17        last_name = pick_one(
    18            'doe', 'smith', 'blow', 'public',
    19        )
    20        sex = pick_one('F', 'M')
    21
    22        a_person = dict(
    23            first_name=first_name,
    24            last_name=last_name,
    25            sex=sex,
    26        )
    27
    28        this_year = datetime.datetime.now().year
    29        year_of_birth = random.randint(
    30            this_year-120, this_year
    31        )
    
  • I use the new variable to remove first_name, last_name, and sex from the call to src.person.factory

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

    the terminal is my friend, and shows TypeError

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

    because this happens

    first_name = pick_one('jane', 'joe', 'john', 'person',)
    last_name = pick_one('doe', 'smith', 'blow', 'public',)
    sex = pick_one('F', 'M')
    
    a_person = dict(
                   first_name=first_name,
                   last_name=last_name,
                   sex=sex,
               )
    
    reality = src.person.factory(
                  a_person,
                  year_of_birth
              )
                  src.person.factory(
                      first_name=a_person,
                      year_of_birth=year_of_birth
                  )
    

    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 expressions for the dictionary like I did in test_unknown_number_of_arguments to make it take the key-value pairs of the dictionary as keyword arguments

33          reality = src.person.factory(
34              # first_name=first_name,
35              # last_name=last_name,
36              # sex=sex,
37              # a_person,
38              **a_person,
39              year_of_birth=year_of_birth,
40          )
41          my_expectation = dict(
42              first_name=first_name,
43              last_name=last_name,
44              sex=sex,
45              age=this_year-year_of_birth,
46          )
47          self.assertEqual(reality, my_expectation)
48
49
50  # Exceptions seen

the test is green again, because this happens

first_name = pick_one('jane', 'joe', 'john', 'person',)
last_name = pick_one('doe', 'smith', 'blow', 'public',)
sex = pick_one('F', 'M')
a_person = dict(
               first_name=first_name,
               last_name=last_name,
               sex=sex,
           )
reality = src.person.factory(
              a_person,
              year_of_birth
          )
              src.person.factory(
                  **a_person,
                  year_of_birth
              )
              src.person.factory(
                  first_name=first_name,
                  last_name=last_name,
                  sex=sex,
                  year_of_birth=year_of_birth
              )

Python sends the dictionary as keyword arguments since I used a double starred expressions (**a_person).


REFACTOR: make it better


  • I use the variable for my_expectation

    33        reality = src.person.factory(
    34            # first_name=first_name,
    35            # last_name=last_name,
    36            # sex=sex,
    37            # a_person,
    38            **a_person,
    39            year_of_birth=year_of_birth,
    40        )
    41        my_expectation = dict(
    42            # first_name=first_name,
    43            # last_name=last_name,
    44            # sex=sex,
    45            a_person,
    46            age=this_year-year_of_birth,
    47        )
    48        self.assertEqual(reality, my_expectation)
    49
    50
    51# Exceptions seen
    

    the test is still green because this happens

    first_name = pick_one('jane', 'joe', 'john', 'person',)
    last_name = pick_one('doe', 'smith', 'blow', 'public',)
    sex = pick_one('F', 'M')
    
    a_person = dict(
                   first_name=first_name,
                   last_name=last_name,
                   sex=sex,
               )
    
    my_expectation = dict(
                         a_person,
                         age=this_year-year_of_birth
                     )
                         dict(
                             first_name=first_name,
                             last_name=last_name,
                             sex=sex,
                             age=this_year-year_of_birth
                         )
    

    the dict constructor can take another dictionary as input and uses the key-value pairs of the dictionary as keyword arguments.

  • 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 = pick_one(
    15        #     'jane', 'joe', 'john', 'person',
    16        # )
    17        # last_name = pick_one(
    18        #     'doe', 'smith', 'blow', 'public',
    19        # )
    20        # sex = pick_one('F', 'M')
    21
    22        a_person = dict(
    23            # first_name=first_name,
    24            # last_name=last_name,
    25            # sex=sex,
    26            first_name=pick_one(
    27                'jane', 'joe', 'john', 'person',
    28            ),
    29            last_name=pick_one(
    30                'doe', 'smith', 'blow', 'public',
    31            ),
    32            sex=pick_one('F', 'M'),
    33        )
    

    still green.

  • I add 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

    20    def test_factory_w_keyword_arguments(self):
    21        # first_name = pick_one(
    22        #     'jane', 'joe', 'john', 'person',
    23        # )
    24        # last_name = pick_one(
    25        #     'doe', 'smith', 'blow', 'public',
    26        # )
    27        # sex = pick_one('F', 'M')
    28
    29        a_person = dict(
    30            # first_name=first_name,
    31            # last_name=last_name,
    32            # sex=sex,
    33            # first_name=pick_one(
    34            #     'jane', 'joe', 'john', 'person',
    35            # ),
    36            # last_name=pick_one(
    37            #     'doe', 'smith', 'blow', 'public',
    38            # ),
    39            first_name=get_random_name(),
    40            last_name=get_random_name(),
    41            sex=pick_one('F', 'M'),
    42        )
    

    green.

  • I change the assertion to make sure the test works correctly

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

    the terminal is my friend, and shows AssertionError

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

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

  • I use ctrl/command+s (Windows & Linux/MacOS) to run the test a few times and I get random values for 'first_name', 'last_name', 'sex' and 'age' each time

  • I change the assertion back

    49        reality = src.person.factory(
    50            # first_name=first_name,
    51            # last_name=last_name,
    52            # sex=sex,
    53            # a_person,
    54            **a_person,
    55            year_of_birth=year_of_birth,
    56        )
    57        my_expectation = dict(
    58            # first_name=first_name,
    59            # last_name=last_name,
    60            # sex=sex,
    61            a_person,
    62            age=this_year-year_of_birth,
    63        )
    64        self.assertEqual(reality, my_expectation)
    65        # self.assertEqual(reality, {})
    66
    67
    68# Exceptions seen
    
  • I remove the commented lines

    20    def test_factory_w_keyword_arguments(self):
    21        a_person = dict(
    22            first_name=get_random_name(),
    23            last_name=get_random_name(),
    24            sex=pick_one('F', 'M'),
    25        )
    26
    27        this_year = datetime.datetime.now().year
    28        year_of_birth = random.randint(
    29            this_year-120, this_year
    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 where the tests are running

  • 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        a_person = dict(
    44            first_name=get_random_name(),
    45            # last_name=get_random_name(),
    46            sex=pick_one('F', 'M'),
    47        )
    

    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 last_name a choice, not a requirement.


GREEN: make it pass


  • I add a default value for last_name to make it optional, in the factory function in person.py

    4def factory(
    5        # first_name, last_name,
    6        first_name, last_name=None,
    7        sex, year_of_birth,
    8    ):
    

    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 SyntaxError to the list of Exceptions seen in test_person.py

    65# Exceptions seen
    66# AssertionError
    67# NameError
    68# AttributeError
    69# TypeError
    70# SyntaxError
    
  • I add a default value for sex to make it optional, in the factory function in person.py

    4def factory(
    5        # first_name, last_name,
    6        first_name, last_name=None,
    7        # sex, year_of_birth,
    8        sex=None, year_of_birth,
    9    ):
    

    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 year_of_birth to make it optional

     4def factory(
     5        # first_name, last_name,
     6        first_name, last_name=None,
     7        # sex, year_of_birth,
     8        # sex=None, year_of_birth,
     9        sex=None, year_of_birth=None,
    10    ):
    

    the terminal is my friend, and shows AssertionError

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

    because this happens when the factory function is called without a value for last_name

    a_person = dict(
                   first_name=get_random_name(),
                   sex=pick_one('F', 'M'),
               )
    
    reality = src.person.factory(
                  **a_person,
                  year_of_birth=year_of_birth,
              )
                  src.person.factory(
                      first_name=get_random_name(),
                      sex=pick_one('F', 'M'),
                      year_of_birth=year_of_birth,
                  )
                  src.person.factory(
                      first_name=get_random_name(),
                      sex=pick_one('F', 'M'),
                      last_name=None, # use the default value
                      year_of_birth=year_of_birth,
                  )
              # the factory function returns
              {'first_name': Z, 'last_name': None, 'sex': Y,
                'age': X}
    
    my_expectation = dict(
                         a_person,
                         age=this_year-year_of_birth,
                     )
                         dict(
                             first_name=get_random_name(),
                             sex=pick_one('F', 'M'),
                             year_of_birth=year_of_birth,
                         )
                     # the dict constructor returns
                     {'first_name': Z, 'sex': Y, 'age': X}
    
  • I add a key-value pair for last_name to my_expectation in test_factory_w_optional_arguments in test_person.py

    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            age=this_year-year_of_birth,
    62        )
    63        self.assertEqual(reality, my_expectation)
    64
    65
    66# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

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

    because the factory function returns a dictionary with a value of None for last_name and the assertion expects 'doe'.

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

     4def factory(
     5        # first_name, last_name,
     6        # first_name, last_name=None,
     7        # sex, year_of_birth,
     8        # sex=None, year_of_birth,
     9        first_name, last_name='doe',
    10        sex=None, year_of_birth=None,
    11    ):
    

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

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

    is the same as

    src.person.factory(
        first_name=first_name,
        sex=sex,
        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.


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': Y, 'last_name': 'doe,
         'sex': None, 'age': X}
     != {'first_name': Y, 'last_name': 'doe', 'age': X}
    

    because this happens when the factory function is called without a value for sex

    a_person = dict(first_name=get_random_name())
    
    reality = src.person.factory(
                  **a_person,
                  year_of_birth=year_of_birth,
              )
                  src.person.factory(
                      first_name=get_random_name(),
                      year_of_birth=year_of_birth,
                  )
                  src.person.factory(
                      first_name=get_random_name(),
                      sex=None,        # use the default value
                      last_name='doe', # use the default value
                      year_of_birth=year_of_birth,
                  )
              # the factory function returns
              {'first_name': Y, 'last_name': 'doe,
               'sex': None, 'age': X}
    
    my_expectation = dict(
                         a_person,
                         last_name='doe',
                         age=this_year-year_of_birth,
                     )
                         dict(
                             first_name=get_random_name(),
                             last_name='doe',
                             age=this_year-year_of_birth,
                         )
                     # the dict constructor returns
                     {'first_name': Y, 'last_name': 'doe', 'age': X}
    
  • 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,
     6        # first_name, last_name=None,
     7        # sex, year_of_birth,
     8        # sex=None, year_of_birth,
     9        first_name, last_name='doe',
    10        # sex=None, year_of_birth=None,
    11        sex='M', year_of_birth=None,
    12    ):
    

    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        a_person = dict(
    45            first_name=get_random_name(),
    46            # last_name=get_random_name(),
    47            # sex=pick_one('F', 'M'),
    48        )
    
  • I use the variable for first_name in the call to src.person.factory

    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            last_name='doe',
    63            sex='M',
    64            age=this_year-year_of_birth,
    65        )
    66        self.assertEqual(reality, my_expectation)
    67
    68
    69# Exceptions seen
    

    the terminal is my friend, and shows TypeError

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

    because this happens

    first_name = get_random_name()
    a_person = dict(first_name=get_random_name())
    
    reality = src.person.factory(
                  **a_person,
                  first_name=first_name,
                  year_of_birth=year_of_birth,
              )
                  src.person.factory(
                      first_name=get_random_nam(),
                      first_name=first_name, # repeated keyword
                      year_of_birth=year_of_birth
                  )
    

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

  • I comment out **a_person,

    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            last_name='doe',
    63            sex='M',
    64            age=this_year-year_of_birth,
    65        )
    66        self.assertEqual(reality, my_expectation)
    67
    68
    69# Exceptions seen
    

    I use ctrl/command+s (Windows & Linux/MacOS) to run the test a few times

    • if the values for first_name match, the test passes

    • if the values for first_name are not the same, the terminal shows AssertionError

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

      where Y and Z are the random first names, and X is the random age

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

    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 test passes.

  • 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
    45        this_year = datetime.datetime.now().year
    46        year_of_birth = random.randint(
    47            this_year-120, this_year
    48        )
    49
    50        reality = src.person.factory(
    51            first_name=first_name,
    52            year_of_birth=year_of_birth,
    53        )
    54        my_expectation = dict(
    55            first_name=first_name,
    56            last_name='doe',
    57            sex='M',
    58            age=this_year-year_of_birth,
    59        )
    60        self.assertEqual(reality, my_expectation)
    61
    62
    63# Exceptions seen
    

    the test is still green.

  • I remove the commented lines from 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    }
    
  • 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

    50        reality = src.person.factory(
    51            first_name=first_name,
    52            year_of_birth=year_of_birth,
    53        )
    54        my_expectation = dict(
    55            first_name=first_name,
    56            last_name='doe',
    57            sex='M',
    58            age=this_year-year_of_birth,
    59        )
    60        self.assertEqual(reality, my_expectation)
    61
    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        self.assertEqual(reality, my_expectation)
    72
    73
    74# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.person'
                    has no attribute 'say_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: say_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

    19# def say_hello():
    20def say_hello(person):
    21    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

    19# def say_hello():
    20def say_hello(person):
    21    # return None
    22    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

    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        jane = src.person.factory(
    78            first_name='jane',
    79            sex='F',
    80            year_of_birth=1991,
    81        )
    82
    83        reality = src.person.say_hello(jane)
    84        my_expectation = (
    85            'Hi, my name is jane doe and I am'
    86            f' {datetime.datetime.now().year-1991}'
    87        )
    88        self.assertEqual(reality, my_expectation)
    89
    90
    91# 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

    19# def say_hello():
    20def say_hello(person):
    21    first_name = person.get('first_name')
    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 values for first_name are the same because

    • this happens when jane = src.person.factory(first_name='jane', sex='F', year_of_birth=1991) runs

      jane = src.person.factory(
                 first_name='jane',
                 sex='F',
                 year_of_birth=1991,
             )
                 src.person.factory(
                     first_name='jane',
                     last_name='doe', # use the default value
                     year_of_birth=1991,
                     sex='F'
                 )
             # the factory function returns
             {'first_name': 'jane', 'last_name': 'doe',
              'sex': 'F', 'age': 35}
      
    • this happens in say_hello when src.person.say_hello(jane) runs

      first_name = person.get('first_name')
                     jane.get('first_name')
                     {
                         'first_name': 'jane',
                         'last_name': 'doe',
                         'sex': 'F', 'age': 35
                     }.get('first_name')
                   # the get method returns
                   'jane'
      
      return (
          f'Hi, my name is {first_name}'
          ' blow and I am 30'
      )
      
    src.person.say_hello(jane)
    # the say_hello function returns
    'Hi, my name is jane blow and I am 30'
    

    which raises AssertionError since the values for last_name and age 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

    19# def say_hello():
    20def say_hello(person):
    21    first_name = person.get('first_name')
    22    last_name = person.get('last_name')
    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 values for last_name are the same because

    • this happens when jane = src.person.factory(first_name='jane', sex='F', year_of_birth=1991) runs

      jane = src.person.factory(
                 first_name='jane',
                 sex='F',
                 year_of_birth=1991,
             )
                 src.person.factory(
                     first_name='jane',
                     last_name='doe', # use the default value
                     year_of_birth=1991,
                     sex='F'
                 )
             # the factory function returns
             {'first_name': 'jane', 'last_name': 'doe',
              'sex': 'F', 'age': 35}
      
    • this happens in say_hello when src.person.say_hello(jane) runs

      first_name = person.get('first_name')
                   # the get method returns
                   'jane'
      
      last_name = person.get('last_name')
                    jane.get('last_name')
                    {
                        'first_name': 'jane',
                        'last_name': 'doe',
                        'sex': 'F', 'age': 35
                    }.get('last_name')
                  # the get method returns
                  'doe'
      
      return (
          f'Hi, my name is {first_name} {last_name}'
          ' and I am 30'
      )
      
    src.person.say_hello(jane)
    # the say_hello function returns
    'Hi, my name is jane doe and I am 30'
    

    which raises AssertionError since the values for age are different.

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

    19# def say_hello():
    20def say_hello(person):
    21    first_name = person.get('first_name')
    22    last_name = person.get('last_name')
    23    age = person.get('age')
    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 because

    • this happens when jane = src.person.factory(first_name='jane', sex='F', year_of_birth=1991) runs

      jane = src.person.factory(
                 first_name='jane',
                 sex='F',
                 year_of_birth=1991,
             )
                 src.person.factory(
                     first_name='jane',
                     last_name='doe', # use the default value
                     year_of_birth=1991,
                     sex='F'
                 )
             # the factory function returns
             {'first_name': 'jane', 'last_name': 'doe',
              'sex': 'F', 'age': 35}
      
    • this happens in say_hello when src.person.say_hello(jane) runs

      first_name = person.get('first_name')
                   # the get method returns
                   'jane'
      last_name  = person.get('last_name')
                   # the get method returns
                   'doe'
      
      age = person.get('last_name')
              jane.get('last_name')
              {
                  'first_name': 'jane',
                  'last_name': 'doe',
                  'sex': 'F', 'age': 35
              }.get('age')
            # the get method returns
            35
      
      return (
          f'Hi, my name is {first_name} {last_name}'
          f' and I am {age}'
      )
      
    src.person.say_hello(jane)
    # the say_hello function returns
    'Hi, my name is jane doe and I am 35'
    
  • 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

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

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

    the test passes.

  • I add an assertion for one more person

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

    the terminal is my friend, and shows AssertionError

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

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

    the test passes.

  • I add a variable for datetime.datetime.now().year

    62    def test_factory_person_says_hello(self):
    63        this_year = datetime.datetime.now().year
    64
    65        joe = src.person.factory(
    66            first_name='joe',
    67            last_name='blow',
    68            year_of_birth=1996,
    69        )
    
  • I use the variable to remove repetition of datetime.datetime.now().year from the test

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

    the test is still green.

  • I add variables for 'mary', 'public', 2000 and the age calculation of mary

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

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

    still green.

  • I add variables for 'john', 'smith', 1580 and the age calculation of john

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

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

    green.

  • I add variables for 'jane', 1991 and the age calculation of jane

    71        reality = src.person.say_hello(joe)
    72        # my_expectation = None
    73        my_expectation = (
    74            'Hi, my name is joe blow and I am'
    75            # f' {datetime.datetime.now().year-1996}'
    76            f' {this_year-1996}'
    77        )
    78        self.assertEqual(reality, my_expectation)
    79
    80        first_name = 'jane'
    81        year_of_birth = 1991
    82        age = this_year - year_of_birth
    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

     80        first_name = 'jane'
     81        year_of_birth = 1991
     82        age = this_year - year_of_birth
     83
     84        jane = src.person.factory(
     85            # first_name='jane',
     86            first_name=first_name,
     87            sex='F',
     88            # year_of_birth=1991,
     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' {this_year-1991}'
     97            f'Hi, my name is {first_name}'
     98            f' doe and I am {age}'
     99        )
    100        self.assertEqual(reality, my_expectation)
    

    still green.

  • I add variables for 'joe', 'blow', 1996 and the age calculation for joe

    62    def test_factory_person_says_hello(self):
    63        this_year = datetime.datetime.now().year
    64
    65        first_name = 'joe'
    66        last_name = 'blow'
    67        year_of_birth = 1996
    68        age = this_year - year_of_birth
    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

    62    def test_factory_person_says_hello(self):
    63        this_year = datetime.datetime.now().year
    64
    65        first_name = 'joe'
    66        last_name = 'blow'
    67        year_of_birth = 1996
    68        age = this_year - year_of_birth
    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' {this_year-1996}'
    85            f'Hi, my name is {first_name}'
    86            f' {last_name} and I am {age}'
    87        )
    88        self.assertEqual(reality, my_expectation)
    

    the test is still green.

  • I remove the commented lines

     62    def test_factory_person_says_hello(self):
     63        this_year = datetime.datetime.now().year
     64
     65        first_name = 'joe'
     66        last_name = 'blow'
     67        year_of_birth = 1996
     68        age = this_year - year_of_birth
     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}'
     79            f' {last_name} and I am {age}'
     80        )
     81        self.assertEqual(reality, my_expectation)
     82
     83        first_name = 'jane'
     84        year_of_birth = 1991
     85        age = this_year - year_of_birth
     86
     87        jane = src.person.factory(
     88            first_name=first_name,
     89            sex='F',
     90            year_of_birth=year_of_birth
     91        )
     92
     93        reality = src.person.say_hello(jane)
     94        my_expectation = (
     95            f'Hi, my name is {first_name}'
     96            f' doe and I am {age}'
     97        )
     98        self.assertEqual(reality, my_expectation)
     99
    100        first_name = 'john'
    101        last_name = 'smith'
    102        year_of_birth = 1580
    103        age = this_year - year_of_birth
    104
    105        john = src.person.factory(
    106            first_name=first_name,
    107            last_name=last_name,
    108            year_of_birth=year_of_birth,
    109        )
    110
    111        reality = src.person.say_hello(john)
    112        my_expectation = (
    113            f'Hi, my name is {first_name}'
    114            f' {last_name} and I am {age}'
    115        )
    116        self.assertEqual(reality, my_expectation)
    117
    118        first_name = 'mary'
    119        last_name = 'public'
    120        year_of_birth = 2000
    121        age = this_year - year_of_birth
    122
    123        mary = src.person.factory(
    124            first_name=first_name,
    125            last_name=last_name,
    126            year_of_birth=year_of_birth,
    127            sex='F',
    128        )
    129
    130        reality = src.person.say_hello(mary)
    131        my_expectation = (
    132            f'Hi, my name is {first_name}'
    133            f' {last_name} and I am {age}'
    134        )
    135        self.assertEqual(reality, my_expectation)
    136
    137
    138# 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 get_current_year function

Each test has uses datetime.datetime.now().year to get the current year.

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

  • I add a function that returns the value of the current year

    11def get_random_name():
    12    return pick_one(
    13        'jane', 'joe', 'john', 'person',
    14        'doe', 'smith', 'blow', 'public',
    15    )
    16
    17
    18def get_current_year():
    19    return datetime.datetime.now().year
    20
    21
    22class TestPerson(unittest.TestCase):
    23
    24    def test_factory_w_keyword_arguments(self):
    
  • I use the new function for this_year in test_factory_w_keyword_arguments

    24    def test_factory_w_keyword_arguments(self):
    25        a_person = dict(
    26            first_name=get_random_name(),
    27            last_name=get_random_name(),
    28            sex=pick_one('F', 'M'),
    29        )
    30
    31        # this_year = datetime.datetime.now().year
    32        this_year = get_current_year()
    33        year_of_birth = random.randint(
    34            this_year-120, this_year
    35        )
    

    the test is still green.

  • I use the function for this_year in test_factory_w_optional_arguments

    47    def test_factory_w_optional_arguments(self):
    48        first_name = get_random_name()
    49
    50        # this_year = datetime.datetime.now().year
    51        this_year = get_current_year()
    52        year_of_birth = random.randint(
    53            this_year-120, this_year
    54        )
    

    still green.

  • I use the function for this_year in test_factory_person_says_hello

    68    def test_factory_person_says_hello(self):
    69        # this_year = datetime.datetime.now().year
    70        this_year = get_current_year()
    71
    72        first_name = 'joe'
    73        last_name = 'blow'
    74        year_of_birth = 1996
    75        age = this_year - year_of_birth
    

    green.

  • I add a git commit message in the other terminal

    git commit -am \
    'extract get_current_year function'
    

    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 where the tests are running

  • I add a function to calculate age

    18def get_current_year():
    19    return datetime.datetime.now().year
    20
    21
    22def calculate_age(year_of_birth):
    23    return (
    24        get_current_year()
    25      - year_of_birth
    26    )
    27
    28
    29class TestPerson(unittest.TestCase):
    30
    31    def test_factory_w_keyword_arguments(self):
    
  • I add a call to the function for the value of age in my_expectation in test_factory_w_keyword_arguments

    44        reality = src.person.factory(
    45            **a_person,
    46            year_of_birth=year_of_birth,
    47        )
    48        my_expectation = dict(
    49            a_person,
    50            # age=this_year-year_of_birth,
    51            age=calculate_age(year_of_birth),
    52        )
    53        self.assertEqual(reality, my_expectation)
    54
    55    def test_factory_w_optional_arguments(self):
    

    the test is still green.

  • I remove the commented lines

    31    def test_factory_w_keyword_arguments(self):
    32        a_person = dict(
    33            first_name=get_random_name(),
    34            last_name=get_random_name(),
    35            sex=pick_one('F', 'M'),
    36        )
    37
    38        this_year = get_current_year()
    39        year_of_birth = random.randint(
    40            this_year-120, this_year
    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 add a call to the function for the value of age in my_expectation in test_factory_w_optional_arguments

    62        reality = src.person.factory(
    63            first_name=first_name,
    64            year_of_birth=year_of_birth,
    65        )
    66        my_expectation = dict(
    67            first_name=first_name,
    68            last_name='doe',
    69            sex='M',
    70            # age=this_year-year_of_birth,
    71            age=calculate_age(year_of_birth),
    72        )
    73        self.assertEqual(reality, my_expectation)
    74
    75    def test_factory_person_says_hello(self):
    

    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
    56        this_year = get_current_year()
    57        year_of_birth = random.randint(
    58            this_year-120, this_year
    59        )
    60
    61        reality = src.person.factory(
    62            first_name=first_name,
    63            year_of_birth=year_of_birth,
    64        )
    65        my_expectation = dict(
    66            first_name=first_name,
    67            last_name='doe',
    68            sex='M',
    69            age=calculate_age(year_of_birth),
    70        )
    71        self.assertEqual(reality, my_expectation)
    72
    73    def test_factory_person_says_hello(self):
    
  • I add a call to the function for the value of age for joe in test_factory_person_says_hello

    73    def test_factory_person_says_hello(self):
    74        # this_year = datetime.datetime.now().year
    75        this_year = get_current_year()
    76
    77        first_name = 'joe'
    78        last_name = 'blow'
    79        year_of_birth = 1996
    80        # age = this_year - year_of_birth
    81        age = calculate_age(year_of_birth)
    82
    83        joe = src.person.factory(
    84            first_name=first_name,
    85            last_name=last_name,
    86            year_of_birth=year_of_birth,
    87        )
    88
    89        reality = src.person.say_hello(joe)
    90        my_expectation = (
    91            f'Hi, my name is {first_name}'
    92            f' {last_name} and I am {age}'
    93        )
    94        self.assertEqual(reality, my_expectation)
    95
    96        first_name = 'jane'
    97        year_of_birth = 1991
    98        age = this_year - year_of_birth
    

    the test is still green.

  • I add a call to the function for the value of age for jane

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

    still green.

  • I add a call to the function for the value of age for john

    114        first_name = 'john'
    115        last_name = 'smith'
    116        year_of_birth = 1580
    117        # age = this_year - year_of_birth
    118        age = calculate_age(year_of_birth)
    119
    120        john = src.person.factory(
    121            first_name=first_name,
    122            last_name=last_name,
    123            year_of_birth=year_of_birth,
    124        )
    125
    126        reality = src.person.say_hello(john)
    127        my_expectation = (
    128            f'Hi, my name is {first_name}'
    129            f' {last_name} and I am {age}'
    130        )
    131        self.assertEqual(reality, my_expectation)
    132
    133        first_name = 'mary'
    134        last_name = 'public'
    135        year_of_birth = 2000
    136        age = this_year - year_of_birth
    

    green.

  • I add a call to the function for the value of age for mary

    133        first_name = 'mary'
    134        last_name = 'public'
    135        year_of_birth = 2000
    136        # age = this_year - year_of_birth
    137        age = calculate_age(year_of_birth)
    

    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 year_of_birth

I want to use random values for year_of_birth in the tests

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

  • I use a call to random.randint to get a random value for the year_of_birth of joe

     73    def test_factory_person_says_hello(self):
     74        # this_year = datetime.datetime.now().year
     75        this_year = get_current_year()
     76
     77        first_name = 'joe'
     78        last_name = 'blow'
     79        # year_of_birth = 1996
     80        year_of_birth = random.randint(
     81            this_year-120, this_year
     82        )
     83        # age = this_year - year_of_birth
     84        age = calculate_age(year_of_birth)
     85
     86        joe = src.person.factory(
     87            first_name=first_name,
     88            last_name=last_name,
     89            year_of_birth=year_of_birth,
     90        )
     91
     92        reality = src.person.say_hello(joe)
     93        my_expectation = (
     94            f'Hi, my name is {first_name}'
     95            f' {last_name} and I am {age}'
     96        )
     97        self.assertEqual(reality, my_expectation)
     98
     99        first_name = 'jane'
    100        year_of_birth = 1991
    101        # age = this_year - year_of_birth
    102        age = calculate_age(year_of_birth)
    

    the test is still green.

  • I use a call to random.randint to get a random value for the year_of_birth of jane

     99        first_name = 'jane'
    100        # year_of_birth = 1991
    101        year_of_birth = random.randint(
    102            this_year-120, this_year
    103        )
    104        # age = this_year - year_of_birth
    105        age = calculate_age(year_of_birth)
    106
    107        jane = src.person.factory(
    108            first_name=first_name,
    109            sex='F',
    110            year_of_birth=year_of_birth
    111        )
    112
    113        reality = src.person.say_hello(jane)
    114        my_expectation = (
    115            f'Hi, my name is {first_name}'
    116            f' doe and I am {age}'
    117        )
    118        self.assertEqual(reality, my_expectation)
    119
    120        first_name = 'john'
    121        last_name = 'smith'
    122        year_of_birth = 1580
    123        # age = this_year - year_of_birth
    124        age = calculate_age(year_of_birth)
    

    still green.

  • I use a call to random.randint to get a random value for the year_of_birth of john

    120        first_name = 'john'
    121        last_name = 'smith'
    122        # year_of_birth = 1580
    123        year_of_birth = random.randint(
    124            this_year-120, this_year
    125        )
    126        # age = this_year - year_of_birth
    127        age = calculate_age(year_of_birth)
    128
    129        john = src.person.factory(
    130            first_name=first_name,
    131            last_name=last_name,
    132            year_of_birth=year_of_birth,
    133        )
    134
    135        reality = src.person.say_hello(john)
    136        my_expectation = (
    137            f'Hi, my name is {first_name}'
    138            f' {last_name} and I am {age}'
    139        )
    140        self.assertEqual(reality, my_expectation)
    141
    142        first_name = 'mary'
    143        last_name = 'public'
    144        year_of_birth = 2000
    145        # age = this_year - year_of_birth
    146        age = calculate_age(year_of_birth)
    

    green.

  • I use a call to random.randint to get a random value for the year_of_birth of mary

    142        first_name = 'mary'
    143        last_name = 'public'
    144        # year_of_birth = 2000
    145        year_of_birth = random.randint(
    146            this_year-120, this_year
    147        )
    148        # age = this_year - year_of_birth
    149        age = calculate_age(year_of_birth)
    150
    151        mary = src.person.factory(
    152            first_name=first_name,
    153            last_name=last_name,
    154            year_of_birth=year_of_birth,
    155            sex='F',
    156        )
    157
    158        reality = src.person.say_hello(mary)
    159        my_expectation = (
    160            f'Hi, my name is {first_name}'
    161            f' {last_name} and I am {age}'
    162        )
    163        self.assertEqual(reality, my_expectation)
    164
    165
    166# Exceptions seen
    

    still green.

  • I add a git commit message in the other terminal

    git commit -am \
    'test_factory_person_says_hello with random year_of_birth'
    

    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 where the tests are running

  • 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

    22def calculate_age(year_of_birth):
    23    return (
    24        get_current_year()
    25      - year_of_birth
    26    )
    27
    28
    29def get_random_year_of_birth():
    30    this_year = get_current_year()
    31    return random.randint(
    32        this_year-120, this_year
    33    )
    34
    35
    36class TestPerson(unittest.TestCase):
    37
    38    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

    38    def test_factory_w_keyword_arguments(self):
    39        a_person = dict(
    40            first_name=get_random_name(),
    41            last_name=get_random_name(),
    42            sex=pick_one('F', 'M'),
    43        )
    44
    45        # this_year = get_current_year()
    46        # year_of_birth = random.randint(
    47        #     this_year-120, this_year
    48        # )
    49        year_of_birth = get_random_year_of_birth()
    

    the test is still green.

  • I remove the commented lines

    38    def test_factory_w_keyword_arguments(self):
    39        a_person = dict(
    40            first_name=get_random_name(),
    41            last_name=get_random_name(),
    42            sex=pick_one('F', 'M'),
    43        )
    44        year_of_birth = get_random_year_of_birth()
    45
    46        reality = src.person.factory(
    47            **a_person,
    48            year_of_birth=year_of_birth,
    49        )
    50        my_expectation = dict(
    51            a_person,
    52            age=calculate_age(year_of_birth),
    53        )
    54        self.assertEqual(reality, my_expectation)
    55
    56    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

    56    def test_factory_w_optional_arguments(self):
    57        first_name = get_random_name()
    58
    59        # this_year = get_current_year()
    60        # year_of_birth = random.randint(
    61        #     this_year-120, this_year
    62        # )
    63        year_of_birth = get_random_year_of_birth()
    

    the test is still green.

  • I remove the commented lines

    56    def test_factory_w_optional_arguments(self):
    57        first_name = get_random_name()
    58        year_of_birth = get_random_year_of_birth()
    59
    60        reality = src.person.factory(
    61            first_name=first_name,
    62            year_of_birth=year_of_birth,
    63        )
    64        my_expectation = dict(
    65            first_name=first_name,
    66            last_name='doe',
    67            sex='M',
    68            age=calculate_age(year_of_birth),
    69        )
    70        self.assertEqual(reality, my_expectation)
    71
    72    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 for joe in test_factory_person_says_hello

     72    def test_factory_person_says_hello(self):
     73        # this_year = datetime.datetime.now().year
     74        this_year = get_current_year()
     75
     76        first_name = 'joe'
     77        last_name = 'blow'
     78        # year_of_birth = 1996
     79        # year_of_birth = random.randint(
     80        #     this_year-120, this_year
     81        # )
     82        # age = this_year - year_of_birth
     83        year_of_birth = get_random_year_of_birth()
     84        age = calculate_age(year_of_birth)
     85
     86        joe = src.person.factory(
     87            first_name=first_name,
     88            last_name=last_name,
     89            year_of_birth=year_of_birth,
     90        )
     91
     92        reality = src.person.say_hello(joe)
     93        my_expectation = (
     94            f'Hi, my name is {first_name}'
     95            f' {last_name} and I am {age}'
     96        )
     97        self.assertEqual(reality, my_expectation)
     98
     99        first_name = 'jane'
    100        # year_of_birth = 1991
    101        year_of_birth = random.randint(
    102            this_year-120, this_year
    103        )
    104        # age = this_year - year_of_birth
    105        age = calculate_age(year_of_birth)
    

    the test is still green.

  • 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 for jane

     99        first_name = 'jane'
    100        # year_of_birth = 1991
    101        # year_of_birth = random.randint(
    102        #     this_year-120, this_year
    103        # )
    104        # age = this_year - year_of_birth
    105        year_of_birth = get_random_year_of_birth()
    106        age = calculate_age(year_of_birth)
    107
    108        jane = src.person.factory(
    109            first_name=first_name,
    110            sex='F',
    111            year_of_birth=year_of_birth
    112        )
    113
    114        reality = src.person.say_hello(jane)
    115        my_expectation = (
    116            f'Hi, my name is {first_name}'
    117            f' doe and I am {age}'
    118        )
    119        self.assertEqual(reality, my_expectation)
    120
    121        first_name = 'john'
    122        last_name = 'smith'
    123        # year_of_birth = 1580
    124        year_of_birth = random.randint(
    125            this_year-120, this_year
    126        )
    127        # age = this_year - year_of_birth
    128        age = calculate_age(year_of_birth)
    

    still green.

  • 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 for john

    121        first_name = 'john'
    122        last_name = 'smith'
    123        # year_of_birth = 1580
    124        # year_of_birth = random.randint(
    125        #     this_year-120, this_year
    126        # )
    127        # age = this_year - year_of_birth
    128        year_of_birth = get_random_year_of_birth()
    129        age = calculate_age(year_of_birth)
    130
    131        john = src.person.factory(
    132            first_name=first_name,
    133            last_name=last_name,
    134            year_of_birth=year_of_birth,
    135        )
    136
    137        reality = src.person.say_hello(john)
    138        my_expectation = (
    139            f'Hi, my name is {first_name}'
    140            f' {last_name} and I am {age}'
    141        )
    142        self.assertEqual(reality, my_expectation)
    143
    144        first_name = 'mary'
    145        last_name = 'public'
    146        # year_of_birth = 2000
    147        year_of_birth = random.randint(
    148            this_year-120, this_year
    149        )
    150        # age = this_year - year_of_birth
    151        age = calculate_age(year_of_birth)
    

    green.

  • 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 for mary

    144        first_name = 'mary'
    145        last_name = 'public'
    146        # year_of_birth = 2000
    147        # year_of_birth = random.randint(
    148        #     this_year-120, this_year
    149        # )
    150        # age = this_year - year_of_birth
    151        year_of_birth = get_random_year_of_birth()
    152        age = calculate_age(year_of_birth)
    

    still green.

  • I remove the commented lines and this_year variable since it is no longer used

     72    def test_factory_person_says_hello(self):
     73        first_name = 'joe'
     74        last_name = 'blow'
     75        year_of_birth = get_random_year_of_birth()
     76        age = calculate_age(year_of_birth)
     77
     78        joe = src.person.factory(
     79            first_name=first_name,
     80            last_name=last_name,
     81            year_of_birth=year_of_birth,
     82        )
     83
     84        reality = src.person.say_hello(joe)
     85        my_expectation = (
     86            f'Hi, my name is {first_name}'
     87            f' {last_name} and I am {age}'
     88        )
     89        self.assertEqual(reality, my_expectation)
     90
     91        first_name = 'jane'
     92        year_of_birth = get_random_year_of_birth()
     93        age = calculate_age(year_of_birth)
     94
     95        jane = src.person.factory(
     96            first_name=first_name,
     97            sex='F',
     98            year_of_birth=year_of_birth
     99        )
    100
    101        reality = src.person.say_hello(jane)
    102        my_expectation = (
    103            f'Hi, my name is {first_name}'
    104            f' doe and I am {age}'
    105        )
    106        self.assertEqual(reality, my_expectation)
    107
    108        first_name = 'john'
    109        last_name = 'smith'
    110        year_of_birth = get_random_year_of_birth()
    111        age = calculate_age(year_of_birth)
    112
    113        john = src.person.factory(
    114            first_name=first_name,
    115            last_name=last_name,
    116            year_of_birth=year_of_birth,
    117        )
    118
    119        reality = src.person.say_hello(john)
    120        my_expectation = (
    121            f'Hi, my name is {first_name}'
    122            f' {last_name} and I am {age}'
    123        )
    124        self.assertEqual(reality, my_expectation)
    125
    126        first_name = 'mary'
    127        last_name = 'public'
    128        year_of_birth = get_random_year_of_birth()
    129        age = calculate_age(year_of_birth)
    130
    131        mary = src.person.factory(
    132            first_name=first_name,
    133            last_name=last_name,
    134            year_of_birth=year_of_birth,
    135            sex='F',
    136        )
    137
    138        reality = src.person.say_hello(mary)
    139        my_expectation = (
    140            f'Hi, my name is {first_name}'
    141            f' {last_name} and I am {age}'
    142        )
    143        self.assertEqual(reality, my_expectation)
    144
    145
    146# Exceptions seen
    147# AssertionError
    148# NameError
    149# AttributeError
    150# TypeError
    151# 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 where the tests are running

  • 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.

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


GREEN: make it pass


  • I add the name

    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/command+s (Windows & Linux/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/command+s (Windows & Linux/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/command+s (Windows & Linux/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.