how to make a person with a class


The factory and say_hi functions use three of the same inputs

  • first_name

  • last_name

  • year_of_birth

I want to give those values once, and get a representation for a person. I can do that with a class

what is a class?

I think of classes_ as attributes (variables) and methods (functions) that belong together.

Everything in Python is an object_, which is another word for a class_, it means that everything in Python has a class_ definition somewhere. Knowing how classes_ work and how to make them shows how everything in Python works or at least that they are attributes (variables) and methods (functions) that belong together.


preview

I have these tests by the end of the chapter

  1import src.person
  2
  3
  4def test_joe():
  5    first_name = 'joe'
  6    last_name = 'blow'
  7    sex = 'M'
  8    year_of_birth = 1996
  9
 10    reality = src.person.factory(
 11        first_name=first_name,
 12        last_name=last_name,
 13        sex=sex,
 14        year_of_birth=year_of_birth,
 15    )
 16    my_expectation = (
 17        f'{first_name}, {last_name},'
 18        f' {sex}, {year_of_birth}'
 19    )
 20    assert reality == my_expectation
 21
 22    reality = src.person.say_hi(
 23        first_name=first_name,
 24        last_name=last_name,
 25        year_of_birth=year_of_birth,
 26    )
 27    my_expectation = (
 28        f'Hi, my name is {first_name}'
 29        f' {last_name} and I am'
 30        f' {2026-year_of_birth}.'
 31    )
 32    assert reality == my_expectation
 33
 34
 35def test_jane():
 36    first_name = 'jane'
 37    last_name = 'doe'
 38    sex = 'F'
 39    year_of_birth = 1991
 40
 41    reality = src.person.factory(
 42        first_name=first_name,
 43        last_name=last_name,
 44        sex=sex,
 45        year_of_birth=year_of_birth,
 46    )
 47    my_expectation = (
 48        f'{first_name}, {last_name},'
 49        f' {sex}, {year_of_birth}'
 50    )
 51    assert reality == my_expectation
 52
 53    reality = src.person.say_hi(
 54        first_name=first_name,
 55        last_name=last_name,
 56        year_of_birth=year_of_birth,
 57    )
 58    my_expectation = (
 59        f'Hi, my name is {first_name}'
 60        f' {last_name} and I am'
 61        f' {2026-year_of_birth}.'
 62    )
 63    assert reality == my_expectation
 64
 65
 66def test_john():
 67    first_name = 'john'
 68    last_name = 'smith'
 69    sex = 'M'
 70    year_of_birth = 1580
 71
 72    reality = src.person.factory(
 73        first_name=first_name,
 74        last_name=last_name,
 75        sex=sex,
 76        year_of_birth=year_of_birth,
 77    )
 78    my_expectation = (
 79        f'{first_name}, {last_name},'
 80        f' {sex}, {year_of_birth}'
 81    )
 82    assert reality == my_expectation
 83
 84    reality = src.person.say_hi(
 85        first_name=first_name,
 86        last_name=last_name,
 87        year_of_birth=year_of_birth,
 88    )
 89    my_expectation = (
 90        f'Hi, my name is {first_name}'
 91        f' {last_name} and I am'
 92        f' {2026-year_of_birth}.'
 93    )
 94    assert reality == my_expectation
 95
 96
 97def test_mary():
 98    first_name = 'mary'
 99    last_name = 'public'
100    sex = 'F'
101    year_of_birth = 2000
102
103    reality = src.person.factory(
104        first_name=first_name,
105        last_name=last_name,
106        sex=sex,
107        year_of_birth=year_of_birth,
108    )
109    my_expectation = (
110        f'{first_name}, {last_name},'
111        f' {sex}, {year_of_birth}'
112    )
113    assert reality == my_expectation
114
115    reality = src.person.say_hi(
116        first_name=first_name,
117        last_name=last_name,
118        year_of_birth=year_of_birth,
119    )
120    my_expectation = (
121        f'Hi, my name is {first_name}'
122        f' {last_name} and I am'
123        f' {2026-year_of_birth}.'
124    )
125    assert reality == my_expectation
126
127
128# Exceptions seen
129# AssertionError
130# NameError
131# TypeError
132# AttributeError

open the project

  • I open a terminal

  • I change directory to the project

    cd person
    

    the terminal shows I am in the person folder

    .../pumping_python/ove
    
  • I open test_person.py

  • I use pytest-watcher to run the tests automatically

    uv run pytest-watcher . --now
    

    the terminal shows the tests from before are passing.


test person factory


RED: make it fail


I add an assertion to test_joe in test_person.py

17  def test_joe():
18      assert joe() == 'joe, blow, M, 1996'
19
20      reality = factory()
21      my_expectation = 'joe, blow, M, 1996'
22      assert reality == my_expectation
23
24
25  def test_jane():

the terminal is my friend, and shows NameError

NameError: name 'factory' is not defined

because I have not defined factory in test_person.py, yet.


GREEN: make it pass


I add a function definition for it

1def factory():
2    return 'joe, blow, M, 1996'
3
4
5def joe():

the test passes.


REFACTOR: make it better


  • I add an assertion with a call to the factory function in test_jane

    21def test_joe():
    22    assert joe() == 'joe, blow, M, 1996'
    23
    24    reality = factory()
    25    my_expectation = 'joe, blow, M, 1996'
    26    assert reality == my_expectation
    27
    28
    29def test_jane():
    30    assert jane() == 'jane, doe, F, 1991'
    31
    32    reality = factory()
    33    my_expectation = 'jane, doe, F, 1991'
    34    assert reality == my_expectation
    35
    36
    37def test_john():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'joe, blow, M, 1996'
                        == 'jane, doe, F, 1991'
    

    because the factory function always returns joe, blow, M, 1996 when it is called. It has to return a string based on the input it gets for me to be able to use it to make more than one person.

  • I make the function take input for the first name

    1# def factory():
    2def factory(first_name):
    3    return 'joe, blow, M, 1996'
    

    the terminal is my friend, and shows TypeError

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

    because

  • I add 'jane' to the call to the factory function in test_jane

    30def test_jane():
    31    assert jane() == 'jane, doe, F, 1991'
    32
    33    # reality = factory()
    34    reality = factory('jane')
    35    my_expectation = 'jane, doe, F, 1991'
    36    assert reality == my_expectation
    37
    38
    39def test_john():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'joe, blow, M, 1996'
                        == 'jane, doe, F, 1991'
    
  • I change the return statement of the factory function to an f-string to use first_name in the output

    1# def factory():
    2def factory(first_name):
    3    # return 'joe, blow, M, 1996'
    4    return f'{first_name}, blow, M, 1996'
    5
    6
    7def joe():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'jane, blow, M, 1996'
                        == 'jane, doe, F, 1991'
    

    the first names match. Progress!

  • I add last_name to the return statement

     1# def factory():
     2def factory(first_name):
     3    # return 'joe, blow, M, 1996'
     4    # return f'{first_name}, blow, M, 1996'
     5    return (
     6        f'{first_name}, {last_name},'
     7        ' M, 1996'
     8    )
     9
    10
    11def joe():
    

    the terminal is my friend, and shows NameError

    NameError: name 'last_name' is not defined
    
  • I add the name to the parentheses to define it in the factory function

     1# def factory():
     2# def factory(first_name):
     3def factory(first_name, last_name):
     4    # return 'joe, blow, M, 1996'
     5    # return f'{first_name}, blow, M, 1996'
     6    return (
     7        f'{first_name}, {last_name},'
     8        ' M, 1996'
     9    )
    10
    11
    12def joe():
    

    the terminal is my friend, and shows TypeError

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

    because

  • I add 'doe' to the call to the factory function in test_jane

    36def test_jane():
    37    assert jane() == 'jane, doe, F, 1991'
    38
    39    # reality = factory()
    40    # reality = factory('jane')
    41    reality = factory('jane', 'doe')
    42    my_expectation = 'jane, doe, F, 1991'
    43    assert reality == my_expectation
    44
    45
    46def test_john():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'jane, doe, M, 1996'
                        == 'jane, doe, F, 1991'
    

    the first and last names match. More progress.

  • I use keyword arguments with a value for sex (since I have more than two) to change the call to the factory function in test_jane

    36def test_jane():
    37    assert jane() == 'jane, doe, F, 1991'
    38
    39    # reality = factory()
    40    # reality = factory('jane')
    41    # reality = factory('jane', 'doe')
    42    reality = factory(
    43        first_name='jane',
    44        last_name='doe',
    45        sex='F',
    46    )
    47    my_expectation = 'jane, doe, F, 1991'
    48    assert reality == my_expectation
    49
    50
    51def test_john():
    

    the terminal is my friend, and shows TypeError

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

    because

  • I add sex in the parentheses of the factory function definition

     1# def factory():
     2# def factory(first_name):
     3# def factory(first_name, last_name):
     4def factory(first_name, last_name, sex):
     5    # return 'joe, blow, M, 1996'
     6    # return f'{first_name}, blow, M, 1996'
     7    return (
     8        f'{first_name}, {last_name},'
     9        ' M, 1996'
    10    )
    11
    12
    13def joe():
    

    the terminal shows the last AssertionError again.

  • I add sex to the f-string in the factory function

     1# def factory():
     2# def factory(first_name):
     3# def factory(first_name, last_name):
     4def factory(first_name, last_name, sex):
     5    # return 'joe, blow, M, 1996'
     6    # return f'{first_name}, blow, M, 1996'
     7    return (
     8        f'{first_name}, {last_name},'
     9        # ' M, 1996'
    10        f' {sex}, 1996'
    11    )
    12
    13
    14def joe():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'jane, doe, F, 1996'
                        == 'jane, doe, F, 1991'
    

    the first name, last name and sex match. Yes!

  • I add year_of_birth to the return statement

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

    the terminal is my friend, and shows NameError

    NameError: name 'year_of_birth' is not defined
    
  • I add year_of_birth to the parentheses to define it in the factory function

     1# def factory():
     2# def factory(first_name):
     3# def factory(first_name, last_name):
     4# def factory(first_name, last_name, sex):
     5def factory(
     6    first_name, last_name,
     7    sex, year_of_birth
     8):
     9    # return 'joe, blow, M, 1996'
    10    # return f'{first_name}, blow, M, 1996'
    11    return (
    12        f'{first_name}, {last_name},'
    13        # ' M, 1996'
    14        # f' {sex}, 1996'
    15        f' {sex}, {year_of_birth}'
    16    )
    17
    18
    19def joe():
    

    the terminal is my friend, and shows TypeError

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

    because

  • I add year_of_birth=1996 to the call to the factory function in test_jane

    43def test_jane():
    44    assert jane() == 'jane, doe, F, 1991'
    45
    46    # reality = factory()
    47    # reality = factory('jane')
    48    # reality = factory('jane', 'doe')
    49    reality = factory(
    50        first_name='jane',
    51        last_name='doe',
    52        sex='F',
    53        year_of_birth=1991,
    54    )
    55    my_expectation = 'jane, doe, F, 1991'
    56    assert reality == my_expectation
    57
    58
    59def test_john():
    

    the terminal is my friend, and shows TypeError

    TypeError: factory() missing
               4 required positional arguments:
               'first_name', 'last_name', 'sex',
               and 'year_of_birth'
    

    because

  • Since there are more than two, I add keyword arguments to the call to the factory function in test_joe

    35def test_joe():
    36    assert joe() == 'joe, blow, M, 1996'
    37
    38    # reality = factory()
    39    reality = factory(
    40        first_name='joe',
    41        last_name='blow',
    42        sex='M',
    43        year_of_birth=1996,
    44    )
    45    my_expectation = 'joe, blow, M, 1996'
    46    assert reality == my_expectation
    47
    48
    49def test_jane():
    

    the test passes and I have one function that I can use to make any number of people.


  • I remove the commented lines from the factory function

     1def factory(
     2    first_name, last_name,
     3    sex, year_of_birth
     4):
     5    return (
     6        f'{first_name}, {last_name},'
     7        f' {sex}, {year_of_birth}'
     8    )
     9
    10
    11def joe():
    
  • I comment out the call to joe in test_joe because I no longer need it since the factory function does the same thing

    27def test_joe():
    28    # assert joe() == 'joe, blow, M, 1996'
    29
    30    reality = factory(
    31        first_name='joe',
    32        last_name='blow',
    33        sex='M',
    34        year_of_birth=1996,
    35    )
    36    my_expectation = 'joe, blow, M, 1996'
    37    assert reality == my_expectation
    38
    39
    40def test_jane():
    
  • I add a variable for 'joe' in test_joe

    27def test_joe():
    28    # assert joe() == 'joe, blow, M, 1996'
    29    first_name = 'joe'
    30
    31    reality = factory(
    32        first_name='joe',
    33        last_name='blow',
    34        sex='M',
    35        year_of_birth=1996,
    36    )
    37    my_expectation = 'joe, blow, M, 1996'
    38    assert reality == my_expectation
    39
    40
    41def test_jane():
    
  • I use the variable to remove repetition of 'joe'

    27def test_joe():
    28    # assert joe() == 'joe, blow, M, 1996'
    29    first_name = 'joe'
    30
    31    reality = factory(
    32        # first_name='joe',
    33        first_name=first_name,
    34        last_name='blow',
    35        sex='M',
    36        year_of_birth=1996,
    37    )
    38    # my_expectation = 'joe, blow, M, 1996'
    39    my_expectation = (
    40        f'{first_name}, blow,'
    41        ' M, 1996'
    42    )
    43    assert reality == my_expectation
    44
    45
    46def test_jane():
    

    the test is still green.

  • I add a variable for 'blow' in test_joe

    27def test_joe():
    28    # assert joe() == 'joe, blow, M, 1996'
    29    first_name = 'joe'
    30    last_name = 'blow'
    31
    32    reality = factory(
    33        # first_name='joe',
    34        first_name=first_name,
    35        last_name='blow',
    36        sex='M',
    37        year_of_birth=1996,
    38    )
    39    # my_expectation = 'joe, blow, M, 1996'
    40    my_expectation = (
    41        f'{first_name}, blow,'
    42        ' M, 1996'
    43    )
    44    assert reality == my_expectation
    45
    46
    47def test_jane():
    
  • I use the variable to remove repetition of 'blow'

    27def test_joe():
    28    # assert joe() == 'joe, blow, M, 1996'
    29    first_name = 'joe'
    30    last_name = 'blow'
    31
    32    reality = factory(
    33        # first_name='joe',
    34        first_name=first_name,
    35        # last_name='blow',
    36        last_name=last_name,
    37        sex='M',
    38        year_of_birth=1996,
    39    )
    40    # my_expectation = 'joe, blow, M, 1996'
    41    my_expectation = (
    42        # f'{first_name}, blow,'
    43        f'{first_name}, {last_name},'
    44        ' M, 1996'
    45    )
    46    assert reality == my_expectation
    47
    48
    49def test_jane():
    

    still green.

  • I add a variable for 'M' in test_joe

    27def test_joe():
    28    # assert joe() == 'joe, blow, M, 1996'
    29    first_name = 'joe'
    30    last_name = 'blow'
    31    sex = 'M'
    32
    33    reality = factory(
    34        # first_name='joe',
    35        first_name=first_name,
    36        # last_name='blow',
    37        last_name=last_name,
    38        sex='M',
    39        year_of_birth=1996,
    40    )
    
  • I use the variable to remove repetition of 'M'

    27def test_joe():
    28    # assert joe() == 'joe, blow, M, 1996'
    29    first_name = 'joe'
    30    last_name = 'blow'
    31    sex = 'M'
    32
    33    reality = factory(
    34        # first_name='joe',
    35        first_name=first_name,
    36        # last_name='blow',
    37        last_name=last_name,
    38        # sex='M',
    39        sex=sex,
    40        year_of_birth=1996,
    41    )
    42    # my_expectation = 'joe, blow, M, 1996'
    43    my_expectation = (
    44        # f'{first_name}, blow,'
    45        f'{first_name}, {last_name},'
    46        # ' M, 1996'
    47        f' {sex}, 1996'
    48    )
    49    assert reality == my_expectation
    50
    51
    52def test_jane():
    

    green.

  • I add a variable for 1996 in test_joe

    27def test_joe():
    28    # assert joe() == 'joe, blow, M, 1996'
    29    first_name = 'joe'
    30    last_name = 'blow'
    31    sex = 'M'
    32    year_of_birth = 1996
    33
    34    reality = factory(
    35        # first_name='joe',
    36        first_name=first_name,
    37        # last_name='blow',
    38        last_name=last_name,
    39        # sex='M',
    40        sex=sex,
    41        year_of_birth=1996,
    42    )
    
  • I use the variable to remove repetition of 1996

    27def test_joe():
    28    # assert joe() == 'joe, blow, M, 1996'
    29    first_name = 'joe'
    30    last_name = 'blow'
    31    sex = 'M'
    32    year_of_birth = 1996
    33
    34    reality = factory(
    35        # first_name='joe',
    36        first_name=first_name,
    37        # last_name='blow',
    38        last_name=last_name,
    39        # sex='M',
    40        sex=sex,
    41        # year_of_birth=1996,
    42        year_of_birth=year_of_birth,
    43    )
    44    # my_expectation = 'joe, blow, M, 1996'
    45    my_expectation = (
    46        # f'{first_name}, blow,'
    47        f'{first_name}, {last_name},'
    48        # ' M, 1996'
    49        # f' {sex}, 1996'
    50        f' {sex}, {year_of_birth}'
    51    )
    52    assert reality == my_expectation
    53
    54
    55def test_jane():
    

    still green.

  • I remove the commented lines

    27def test_joe():
    28    first_name = 'joe'
    29    last_name = 'blow'
    30    sex = 'M'
    31    year_of_birth = 1996
    32
    33    reality = factory(
    34        first_name=first_name,
    35        last_name=last_name,
    36        sex=sex,
    37        year_of_birth=year_of_birth,
    38    )
    39    my_expectation = (
    40        f'{first_name}, {last_name},'
    41        f' {sex}, {year_of_birth}'
    42    )
    43    assert reality == my_expectation
    44
    45
    46def test_jane():
    

  • I comment out the call to jane in test_jane because I no longer need it since the factory function does the same thing

    46def test_jane():
    47    # assert jane() == 'jane, doe, F, 1991'
    48
    49    # reality = factory()
    
  • I add a variable for 'jane' in test_jane

    46def test_jane():
    47    # assert jane() == 'jane, doe, F, 1991'
    48    first_name = 'jane'
    49
    50    # reality = factory()
    
  • I use the variable to remove repetition of 'jane'

    46def test_jane():
    47    # assert jane() == 'jane, doe, F, 1991'
    48    first_name = 'jane'
    49
    50    # reality = factory()
    51    # reality = factory('jane')
    52    # reality = factory('jane', 'doe')
    53    reality = factory(
    54        # first_name='jane',
    55        first_name=first_name,
    56        last_name='doe',
    57        sex='F',
    58        year_of_birth=1991,
    59    )
    60    # my_expectation = 'jane, doe, F, 1991'
    61    my_expectation = (
    62        f'{first_name}, doe,'
    63        ' F, 1991'
    64    )
    65    assert reality == my_expectation
    66
    67
    68def test_john():
    

    the test is still green.

  • I add a variable for 'doe' in test_jane

    46def test_jane():
    47    # assert jane() == 'jane, doe, F, 1991'
    48    first_name = 'jane'
    49    last_name = 'doe'
    50
    51    # reality = factory()
    
  • I use the variable to remove repetition of 'doe'

    46def test_jane():
    47    # assert jane() == 'jane, doe, F, 1991'
    48    first_name = 'jane'
    49    last_name = 'doe'
    50
    51    # reality = factory()
    52    # reality = factory('jane')
    53    # reality = factory('jane', 'doe')
    54    reality = factory(
    55        # first_name='jane',
    56        first_name=first_name,
    57        # last_name='doe',
    58        last_name=last_name,
    59        sex='F',
    60        year_of_birth=1991,
    61    )
    62    # my_expectation = 'jane, doe, F, 1991'
    63    my_expectation = (
    64        # f'{first_name}, doe,'
    65        f'{first_name}, {last_name},'
    66        ' F, 1991'
    67    )
    68    assert reality == my_expectation
    69
    70
    71def test_john():
    

    still green.

  • I add a variable for 'F' in test_jane

    46def test_jane():
    47    # assert jane() == 'jane, doe, F, 1991'
    48    first_name = 'jane'
    49    last_name = 'doe'
    50    sex = 'F'
    51
    52    # reality = factory()
    
  • I use the variable to remove repetition of 'F'

    46def test_jane():
    47    # assert jane() == 'jane, doe, F, 1991'
    48    first_name = 'jane'
    49    last_name = 'doe'
    50    sex = 'F'
    51
    52    # reality = factory()
    53    # reality = factory('jane')
    54    # reality = factory('jane', 'doe')
    55    reality = factory(
    56        # first_name='jane',
    57        first_name=first_name,
    58        # last_name='doe',
    59        last_name=last_name,
    60        # sex='F',
    61        sex=sex,
    62        year_of_birth=1991,
    63    )
    64    # my_expectation = 'jane, doe, F, 1991'
    65    my_expectation = (
    66        # f'{first_name}, doe,'
    67        f'{first_name}, {last_name},'
    68        # ' F, 1991'
    69        f' {sex}, 1991'
    70    )
    71    assert reality == my_expectation
    72
    73
    74def test_john():
    

    green.

  • I add a variable for 1991 in test_jane

    46def test_jane():
    47    # assert jane() == 'jane, doe, F, 1991'
    48    first_name = 'jane'
    49    last_name = 'doe'
    50    sex = 'F'
    51    year_of_birth = 1991
    52
    53    # reality = factory()
    
  • I use the variable to remove repetition of 1991

    46def test_jane():
    47    # assert jane() == 'jane, doe, F, 1991'
    48    first_name = 'jane'
    49    last_name = 'doe'
    50    sex = 'F'
    51    year_of_birth = 1991
    52
    53    # reality = factory()
    54    # reality = factory('jane')
    55    # reality = factory('jane', 'doe')
    56    reality = factory(
    57        # first_name='jane',
    58        first_name=first_name,
    59        # last_name='doe',
    60        last_name=last_name,
    61        # sex='F',
    62        sex=sex,
    63        # year_of_birth=1991,
    64        year_of_birth=year_of_birth,
    65    )
    66    # my_expectation = 'jane, doe, F, 1991'
    67    my_expectation = (
    68        # f'{first_name}, doe,'
    69        f'{first_name}, {last_name},'
    70        # ' F, 1991'
    71        # f' {sex}, 1991'
    72        f' {sex}, {year_of_birth}'
    73    )
    74    assert reality == my_expectation
    75
    76
    77def test_john():
    

    still green.

  • I remove the commented lines

    46def test_jane():
    47    first_name = 'jane'
    48    last_name = 'doe'
    49    sex = 'F'
    50    year_of_birth = 1991
    51
    52    reality = factory(
    53        first_name=first_name,
    54        last_name=last_name,
    55        sex=sex,
    56        year_of_birth=year_of_birth,
    57    )
    58    my_expectation = (
    59        f'{first_name}, {last_name},'
    60        f' {sex}, {year_of_birth}'
    61    )
    62    assert reality == my_expectation
    63
    64
    65def test_john():
    

  • I change the call in test_john to a call to the factory function

    65def test_john():
    66    # assert john() == 'john, smith, M, 1580'
    67    reality = factory(
    68        first_name='john',
    69        last_name='smith',
    70        sex='M',
    71        year_of_birth=1580,
    72    )
    73    my_expectation = 'jane, doe, F, 1991'
    74    assert reality == my_expectation
    75
    76
    77def test_mary():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'john, smith, M, 1580'
                        == 'jane, doe, F, 1991'
    
  • I change my_expectation to match reality

    65def test_john():
    66    # assert john() == 'john, smith, M, 1580'
    67    reality = factory(
    68        first_name='john',
    69        last_name='smith',
    70        sex='M',
    71        year_of_birth=1580,
    72    )
    73    # my_expectation = 'jane, doe, F, 1991'
    74    my_expectation = 'john, smith, M, 1580'
    75    assert reality == my_expectation
    76
    77
    78def test_mary():
    

    the test passes.

  • I add a variable for 'john' in test_john

    65def test_john():
    66    # assert john() == 'john, smith, M, 1580'
    67    first_name = 'john'
    68
    69    reality = factory(
    70        first_name='john',
    71        last_name='smith',
    72        sex='M',
    73        year_of_birth=1580,
    74    )
    75    # my_expectation = 'jane, doe, F, 1991'
    76    my_expectation = 'john, smith, M, 1580'
    77    assert reality == my_expectation
    78
    79
    80def test_mary():
    
  • I use the variable to remove repetition of 'john'

    65def test_john():
    66    # assert john() == 'john, smith, M, 1580'
    67    first_name = 'john'
    68
    69    reality = factory(
    70        # first_name='john',
    71        first_name=first_name,
    72        last_name='smith',
    73        sex='M',
    74        year_of_birth=1580,
    75    )
    76    # my_expectation = 'jane, doe, F, 1991'
    77    # my_expectation = 'john, smith, M, 1580'
    78    my_expectation = (
    79        f'{first_name}, smith,'
    80        ' M, 1580'
    81    )
    82    assert reality == my_expectation
    83
    84
    85def test_mary():
    

    the test is still green.

  • I add a variable for 'smith' in test_john

    65def test_john():
    66    # assert john() == 'john, smith, M, 1580'
    67    first_name = 'john'
    68    last_name = 'smith'
    
  • I use the variable to remove repetition of 'smith'

    65def test_john():
    66    # assert john() == 'john, smith, M, 1580'
    67    first_name = 'john'
    68    last_name = 'smith'
    69
    70    reality = factory(
    71        # first_name='john',
    72        first_name=first_name,
    73        # last_name='smith',
    74        last_name=last_name,
    75        sex='M',
    76        year_of_birth=1580,
    77    )
    78    # my_expectation = 'jane, doe, F, 1991'
    79    # my_expectation = 'john, smith, M, 1580'
    80    my_expectation = (
    81        # f'{first_name}, smith,'
    82        f'{first_name}, {last_name},'
    83        ' M, 1580'
    84    )
    85    assert reality == my_expectation
    86
    87
    88def test_mary():
    

    still green.

  • I add a variable for 'M' in test_john

    65def test_john():
    66    # assert john() == 'john, smith, M, 1580'
    67    first_name = 'john'
    68    last_name = 'smith'
    69    sex = 'M'
    
  • I use the variable to remove repetition of 'M'

    65def test_john():
    66    # assert john() == 'john, smith, M, 1580'
    67    first_name = 'john'
    68    last_name = 'smith'
    69    sex = 'M'
    70
    71    reality = factory(
    72        # first_name='john',
    73        first_name=first_name,
    74        # last_name='smith',
    75        last_name=last_name,
    76        # sex='M',
    77        sex=sex,
    78        year_of_birth=1580,
    79    )
    80    # my_expectation = 'jane, doe, F, 1991'
    81    # my_expectation = 'john, smith, M, 1580'
    82    my_expectation = (
    83        # f'{first_name}, smith,'
    84        f'{first_name}, {last_name},'
    85        # ' M, 1580'
    86        f' {sex}, 1580'
    87    )
    88    assert reality == my_expectation
    89
    90
    91def test_mary():
    

    green.

  • I add a variable for 1580 in test_john

    65def test_john():
    66    # assert john() == 'john, smith, M, 1580'
    67    first_name = 'john'
    68    last_name = 'smith'
    69    sex = 'M'
    70    year_of_birth = 1580
    
  • I use the variable to remove repetition of 1580

    65def test_john():
    66    # assert john() == 'john, smith, M, 1580'
    67    first_name = 'john'
    68    last_name = 'smith'
    69    sex = 'M'
    70    year_of_birth = 1580
    71
    72    reality = factory(
    73        # first_name='john',
    74        first_name=first_name,
    75        # last_name='smith',
    76        last_name=last_name,
    77        # sex='M',
    78        sex=sex,
    79        # year_of_birth=1580,
    80        year_of_birth=year_of_birth,
    81    )
    82    # my_expectation = 'jane, doe, F, 1991'
    83    # my_expectation = 'john, smith, M, 1580'
    84    my_expectation = (
    85        # f'{first_name}, smith,'
    86        f'{first_name}, {last_name},'
    87        # ' M, 1580'
    88        # f' {sex}, 1580'
    89        f' {sex}, {year_of_birth}'
    90    )
    91    assert reality == my_expectation
    92
    93
    94def test_mary():
    

    still green.

  • I remove the commented lines

    65def test_john():
    66    first_name = 'john'
    67    last_name = 'smith'
    68    sex = 'M'
    69    year_of_birth = 1580
    70
    71    reality = factory(
    72        first_name=first_name,
    73        last_name=last_name,
    74        sex=sex,
    75        year_of_birth=year_of_birth,
    76    )
    77    my_expectation = (
    78        f'{first_name}, {last_name},'
    79        f' {sex}, {year_of_birth}'
    80    )
    81    assert reality == my_expectation
    82
    83
    84def test_mary():
    
  • I make the same change to test_mary

     84def test_mary():
     85    # assert mary() == 'mary, public, F, 2000'
     86    first_name = 'mary'
     87    last_name = 'public'
     88    sex = 'F'
     89    year_of_birth = 2000
     90
     91    reality = factory(
     92        first_name=first_name,
     93        last_name=last_name,
     94        sex=sex,
     95        year_of_birth=year_of_birth,
     96    )
     97    my_expectation = (
     98        f'{first_name}, {last_name},'
     99        f' {sex}, {year_of_birth}'
    100    )
    101    assert reality == my_expectation
    102
    103
    104# Exceptions seen
    

    the test is still green.

  • I remove the commented line

     84def test_mary():
     85    first_name = 'mary'
     86    last_name = 'public'
     87    sex = 'F'
     88    year_of_birth = 2000
     89
     90    reality = factory(
     91        first_name=first_name,
     92        last_name=last_name,
     93        sex=sex,
     94        year_of_birth=year_of_birth,
     95    )
     96    my_expectation = (
     97        f'{first_name}, {last_name},'
     98        f' {sex}, {year_of_birth}'
     99    )
    100    assert reality == my_expectation
    101
    102
    103# Exceptions seen
    
  • I remove the joe, jane, john and mary functions because they are no longer used

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

    the factory function can make a string for any person I want when I give it the first name, last name, sex and year of birth.

  • I open a new terminal then change directories to person

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

    git commit -am 'extract factory function'
    

separate and equal factory


RED: make it fail


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

  • I change reality for test_joe to be the result of a call to the factory function of the person module in the src folder instead of a call to the factory function in test_person.py

    11def test_joe():
    12    first_name = 'joe'
    13    last_name = 'blow'
    14    sex = 'M'
    15    year_of_birth = 1996
    16
    17    # reality = factory(
    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    )
    24    my_expectation = (
    25        f'{first_name}, {last_name},'
    26        f' {sex}, {year_of_birth}'
    27    )
    28    assert reality == my_expectation
    29
    30
    31def test_jane():
    

    the terminal is my friend, and shows NameError

    NameError: name 'src' is not
    

    because src is not defined in test_person.py


GREEN: make it pass


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

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

    the terminal is my friend, and shows AttributeError

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

    because there is nothing named factory in person.py in the src folder.

  • I add AttributeError to the list of Exceptions seen

    90# Exceptions seen
    91# AssertionError
    92# NameError
    93# TypeError
    94# AttributeError
    
  • I open person.py from the src folder

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

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

    the test passes because

    I think of src.person.factory like an address

    • factory is something in person, in this case it is a function in person

    • person is something in src, in this case it is person.py (a module) in the src folder

    • src is something Python can import (a module, Python package or folder)

      src
      └── person.py
          └── def factory(
                  first_name, last_name,
                  sex, year_of_birth
              ):
                  return (
                      f'{first_name}, {last_name},'
                      f' {sex}, {year_of_birth}'
                  )
      

REFACTOR: make it better


  • I remove the commented line from test_joe in test_person.py

    14def test_joe():
    15    first_name = 'joe'
    16    last_name = 'blow'
    17    sex = 'M'
    18    year_of_birth = 1996
    19
    20    reality = src.person.factory(
    21        first_name=first_name,
    22        last_name=last_name,
    23        sex=sex,
    24        year_of_birth=year_of_birth,
    25    )
    26    my_expectation = (
    27        f'{first_name}, {last_name},'
    28        f' {sex}, {year_of_birth}'
    29    )
    30    assert reality == my_expectation
    31
    32
    33def test_jane():
    
  • I change reality for test_jane to be the result of a call to the factory function of the person module in the src folder

    33def test_jane():
    34    first_name = 'jane'
    35    last_name = 'doe'
    36    sex = 'F'
    37    year_of_birth = 1991
    38
    39    # reality = factory(
    40    reality = src.person.factory(
    41        first_name=first_name,
    42        last_name=last_name,
    43        sex=sex,
    44        year_of_birth=year_of_birth,
    45    )
    46    my_expectation = (
    47        f'{first_name}, {last_name},'
    48        f' {sex}, {year_of_birth}'
    49    )
    50    assert reality == my_expectation
    51
    52
    53def test_john():
    

    the test is still green.

  • I remove the commented line from test_jane

    33def test_jane():
    34    first_name = 'jane'
    35    last_name = 'doe'
    36    sex = 'F'
    37    year_of_birth = 1991
    38
    39    reality = src.person.factory(
    40        first_name=first_name,
    41        last_name=last_name,
    42        sex=sex,
    43        year_of_birth=year_of_birth,
    44    )
    45    my_expectation = (
    46        f'{first_name}, {last_name},'
    47        f' {sex}, {year_of_birth}'
    48    )
    49    assert reality == my_expectation
    50
    51
    52def test_john():
    
  • I change reality for test_john to be the result of a call to the factory function of the person module in the src folder

    52def test_john():
    53    first_name = 'john'
    54    last_name = 'smith'
    55    sex = 'M'
    56    year_of_birth = 1580
    57
    58    # reality = factory(
    59    reality = src.person.factory(
    60        first_name=first_name,
    61        last_name=last_name,
    62        sex=sex,
    63        year_of_birth=year_of_birth,
    64    )
    65    my_expectation = (
    66        f'{first_name}, {last_name},'
    67        f' {sex}, {year_of_birth}'
    68    )
    69    assert reality == my_expectation
    70
    71
    72def test_mary():
    

    still green.

  • I remove the commented line

    52def test_john():
    53    first_name = 'john'
    54    last_name = 'smith'
    55    sex = 'M'
    56    year_of_birth = 1580
    57
    58    reality = src.person.factory(
    59        first_name=first_name,
    60        last_name=last_name,
    61        sex=sex,
    62        year_of_birth=year_of_birth,
    63    )
    64    my_expectation = (
    65        f'{first_name}, {last_name},'
    66        f' {sex}, {year_of_birth}'
    67    )
    68    assert reality == my_expectation
    69
    70
    71def test_mary():
    
  • I do the same thing to the call to the factory function in test_mary

    71def test_mary():
    72    first_name = 'mary'
    73    last_name = 'public'
    74    sex = 'F'
    75    year_of_birth = 2000
    76
    77    # reality = factory(
    78    reality = src.person.factory(
    79        first_name=first_name,
    80        last_name=last_name,
    81        sex=sex,
    82        year_of_birth=year_of_birth,
    83    )
    84    my_expectation = (
    85        f'{first_name}, {last_name},'
    86        f' {sex}, {year_of_birth}'
    87    )
    88    assert reality == my_expectation
    89
    90
    91# Exceptions seen
    

    green.

  • I remove the commented line

    71def test_mary():
    72    first_name = 'mary'
    73    last_name = 'public'
    74    sex = 'F'
    75    year_of_birth = 2000
    76
    77    reality = src.person.factory(
    78        first_name=first_name,
    79        last_name=last_name,
    80        sex=sex,
    81        year_of_birth=year_of_birth,
    82    )
    83    my_expectation = (
    84        f'{first_name}, {last_name},'
    85        f' {sex}, {year_of_birth}'
    86    )
    87    assert reality == my_expectation
    88
    89
    90# Exceptions seen
    91# AssertionError
    92# NameError
    93# TypeError
    94# AttributeError
    
  • I remove the factory function from test_person.py

     1import src.person
     2
     3
     4def test_joe():
     5    first_name = 'joe'
     6    last_name = 'blow'
     7    sex = 'M'
     8    year_of_birth = 1996
     9
    10    reality = src.person.factory(
    11        first_name=first_name,
    12        last_name=last_name,
    13        sex=sex,
    14        year_of_birth=year_of_birth,
    15    )
    16    my_expectation = (
    17        f'{first_name}, {last_name},'
    18        f' {sex}, {year_of_birth}'
    19    )
    20    assert reality == my_expectation
    21
    22
    23def test_jane():
    

    all the tests are still green because the calls that were made to the factory function that was in test_person.py are now to the factory function in person.py in the src folder. When src.person.factory is called, Python follows this path

    src
    └── person.py
        └── def factory(
                first_name, last_name,
                sex, year_of_birth
            ):
                return (
                    f'{first_name}, {last_name},'
                    f' {sex}, {year_of_birth}'
                )
    
  • I add a git commit message in the other terminal

    git commit -am \
    'move factory function to person.py'
    

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

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


test say_hi

I want the person I make to say hi. I can make a function that takes input about a person and returns a message.


RED: make it fail


  • I add an assertion to test_joe in test_person.py

     1import src.person
     2
     3
     4def test_joe():
     5    first_name = 'joe'
     6    last_name = 'blow'
     7    sex = 'M'
     8    year_of_birth = 1996
     9
    10    reality = src.person.factory(
    11        first_name=first_name,
    12        last_name=last_name,
    13        sex=sex,
    14        year_of_birth=year_of_birth,
    15    )
    16    my_expectation = (
    17        f'{first_name}, {last_name},'
    18        f' {sex}, {year_of_birth}'
    19    )
    20    assert reality == my_expectation
    21
    22    reality = say_hi(
    23        first_name=first_name,
    24        last_name=last_name,
    25        year_of_birth=year_of_birth,
    26    )
    27    my_expectation = (
    28        f'Hi, my name is {first_name}'
    29        f' {last_name} and I am'
    30        f' {2026-year_of_birth}.'
    31    )
    32    assert reality == my_expectation
    33
    34
    35def test_jane():
    

    the terminal is my friend, and shows NameError

    NameError: name 'say_hi' is not defined
    

GREEN: make it pass


  • I add a function definition for it

    1import src.person
    2
    3
    4def say_hi():
    5    return None
    6
    7
    8def test_joe():
    

    the terminal is my friend, and shows TypeError

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

    because

  • I add first_name in parentheses

    4# def say_hi():
    5def say_hi(first_name):
    6    return None
    7
    8
    9def test_joe():
    

    the terminal is my friend, and shows TypeError

    TypeError: say_hi()
               got an unexpected keyword argument 'last_name'.
               Did you mean 'first_name'?
    
  • I add last_name in parentheses

     4# def say_hi():
     5# def say_hi(first_name):
     6def say_hi(first_name, last_name):
     7    return None
     8
     9
    10def test_joe():
    

    the terminal is my friend, and shows TypeError

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

    because

  • I add year_of_birth in parentheses

     4# def say_hi():
     5# def say_hi(first_name):
     6# def say_hi(first_name, last_name):
     7def say_hi(
     8    first_name, last_name,
     9    year_of_birth
    10):
    11    return None
    12
    13
    14def test_joe():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert None
        == 'Hi, my name is joe blow and I am 30.'
    
  • I change the return statement to give the test what it wants

     4# def say_hi():
     5# def say_hi(first_name):
     6# def say_hi(first_name, last_name):
     7def say_hi(
     8    first_name, last_name,
     9    year_of_birth
    10):
    11    # return None
    12    return 'Hi, my name is joe blow and I am 30.'
    13
    14
    15def test_joe():
    

    the test passes.


REFACTOR: make it better


  • I add an assertion for the say_hi function to test_jane in test_person.py

    46def test_jane():
    47    first_name = 'jane'
    48    last_name = 'doe'
    49    sex = 'F'
    50    year_of_birth = 1991
    51
    52    reality = src.person.factory(
    53        first_name=first_name,
    54        last_name=last_name,
    55        sex=sex,
    56        year_of_birth=year_of_birth,
    57    )
    58    my_expectation = (
    59        f'{first_name}, {last_name},'
    60        f' {sex}, {year_of_birth}'
    61    )
    62    assert reality == my_expectation
    63
    64    reality = say_hi(
    65        first_name=first_name,
    66        last_name=last_name,
    67        year_of_birth=year_of_birth,
    68    )
    69    my_expectation = (
    70        f'Hi, my name is {first_name}'
    71        f' {last_name} and I am'
    72        f' {2026-year_of_birth}.'
    73    )
    74    assert reality == my_expectation
    75
    76
    77def test_john():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'Hi, my name ... and I am 30.'
                        == 'Hi, my name ... and I am 35.'
    
    • because the say_hi function always returns 'Hi, my name is joe blow and I am 30.' when it is called. It has to return a string based on the input it gets for me to be able to use it to make messages based on the person.

    • I need better error messages.

  • I change the return statement of the say_hi function to an f-string to use first_name in the output

     4# def say_hi():
     5# def say_hi(first_name):
     6# def say_hi(first_name, last_name):
     7def say_hi(
     8    first_name, last_name,
     9    year_of_birth
    10):
    11    # return None
    12    # return 'Hi, my name is joe blow and I am 30.'
    13    return (
    14        f'Hi, my name is {first_name}'
    15        ' blow and I am 30.'
    16    )
    17
    18
    19def test_joe():
    

    the terminal shows AssertionError

    AssertionError: assert 'Hi, my name ... and I am 30.'
                        == 'Hi, my name ... and I am 35.'
    

    the first names now match and I still need better error messages.

  • I add last_name to the return statement

     4# def say_hi():
     5# def say_hi(first_name):
     6# def say_hi(first_name, last_name):
     7def say_hi(
     8    first_name, last_name,
     9    year_of_birth
    10):
    11    # return None
    12    # return 'Hi, my name is joe blow and I am 30.'
    13    return (
    14        f'Hi, my name is {first_name}'
    15        # ' blow and I am 30.'
    16        f' {last_name} and I am 30.'
    17    )
    18
    19
    20def test_joe():
    

    the terminal shows AssertionError

    AssertionError: assert 'Hi, my name ... and I am 30.'
                        == 'Hi, my name ... and I am 35.'
    

    the ages are different.

  • I add the age calculation to the return statement

     4# def say_hi():
     5# def say_hi(first_name):
     6# def say_hi(first_name, last_name):
     7def say_hi(
     8    first_name, last_name,
     9    year_of_birth
    10):
    11    # return None
    12    # return 'Hi, my name is joe blow and I am 30.'
    13    age = 2026 - year_of_birth
    14
    15    return (
    16        f'Hi, my name is {first_name}'
    17        # ' blow and I am 30.'
    18        # f' {last_name} and I am 30.'
    19        f' {last_name} and I am {age}.'
    20    )
    21
    22
    23def test_joe():
    

    the test passes.

  • I add an assertion for the say_hi function to test_john in test_person.py

     85def test_john():
     86    first_name = 'john'
     87    last_name = 'smith'
     88    sex = 'M'
     89    year_of_birth = 1580
     90
     91    reality = src.person.factory(
     92        first_name=first_name,
     93        last_name=last_name,
     94        sex=sex,
     95        year_of_birth=year_of_birth,
     96    )
     97    my_expectation = (
     98        f'{first_name}, {last_name},'
     99        f' {sex}, {year_of_birth}'
    100    )
    101    assert reality == my_expectation
    102
    103    reality = say_hi(
    104        first_name=first_name,
    105        last_name=last_name,
    106        year_of_birth=year_of_birth,
    107    )
    108    my_expectation = (
    109        'Hi, my name is jane'
    110        f' {last_name} and I am'
    111        f' {2026-year_of_birth}.'
    112    )
    113    assert reality == my_expectation
    114
    115
    116def test_mary():
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'Hi, my name ...and I am 446.'
                        == 'Hi, my name ...and I am 446.'
    
    • The first names are different.

    • I want more detail in the error messages.

  • I change my_expectation to match reality in test_john

    103    reality = say_hi(
    104        first_name=first_name,
    105        last_name=last_name,
    106        year_of_birth=year_of_birth,
    107    )
    108    my_expectation = (
    109        # 'Hi, my name is jane'
    110        f'Hi, my name is {first_name}'
    111        f' {last_name} and I am'
    112        f' {2026-year_of_birth}.'
    113    )
    114    assert reality == my_expectation
    115
    116
    117def test_mary():
    

    the test passes.

  • I add an assertion for the say_hi function to test_mary

    117def test_mary():
    118    first_name = 'mary'
    119    last_name = 'public'
    120    sex = 'F'
    121    year_of_birth = 2000
    122
    123    reality = src.person.factory(
    124        first_name=first_name,
    125        last_name=last_name,
    126        sex=sex,
    127        year_of_birth=year_of_birth,
    128    )
    129    my_expectation = (
    130        f'{first_name}, {last_name},'
    131        f' {sex}, {year_of_birth}'
    132    )
    133    assert reality == my_expectation
    134
    135    reality = say_hi(
    136        first_name=first_name,
    137        last_name=last_name,
    138        year_of_birth=year_of_birth,
    139    )
    140    my_expectation = (
    141        f'Hi, my name is {first_name}'
    142        ' smith and I am'
    143        f' {2026-year_of_birth}.'
    144    )
    145    assert reality == my_expectation
    146
    147
    148# Exceptions seen
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'Hi, my name ... and I am 26.'
                        == 'Hi, my name ... and I am 26.'
    
    • The last names are different.

    • I want better messages that show as much of the difference in the summary.

  • I change my_expectation to match reality in test_mary

    135    reality = say_hi(
    136        first_name=first_name,
    137        last_name=last_name,
    138        year_of_birth=year_of_birth,
    139    )
    140    my_expectation = (
    141        f'Hi, my name is {first_name}'
    142        # ' smith and I am'
    143        f' {last_name} and I am'
    144        f' {2026-year_of_birth}.'
    145    )
    146    assert reality == my_expectation
    147
    148
    149# Exceptions seen
    

    the test passes.

  • I remove the commented line

    116def test_mary():
    117    first_name = 'mary'
    118    last_name = 'public'
    119    sex = 'F'
    120    year_of_birth = 2000
    121
    122    reality = src.person.factory(
    123        first_name=first_name,
    124        last_name=last_name,
    125        sex=sex,
    126        year_of_birth=year_of_birth,
    127    )
    128    my_expectation = (
    129        f'{first_name}, {last_name},'
    130        f' {sex}, {year_of_birth}'
    131    )
    132    assert reality == my_expectation
    133
    134    reality = say_hi(
    135        first_name=first_name,
    136        last_name=last_name,
    137        year_of_birth=year_of_birth,
    138    )
    139    my_expectation = (
    140        f'Hi, my name is {first_name}'
    141        f' {last_name} and I am'
    142        f' {2026-year_of_birth}.'
    143    )
    144    assert reality == my_expectation
    145
    146
    147# Exceptions seen
    

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

    say_hi(
        first_name=first_name,
        last_name=last_name,
        year_of_birth=year_of_birth,
    )
        say_hi(
            first_name, last_name,
            year_of_birth,
        )
            first_name = 'mary'
            last_name = 'public'
            year_of_birth = 1991
    
            age = 2026 - year_of_birth
            age = 2026 - 1991
            age = 35
    
            return (
                f'Hi, my name is {first_name}'
                f' {last_name} and I am {age}.'
            )
            return f'Hi, my name is mary public and I am 35'
    

separate and equal say_hi

  • I change reality to be the result of a call to the say_hi function of the person module in the src folder instead of a call to the say_hi function in test_person.py

    117def test_mary():
    118    first_name = 'mary'
    119    last_name = 'public'
    120    sex = 'F'
    121    year_of_birth = 2000
    122
    123    reality = src.person.factory(
    124        first_name=first_name,
    125        last_name=last_name,
    126        sex=sex,
    127        year_of_birth=year_of_birth,
    128    )
    129    my_expectation = (
    130        f'{first_name}, {last_name},'
    131        f' {sex}, {year_of_birth}'
    132    )
    133    assert reality == my_expectation
    134
    135    # reality = say_hi(
    136    reality = src.person.say_hi(
    137        first_name=first_name,
    138        last_name=last_name,
    139        year_of_birth=year_of_birth,
    140    )
    141    my_expectation = (
    142        f'Hi, my name is {first_name}'
    143        # ' smith and I am'
    144        f' {last_name} and I am'
    145        f' {2026-year_of_birth}.'
    146    )
    147    assert reality == my_expectation
    148
    149
    150# Exceptions seen
    

    the terminal is my friend, and shows AttributeError

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

    because there is nothing named say_hi in the person.py file in the src folder.

  • I add a copy of the say_hi function without the commented lines to person.py

     1def factory(
     2    first_name, last_name,
     3    sex, year_of_birth
     4):
     5    return (
     6        f'{first_name}, {last_name},'
     7        f' {sex}, {year_of_birth}'
     8    )
     9
    10
    11def say_hi(
    12    first_name, last_name,
    13    year_of_birth
    14):
    15    age = 2026 - year_of_birth
    16
    17    return (
    18        f'Hi, my name is {first_name}'
    19        f' {last_name} and I am {age}.'
    20    )
    

    the test passes.

  • I remove the commented lines from test_mary in test_person.py

    117def test_mary():
    118    first_name = 'mary'
    119    last_name = 'public'
    120    sex = 'F'
    121    year_of_birth = 2000
    122
    123    reality = src.person.factory(
    124        first_name=first_name,
    125        last_name=last_name,
    126        sex=sex,
    127        year_of_birth=year_of_birth,
    128    )
    129    my_expectation = (
    130        f'{first_name}, {last_name},'
    131        f' {sex}, {year_of_birth}'
    132    )
    133    assert reality == my_expectation
    134
    135    reality = src.person.say_hi(
    136        first_name=first_name,
    137        last_name=last_name,
    138        year_of_birth=year_of_birth,
    139    )
    140    my_expectation = (
    141        f'Hi, my name is {first_name}'
    142        f' {last_name} and I am'
    143        f' {2026-year_of_birth}.'
    144    )
    145    assert reality == my_expectation
    146
    147
    148# Exceptions seen
    149# AssertionError
    150# NameError
    151# TypeError
    152# AttributeError
    
  • I change reality for test_john to be the result of a call to the say_hi function of the person module in the src folder

     85def test_john():
     86    first_name = 'john'
     87    last_name = 'smith'
     88    sex = 'M'
     89    year_of_birth = 1580
     90
     91    reality = src.person.factory(
     92        first_name=first_name,
     93        last_name=last_name,
     94        sex=sex,
     95        year_of_birth=year_of_birth,
     96    )
     97    my_expectation = (
     98        f'{first_name}, {last_name},'
     99        f' {sex}, {year_of_birth}'
    100    )
    101    assert reality == my_expectation
    102
    103    # reality = say_hi(
    104    reality = src.person.say_hi(
    105        first_name=first_name,
    106        last_name=last_name,
    107        year_of_birth=year_of_birth,
    108    )
    109    my_expectation = (
    110        # 'Hi, my name is jane'
    111        f'Hi, my name is {first_name}'
    112        f' {last_name} and I am'
    113        f' {2026-year_of_birth}.'
    114    )
    115    assert reality == my_expectation
    116
    117
    118def test_mary():
    

    the test is still green.

  • I remove the commented lines from test_john

     85def test_john():
     86    first_name = 'john'
     87    last_name = 'smith'
     88    sex = 'M'
     89    year_of_birth = 1580
     90
     91    reality = src.person.factory(
     92        first_name=first_name,
     93        last_name=last_name,
     94        sex=sex,
     95        year_of_birth=year_of_birth,
     96    )
     97    my_expectation = (
     98        f'{first_name}, {last_name},'
     99        f' {sex}, {year_of_birth}'
    100    )
    101    assert reality == my_expectation
    102
    103    reality = src.person.say_hi(
    104        first_name=first_name,
    105        last_name=last_name,
    106        year_of_birth=year_of_birth,
    107    )
    108    my_expectation = (
    109        f'Hi, my name is {first_name}'
    110        f' {last_name} and I am'
    111        f' {2026-year_of_birth}.'
    112    )
    113    assert reality == my_expectation
    114
    115
    116def test_mary():
    
  • I change reality for test_joe to be the result of a call to the say_hi function of the person module in the src folder

    23def test_joe():
    24    first_name = 'joe'
    25    last_name = 'blow'
    26    sex = 'M'
    27    year_of_birth = 1996
    28
    29    reality = src.person.factory(
    30        first_name=first_name,
    31        last_name=last_name,
    32        sex=sex,
    33        year_of_birth=year_of_birth,
    34    )
    35    my_expectation = (
    36        f'{first_name}, {last_name},'
    37        f' {sex}, {year_of_birth}'
    38    )
    39    assert reality == my_expectation
    40
    41    # reality = say_hi(
    42    reality = src.person.say_hi(
    43        first_name=first_name,
    44        last_name=last_name,
    45        year_of_birth=year_of_birth,
    46    )
    47    my_expectation = (
    48        f'Hi, my name is {first_name}'
    49        f' {last_name} and I am'
    50        f' {2026-year_of_birth}.'
    51    )
    52    assert reality == my_expectation
    53
    54
    55def test_jane():
    

    the test is still green.

  • I remove the commented line from test_joe

    23def test_joe():
    24    first_name = 'joe'
    25    last_name = 'blow'
    26    sex = 'M'
    27    year_of_birth = 1996
    28
    29    reality = src.person.factory(
    30        first_name=first_name,
    31        last_name=last_name,
    32        sex=sex,
    33        year_of_birth=year_of_birth,
    34    )
    35    my_expectation = (
    36        f'{first_name}, {last_name},'
    37        f' {sex}, {year_of_birth}'
    38    )
    39    assert reality == my_expectation
    40
    41    reality = src.person.say_hi(
    42        first_name=first_name,
    43        last_name=last_name,
    44        year_of_birth=year_of_birth,
    45    )
    46    my_expectation = (
    47        f'Hi, my name is {first_name}'
    48        f' {last_name} and I am'
    49        f' {2026-year_of_birth}.'
    50    )
    51    assert reality == my_expectation
    52
    53
    54def test_jane():
    
  • I change reality for test_jane to be the result of a call to the say_hi function of the person module in the src folder

    54def test_jane():
    55    first_name = 'jane'
    56    last_name = 'doe'
    57    sex = 'F'
    58    year_of_birth = 1991
    59
    60    reality = src.person.factory(
    61        first_name=first_name,
    62        last_name=last_name,
    63        sex=sex,
    64        year_of_birth=year_of_birth,
    65    )
    66    my_expectation = (
    67        f'{first_name}, {last_name},'
    68        f' {sex}, {year_of_birth}'
    69    )
    70    assert reality == my_expectation
    71
    72    # reality = say_hi(
    73    reality = src.person.say_hi(
    74        first_name=first_name,
    75        last_name=last_name,
    76        year_of_birth=year_of_birth,
    77    )
    78    my_expectation = (
    79        f'Hi, my name is {first_name}'
    80        f' {last_name} and I am'
    81        f' {2026-year_of_birth}.'
    82    )
    83    assert reality == my_expectation
    84
    85
    86def test_john():
    

    the test is still green.

  • I remove the commented line from test_jane

    54def test_jane():
    55    first_name = 'jane'
    56    last_name = 'doe'
    57    sex = 'F'
    58    year_of_birth = 1991
    59
    60    reality = src.person.factory(
    61        first_name=first_name,
    62        last_name=last_name,
    63        sex=sex,
    64        year_of_birth=year_of_birth,
    65    )
    66    my_expectation = (
    67        f'{first_name}, {last_name},'
    68        f' {sex}, {year_of_birth}'
    69    )
    70    assert reality == my_expectation
    71
    72    reality = src.person.say_hi(
    73        first_name=first_name,
    74        last_name=last_name,
    75        year_of_birth=year_of_birth,
    76    )
    77    my_expectation = (
    78        f'Hi, my name is {first_name}'
    79        f' {last_name} and I am'
    80        f' {2026-year_of_birth}.'
    81    )
    82    assert reality == my_expectation
    83
    84
    85def test_john():
    
  • I remove the say_hi function from test_person.py

    1import src.person
    2
    3
    4def test_joe():
    

    all the tests are still green because the calls that were made to the say_hi function that was in test_person.py are now to the say_hi function in person.py in the src folder. When src.person.say_hi is called, Python follows this path

    src
    └── person.py
        └── def say_hi(
                first_name, last_name,
                year_of_birth
            ):
                age = 2026 - year_of_birth
    
                return (
                    f'Hi, my name is {first_name}'
                    f' {last_name} and I am {age}.'
                )
    
  • I add a git commit message in the other terminal

    git commit -am 'add say_hi function'
    

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


test_person

Since the solutions are separate from the tests, I can write the programs that make the tests pass without looking at test_person.py.


RED: make it fail


  • I close test_person.py

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

    FAILED ...::test_joe - AttributeError:
        module 'src.person' has no attribute 'factory'
    FAILED ...::test_jane - AttributeError:
        module 'src.person' has no attribute 'factory'
    FAILED ...::test_john - AttributeError:
        module 'src.person' has no attribute 'factory'
    FAILED ...::test_mary - AttributeError:
        module 'src.person' has no attribute 'factory'
    =================== 4 failed in A.BCs ===================
    

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


GREEN: make it pass


  • I add the name to person.py

    1factory
    

    the terminal is my friend, and shows NameError

    NameError: name 'factory' is not defined
    
  • I point factory to None (the simplest object) 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 change factory to 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 this function definition does not allow any inputs, the parentheses are empty.

  • I add first_name in the parentheses so the function can take input

    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 'last_name'.
               Did you mean 'first_name'?
    

    because the function definition now only allows one input (first_name) and it was called with a keyword argument (last_name).

  • I add last_name to the parentheses

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

    the terminal is my friend, and shows TypeError

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

    because the function definition only allows two inputs (first_name and last_name) and it was called with a keyword argument (sex).

  • I add sex to the parentheses

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

    the terminal is my friend, and shows TypeError

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

    because the function definition only allows three inputs (first_name, last_name, sex) and it was called with a keyword argument (year_of_birth).

  • I add year_of_birth to the parentheses

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

    the terminal is my friend, and shows AssertionError

    AssertionError: assert None == 'mary, public, F, 2000'
    
  • I change the return statement to match the expectation of the test

     1# factory
     2# factory = None
     3# def factory():
     4# def factory(first_name):
     5# def factory(first_name, last_name):
     6# def factory(first_name, last_name, sex):
     7def factory(
     8        first_name, last_name,
     9        sex, year_of_birth
    10    ):
    11    return 'mary, public, F, 2000'
    

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.person'
                    has no attribute 'say_hi'
    
  • I add the name

     1say_hi
     2
     3
     4# factory
     5# factory = None
     6# def factory():
     7# def factory(first_name):
     8# def factory(first_name, last_name):
     9# def factory(first_name, last_name, sex):
    10def factory(
    11        first_name, last_name,
    12        sex, year_of_birth
    13    ):
    14    return 'mary, public, F, 2000'
    

    the terminal is my friend, and shows NameError

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

    1# say_hi
    2say_hi = None
    3
    4
    5# factory
    

    the terminal is my friend, and shows TypeError

    TypeError: 'NoneType' object is not callable
    

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

  • I make say_hi a function to make it callable

    1# say_hi
    2# say_hi = None
    3def say_hi():
    4    return None
    5
    6
    7# factory
    

    the terminal is my friend, and shows TypeError

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

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

  • I add first_name in the parentheses

    1# say_hi
    2# say_hi = None
    3# def say_hi():
    4def say_hi(first_name):
    5    return None
    6
    7
    8# factory
    

    the terminal is my friend, and shows TypeError

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

    because the function definition now only allows one input (first_name) and it was called with a keyword argument (last_name).

  • I add last_name to the parentheses

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

    the terminal is my friend, and shows TypeError

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

    because the function definition only allows two inputs (first_name and last_name) and it was called with a keyword argument (year_of_birth).

  • I add year_of_birth to the parentheses

     1# say_hi
     2# say_hi = None
     3# def say_hi():
     4# def say_hi(first_name):
     5# def say_hi(first_name, last_name):
     6def say_hi(
     7    first_name, last_name, year_of_birth
     8):
     9    return None
    10
    11
    12# factory
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert None
        == 'Hi, my name is mary public and I am 26.'
    
  • I use ctrl/command+c (Windows & Linux/MacOS) on the keyboard to copy the string from the terminal and ctrl/command+v to paste it to replace None in the return statement

     1# say_hi
     2# say_hi = None
     3# def say_hi():
     4# def say_hi(first_name):
     5# def say_hi(first_name, last_name):
     6def say_hi(
     7    first_name, last_name, year_of_birth
     8):
     9    # return None
    10    return 'Hi, my name is mary public and I am 26.'
    11
    12
    13# factory
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'mary, public, F, 2000'
                        == 'john, smith, M, 1580'
    

    because the factory function always returns 'mary, public, F, 2000' and this test expects 'john, smith, M, 1580'.

  • I change the return statement of the factory function to see the difference between the input and the expected output (remember the identity function?)

    13# factory
    14# factory = None
    15# def factory():
    16# def factory(first_name):
    17# def factory(first_name, last_name):
    18# def factory(first_name, last_name, sex):
    19def factory(
    20        first_name, last_name,
    21        sex, year_of_birth
    22    ):
    23    # return 'mary, public, F, 2000'
    24    return first_name, last_name, sex, year_of_birth
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert ('mary', 'public', 'F', 2000)
                         == 'mary, public, F, 2000'
    

    the first_name, last_name, sex and year_of_birth are part of the output.

  • I change the return statement to an f-string with those values

    13# factory
    14# factory = None
    15# def factory():
    16# def factory(first_name):
    17# def factory(first_name, last_name):
    18# def factory(first_name, last_name, sex):
    19def factory(
    20        first_name, last_name,
    21        sex, year_of_birth
    22    ):
    23    # return 'mary, public, F, 2000'
    24    # return first_name, last_name, sex, year_of_birth
    25    return (
    26        f'{first_name}, {last_name},'
    27        f' {sex}, {year_of_birth}'
    28    )
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'Hi, my name ... and I am 26.'
                        == 'Hi, my name ...and I am 446.'
    

    because the say_hi function always returns 'Hi, my name is mary public and I am 26.' and this test expects 'Hi, my name is john smith and I am 446.'

  • I change the return statement of the say_hi function to see the difference between the input and the expected output

     1# say_hi
     2# say_hi = None
     3# def say_hi():
     4# def say_hi(first_name):
     5# def say_hi(first_name, last_name):
     6def say_hi(
     7    first_name, last_name, year_of_birth
     8):
     9    # return None
    10    # return 'Hi, my name is mary public and I am 26.'
    11    return first_name, last_name, year_of_birth
    12
    13
    14# factory
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert ('mary', 'public', 'F', 2000)
                         == 'mary, public, F, 2000'
    

    the first_name and last_name are part of the output.

  • I change the return statement to an f-string with the input values

     1# say_hi
     2# say_hi = None
     3# def say_hi():
     4# def say_hi(first_name):
     5# def say_hi(first_name, last_name):
     6def say_hi(
     7    first_name, last_name, year_of_birth
     8):
     9    # return None
    10    # return 'Hi, my name is mary public and I am 26.'
    11    # return first_name, last_name, year_of_birth
    12    return (
    13        f'Hi, my name is {first_name}'
    14        f' {last_name} and I am {year_of_birth}.'
    15    )
    16
    17
    18# factory
    

    the terminal is my friend, and shows AssertionError

    AssertionError: assert 'Hi, my name ...nd I am 2000.'
                        == 'Hi, my name ... and I am 26.'
    

    It looks like 26 is the age.

  • I change the year_of_birth value to a calculation of the age

     1# say_hi
     2# say_hi = None
     3# def say_hi():
     4# def say_hi(first_name):
     5# def say_hi(first_name, last_name):
     6def say_hi(
     7    first_name, last_name, year_of_birth
     8):
     9    # return None
    10    # return 'Hi, my name is mary public and I am 26.'
    11    # return first_name, last_name, year_of_birth
    12    return (
    13        f'Hi, my name is {first_name}'
    14        # f' {last_name} and I am {year_of_birth}.'
    15        f' {last_name} and I am'
    16        f' {2026-year_of_birth}.'
    17    )
    18
    19
    20# factory
    

    all tests are green!

  • I remove the commented lines

     1def say_hi(
     2    first_name, last_name, year_of_birth
     3):
     4    return (
     5        f'Hi, my name is {first_name}'
     6        f' {last_name} and I am'
     7        f' {2026-year_of_birth}.'
     8    )
     9
    10
    11def factory(
    12        first_name, last_name,
    13        sex, year_of_birth
    14    ):
    15    return (
    16        f'{first_name}, {last_name},'
    17        f' {sex}, {year_of_birth}'
    18    )
    

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 write one function that makes a person when given first_name, last_name, sex and year_of_birth so I do not have to make one function for each person.

  • I also ran tests to make another function that uses f-strings to make a string that represents the person I make saying hi when I give it first_name, last_name, and year_of_birth.

  • I saw the following Exceptions

  • My tests and solutions have a few problems,

    • Each test is basically the same two tests, there has to be a way that I can use one test for all the people.

    • The factory and say_hi functions use three of the same inputs

      • first_name

      • last_name

      • year_of_birth

      There has to be a better way, where I can give those values once, and get a representation for a person when I call the factory function and a message when I call the say_hi function.

For now, I am going to clean up the functions project so the tests and solutions are in separate files.


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 me separate the tests and functions in the functions project?


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.