classes


preview

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

 1import datetime
 2import random
 3import src.person
 4import unittest
 5
 6
 7def choose(*choices):
 8    return random.choice(choices)
 9
10
11def this_year():
12    return datetime.datetime.now().year
13
14
15class TestPerson(unittest.TestCase):
16
17    def setUp(self):
18        self.random_year_of_birth = random.randint(
19            this_year()-120, this_year()
20        )
21        self.random_first_name = choose('jane', 'joe', 'john', 'person')
22
23    def test_factory_takes_keyword_arguments(self):
24        a_person = dict(
25            first_name=self.random_first_name,
26            last_name=choose('doe', 'smith', 'blow', 'public'),
27            sex=choose('F', 'M'),
28        )
29
30        self.assertEqual(
31            src.person.factory(
32                **a_person,
33                year_of_birth=self.random_year_of_birth,
34            ),
35            dict(
36                **a_person,
37                age=this_year()-self.random_year_of_birth,
38            )
39        )
40
41    def test_factory_w_default_arguments(self):
42        self.assertEqual(
43            src.person.factory(
44                first_name=self.random_first_name,
45                year_of_birth=self.random_year_of_birth,
46            ),
47            dict(
48                first_name=self.random_first_name,
49                last_name='doe',
50                sex='M',
51                age=this_year()-self.random_year_of_birth,
52            )
53        )
54
55
56# Exceptions seen
57# AssertionError
58# NameError
59# AttributeError
60# TypeError
61# SyntaxError

requirements

how to make a person


open the project

  • I change directory to the person folder

    cd person
    

    the terminal shows I am in the person folder

    .../pumping_python/person
    
  • I activate the virtual environment

    source .venv/bin/activate
    

    Attention

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

    .venv/scripts/activate.ps1
    

    the terminal shows

    (.venv) .../pumping_python/person
    
  • I use pytest-watch to run the tests

    pytest-watch
    

    the terminal shows

    rootdir: .../pumping_python/person
    collected 2 items
    
    tests/test_person.py ..                                             [100%]
    
    ============================ 2 passed in X.YZs =============================
    
  • I hold ctrl on the keyboard and click on tests/test_person.py to open it in the editor


test_factory_person_greeting

I have a function that takes in first name, last name, sex and year of birth for a person and returns a dictionary with the first name, last name, sex and age based on the year of birth.

What if I want the person to send a message about themselves. How would I do that? I can write a function that takes in a person and returns a message

  • I add a new test where I make a person

    47            dict(
    48                first_name=self.random_first_name,
    49                last_name='doe',
    50                sex='M',
    51                age=this_year()-self.random_year_of_birth,
    52            )
    53        )
    54
    55    def test_factory_person_greeting(self):
    56        joe = src.person.factory(
    57            first_name='joe',
    58            last_name='blow',
    59            year_of_birth=1996,
    60        )
    61
    62
    63# Exceptions seen
    

    the factory function will give joe a default value of 'M' for sex because I did not give a value for it

  • I add another person

    55    def test_factory_person_greeting(self):
    56        joe = src.person.factory(
    57            first_name='joe',
    58            last_name='blow',
    59            year_of_birth=1996,
    60        )
    61        jane = src.person.factory(
    62            first_name='jane',
    63            sex='F',
    64            year_of_birth=1991,
    65        )
    66
    67
    68# Exceptions
    

    the factory function will give jane a default value of doe for last_name because I did not give a value for it

  • I add one more person

    61        jane = src.person.factory(
    62            first_name='jane',
    63            sex='F',
    64            year_of_birth=1991,
    65        )
    66        john = src.person.factory(
    67            first_name='john',
    68            last_name='smith',
    69            year_of_birth=1580,
    70        )
    71
    72
    73# Exceptions seen
    

RED: make it fail


I add a for loop with the subTest method and an assertion

66        john = src.person.factory(
67            first_name='john',
68            last_name='smith',
69            year_of_birth=1580,
70        )
71
72        for person in (joe, jane, john):
73            with self.subTest(name=person.get('first_name')):
74                self.assertEqual(
75                    src.person.hello(person),
76                    None
77                )
78
79
80# Exceptions seen

the terminal shows AttributeError for each one of the people

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

person.py does not have a function named hello


GREEN: make it pass


  • I open person.py in the editor

  • I add the function to person.py

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

    the terminal shows TypeError

    TypeError: hello() takes 0 positional arguments but 1 was given
    
  • I add a name to the definition

    16def hello(person):
    17    return None
    

    the test passes


REFACTOR: make it better


I want the hello function to return a message for the person I give as input

  • I change the expectation in test_factory_person_greeting in test_person.py with an f-string like I did in how to pass values

    72        for person in (joe, jane, john):
    73            with self.subTest(name=person.get('first_name')):
    74                self.assertEqual(
    75                    src.person.hello(person),
    76                    (
    77                        f'Hi, my name is {person.get("first_name")} '
    78                        f'{person.get("last_name")} '
    79                        f'and I am {person.get("age")}'
    80                    )
    81                )
    

    the terminal shows AssertionError

    AssertionError: None != 'Hi, my name is john smith and I am 446'
    
  • I copy the value from the terminal and paste it in the return statement in person.py

    16def hello(person):
    17    return 'Hi, my name is john smith and I am 446'
    

    the terminal shows AssertionError

    E               - Hi, my name is john smith and I am 446
    E               + Hi, my name is jane doe and I am 35
    

    the first name, last name and ages are different

  • I change the string to an f-string with the value for first_name

    16def hello(person):
    17    return f'Hi, my name is {person.get("first_name")} smith and I am 446'
    

    the terminal shows AssertionError

    E               - Hi, my name is jane smith and I am 446
    E               ?                     ^^^^^          ^^^
    E               + Hi, my name is jane doe and I am 35
    E               ?                     ^^^          ^^
    

    the first name is the same, the last name and ages are different

  • I change the return statement

    16def hello(person):
    17    return (
    18        f'Hi, my name is {person.get("first_name")} '
    19        f'{person.get("last_name")} and I am 446'
    20    )
    

    the terminal shows AssertionError

    E               - Hi, my name is jane doe and I am 446
    E               ?                                  ^^^
    E               + Hi, my name is jane doe and I am 35
    E               ?                                  ^^
    

    the age is the only thing that is different now

  • I add the age to the return statement

    16def hello(person):
    17    return (
    18        f'Hi, my name is {person.get("first_name")} '
    19        f'{person.get("last_name")} '
    20        f'and I am {person.get("age")}'
    21    )
    

    the test passes

The solution works, it needs different functions - one to make the person and one to make the message.


test_classy_person_greeting

I can also make a person with a class


RED: make it fail


I add a new test to test_person.py

72        for person in (joe, jane, john):
73            with self.subTest(name=person.get('first_name')):
74                self.assertEqual(
75                    src.person.hello(person),
76                    (
77                        f'Hi, my name is {person.get("first_name")} '
78                        f'{person.get("last_name")} '
79                        f'and I am {person.get("age")}'
80                    )
81                )
82
83    def test_classy_person_greeting(self):
84        joe = src.person.Person(
85            first_name='joe',
86            last_name='blow',
87            year_of_birth=1996,
88        )
89
90
91# Exceptions seen

the terminal shows AttributeError

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

GREEN: make it pass


  • I add a class in person.py

    16def hello(person):
    17    return (
    18        f'Hi, my name is {person.get("first_name")} '
    19        f'{person.get("last_name")} '
    20        f'and I am {person.get("age")}'
    21    )
    22
    23
    24class Person:
    25
    26    pass
    

    the terminal shows TypeError

    TypeError: Person() takes no arguments
    
  • classes have a constructor method that is used to make copies of the class. I add it

    24class Person:
    25
    26    def __init__():
    27        return None
    

    the terminal shows TypeError

    TypeError: Person.__init__() got an unexpected keyword argument 'first_name'
    
  • I add the name

    24class Person:
    25
    26    def __init__(first_name):
    27        return None
    

    the terminal shows TypeError

    TypeError: Person.__init__() got multiple values for argument 'first_name'
    
  • The __init__ method takes the class as the first argument. I add self the way I do with all the tests in the book

    24class Person:
    25
    26    def __init__(self, first_name):
    27        return None
    

    the terminal shows TypeError

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

    I have seen this before, so far it is the same as making the factory function

  • I add last_name

    26    def __init__(self, first_name, last_name):
    27        return None
    

    the terminal shows TypeError

    TypeError: Person.__init__() got an unexpected keyword argument 'year_of_birth'
    

    still the same as making the factory function

  • I add year_of_birth to the definition

    24class Person:
    25
    26    def __init__(
    27            self, first_name, last_name,
    28            year_of_birth,
    29        ):
    30        return
    

    the test passes


REFACTOR: make it better


  • I add the next person to test_classy_person_greeting

    83    def test_classy_person_greeting(self):
    84        joe = src.person.Person(
    85            first_name='joe',
    86            last_name='blow',
    87            year_of_birth=1996,
    88        )
    89        jane = src.person.Person(
    90            first_name='jane',
    91            sex='F',
    92            year_of_birth=1991,
    93        )
    

    the terminal shows TypeError

    TypeError: Person.__init__() got an unexpected keyword argument 'sex'
    
  • I add sex to the __init__ method in person.py

    26    def __init__(
    27            self, first_name, last_name,
    28            year_of_birth, sex,
    29        ):
    30        return None
    

    the terminal shows TypeError

    TypeError: Person.__init__() missing 1 required positional argument: 'sex'
    

    I did not provide a value for sex when I made joe, and the factory function has a default value of 'M' for it

  • I add a default value for sex in the __init__ method

    26    def __init__(
    27            self, first_name, last_name,
    28            year_of_birth, sex=None,
    29        ):
    30        return None
    

    the terminal shows SyntaxError

    SyntaxError: parameter without a default follows parameter with a default
    

    I cannot put a parameter that does NOT have a default value after a parameter that has a default value

  • I add a default value for year_of_birth

    26def __init__(
    27        self, first_name, last_name=None,
    28        year_of_birth=None, sex=None,
    29    ):
    30    return None
    

    the test passes


  • I add the next person

     89        jane = src.person.Person(
     90            first_name='jane',
     91            sex='F',
     92            year_of_birth=1991,
     93        )
     94        john = src.person.Person(
     95            first_name='john',
     96            last_name='smith',
     97            year_of_birth=1580,
     98        )
     99
    100
    101# Exceptions seen
    

    the test is still green

  • I copy the for loop with the assertion from test_factory_person_greeting and paste it in test_classy_person_greeting

     94        john = src.person.Person(
     95            first_name='john',
     96            last_name='smith',
     97            year_of_birth=1580,
     98        )
     99
    100        for person in (joe, jane, john):
    101            with self.subTest(name=person.get('first_name')):
    102                self.assertEqual(
    103                    src.person.hello(person),
    104                    (
    105                        f'Hi, my name is {person.get("first_name")} '
    106                        f'{person.get("last_name")} '
    107                        f'and I am {person.get("age")}'
    108                    )
    109                )
    110
    111
    112# Exceptions seen
    

    the terminal shows AttributeError

    AttributeError: 'Person' object has no attribute 'get'
    

    the Person class does not have a method named get

  • I can use class attributes directly with no need for a get method. I change the line for the subTest method

    100        for person in (joe, jane, john):
    101            with self.subTest(name=person.first_name):
    102                self.assertEqual(
    

    the terminal shows AttributeError

    AttributeError: 'Person' object has no attribute 'first_name'
    
  • I add a class attribute to the __init__ method of the Person class in person.py

    26    def __init__(
    27            self, first_name, last_name=None,
    28            year_of_birth=None, sex=None,
    29        ):
    30        self.first_name = first_name
    31        return None
    

    the terminal shows AttributeError

    AttributeError: 'Person' object has no attribute 'get'
    

    the test calls the hello function which takes in a dictionary and calls the get method, the Person object does not have a get method

  • I can call methods from outside a class the way I use a class attribute. I change the call in the assertion in test_person.py

    102                self.assertEqual(
    103                    person.hello(person),
    104                    (
    105                        f'Hi, my name is {person.get("first_name")} '
    106                        f'{person.get("last_name")} '
    107                        f'and I am {person.get("age")}'
    108                    )
    109                )
    

    the terminal shows AttributeError

    AttributeError: 'Person' object has no attribute 'hello'
    
  • I add the method to the Person class in person.py

    26    def __init__(
    27            self, first_name, last_name=None,
    28            year_of_birth=None, sex=None,
    29        ):
    30        self.first_name = first_name
    31        return None
    32
    33    def hello():
    34        return None
    

    the terminal shows TypeError

    TypeError: Person.hello() takes 0 positional arguments but 2 were given
    

    the test calls the method with one input and the definition takes no input

  • I add the staticmethod decorator to the method because it does not use anything in the class

    26    def __init__(
    27            self, first_name, last_name=None,
    28            year_of_birth=None, sex=None,
    29        ):
    30        self.first_name = first_name
    31        return None
    32
    33    @staticmethod
    34    def hello():
    35        return None
    

    the terminal shows TypeError

    TypeError: Person.hello() takes 0 positional arguments but 1 was given
    
  • I add a name to the definition

    33    @staticmethod
    34    def hello(person):
    35        return None
    

    the terminal shows AttributeError

    AttributeError: 'Person' object has no attribute 'get'
    
  • I change the assertion in test_classy_person_greeting in test_person.py to use the first_name class attribute

    102                self.assertEqual(
    103                    person.hello(person),
    104                    (
    105                        f'Hi, my name is {person.first_name} '
    106                        f'{person.get("last_name")} '
    107                        f'and I am {person.get("age")}'
    108                    )
    109                )
    

    the terminal shows AttributeError

    AttributeError: 'Person' object has no attribute 'get'
    
  • I make the same change for the last name

    102                self.assertEqual(
    103                    person.hello(person),
    104                    (
    105                        f'Hi, my name is {person.first_name} '
    106                        f'{person.last_name} '
    107                        f'and I am {person.get("age")}'
    108                    )
    109                )
    

    the terminal shows AttributeError

    AttributeError: 'Person' object has no attribute 'last_name'. Did you mean: 'first_name'?
    
  • I add a class attribute for last_name to the __init__ method of the Person class in person.py

    26    def __init__(
    27            self, first_name, last_name=None,
    28            year_of_birth=None, sex=None,
    29        ):
    30        self.first_name = first_name
    31        self.last_name = last_name
    32        return None
    33
    34    @staticmethod
    35    def hello(person):
    

    the terminal shows AttributeError

    AttributeError: 'Person' object has no attribute 'get'
    
  • I want to use a method to calculate the age. I change the assertion in test_person.py

    102                self.assertEqual(
    103                    person.hello(person),
    104                    (
    105                        f'Hi, my name is {person.first_name} '
    106                        f'{person.last_name} '
    107                        f'and I am {person.get_age()}'
    108                    )
    109                )
    

    the terminal shows AttributeError

    AttributeError: 'Person' object has no attribute 'get_age'
    
  • I add the method to the Person class in person.py

    34    @staticmethod
    35    def hello(person):
    36        return None
    37
    38    def get_age():
    39        return None
    

    the terminal shows TypeError

    TypeError: Person.get_age() takes 0 positional arguments but 1 was given
    
  • I add the staticmethod decorator

    34    @staticmethod
    35    def hello(person):
    36        return None
    37
    38    @staticmethod
    39    def get_age():
    40        return None
    

    the terminal shows AssertionError

    AssertionError: None != 'Hi, my name is john smith and I am None'
    
  • I copy and paste the string in the return statement of the hello method

    38    @staticmethod
    39    def hello(person):
    40        return 'Hi, my name is john smith and I am None'
    

    the terminal shows AssertionError

    E               - Hi, my name is john smith and I am None
    E               ?                  - ^^^^^^
    E               + Hi, my name is jane None and I am None
    E               ?                 +++++  ^
    

    the value for first name and last name are different

  • I change the string in the return statement to an f-string with the value for first_name

    34    @staticmethod
    35    def hello(person):
    36        return f'Hi, my name is {person.first_name} smith and I am None'
    

    the terminal shows AssertionError

    E               - Hi, my name is jane smith and I am None
    E               ?                     ^^^^^
    E               + Hi, my name is jane None and I am None
    E               ?                     ^^^^
    

    the values for the last name are different

  • I add it to the f-string

    34    @staticmethod
    35    def hello(person):
    36        return (
    37            f'Hi, my name is {person.first_name} '
    38            f'{person.last_name} and I am None'
    39        )
    

    the test passes


  • I can call a method that belongs to a class without the need to pass in the class as input since I can use the class with self. I remove the repetition of the Person object in the call to the hello method in test_person.py

    100        for person in (joe, jane, john):
    101            with self.subTest(name=person.first_name):
    102                self.assertEqual(
    103                    person.hello(),
    104                    (
    105                        f'Hi, my name is {person.first_name} '
    106                        f'{person.last_name} '
    107                        f'and I am {person.get_age()}'
    108                    )
    109                )
    110
    111
    112# Exceptions seen
    

    the terminal shows TypeError

    TypeError: Person.hello() missing 1 required positional argument: 'person'
    
  • I remove the staticmethod decorator from the hello method in person.py

    26    def __init__(
    27            self, first_name, last_name=None,
    28            year_of_birth=None, sex=None,
    29        ):
    30        self.first_name = first_name
    31        self.last_name = last_name
    32        return None
    33
    34    def hello(person):
    35        return (
    36            f'Hi, my name is {person.first_name} '
    37            f'{person.last_name} and I am None'
    38        )
    

    the test passes.

    This works because person in the parentheses is for the Person class that the hello method is part of.

    When I wrapped the hello function with the staticmethod decorator, it was a function that did not use other parts (class attributes and methods) of the class it belongs to.

  • I use the Rename Symbol feature of the Integrated Development Environment (IDE) to change the name of the input parameter from person to self to match Python convention

    34    def hello(self):
    35        return (
    36            f'Hi, my name is {self.first_name} '
    37            f'{self.last_name} and I am None'
    38        )
    

    the test is still green, and there is a problem with the last name and age.


test_update_factory_person_year_of_birth


I made a person named john in test_factory_person_greeting and test_classy_person_greeting with a year of birth of 1580.

Maybe I made a mistake when typing his age and typed 5 instead of 9. How would I change the year of birth of a person made with the factory function if I cannot change the original year of birth?

  • I could try updating the year_of_birth

  • I could try making a function that takes a person and a new year of birth as inputs, and returns the person with the correct age

  • I could make a new factory function that returns a dictionary with year_of_birth as a key which allows me to change it, then make another function that calculates the age from the returned dictionary - this sounds like a lot of work, I would also have to rewrite the tests. No, thank you

  • I could make a class


RED: make it fail


I cannot update the year_of_birth key because the function returns a dictionary that does not have a year_of_birth key. I add a new test

100          for person in (joe, jane, john):
101              with self.subTest(name=person.first_name):
102                  self.assertEqual(
103                      person.hello(),
104                      (
105                          f'Hi, my name is {person.first_name} '
106                          f'{person.last_name} '
107                          f'and I am {person.get_age()}'
108                      )
109                  )
110
111      def test_update_factory_person_year_of_birth(self):
112          person = src.person.factory(
113              first_name='john',
114              last_name='smith',
115              year_of_birth=1580,
116          )
117          self.assertEqual(person.get('age', 0))
118
119
120  # Exceptions seen

the terminal shows AssertionError

AssertionError: 446 != 0

GREEN: make it passs


I change the expectation

117        self.assertEqual(person.get('age'), 446)

the test passes


REFACTOR: make it better


  • I try to use the year_of_birth key

    117        self.assertEqual(person.get('age'), 446)
    118
    119        person['year_of_birth']
    120
    121
    122# Exceptions seen
    

    the terminal shows KeyError

    KeyError: 'year_of_birth'
    

    there is no year_of_birth key in the dictionary returned by the factory function

  • I add KeyError to the list of Exceptions seen

    122# Exceptions seen
    123# AssertionError
    124# NameError
    125# AttributeError
    126# TypeError
    127# SyntaxError
    128# KeyError
    
  • I add assertRaises

    117        self.assertEqual(person.get('age'), 446)
    118
    119        with self.assertRaises(KeyError):
    120            person['year_of_birth']
    121
    122
    123# Exceptions seen
    

    the test passes

  • I add a new value for year_of_birth with the setdefault method

    119        with self.assertRaises(KeyError):
    120            person['year_of_birth']
    121        self.assertEqual(
    122            person.setdefault('year_of_birth', 1980),
    123            None
    124        )
    125
    126
    127# Exceptions seen
    

    the terminal shows AssertionError

    AssertionError: 1980 != None
    
  • I change the expectation

    121        self.assertEqual(
    122            person.setdefault('year_of_birth', 1980),
    123            1980
    124        )
    

    the test passes because the dictionary now has a key named year_of_birth with the new value

  • I add an assertion for the age of john smith again

    121        self.assertEqual(
    122            person.setdefault('year_of_birth', 1980),
    123            1980
    124        )
    125        self.assertEqual(person.get('age'), 46)
    126
    127# Exceptions seen
    

    the terminal shows AssertionError

    AssertionError: 446 != 46
    

    the factory function uses the value for year_of_birth to calculate the age when it makes the dictionary.

    When I change the value or add the key, it does not do anything to the age. There has to be a better way

  • I change the expectation

    125self.assertEqual(person.get('age'), 446)
    

    the test passes


  • I can make a function that takes a person and a new year of birth as inputs, and returns the person with the correct age. I add an assertion

    125        self.assertEqual(person.get('age'), 446)
    126
    127        self.assertEqual(
    128            src.person.update_year_of_birth(person, 1980),
    129            dict(
    130                first_name=person.get('first_name'),
    131                last_name=person.get('last_name'),
    132                sex=person.get('sex'),
    133                age=this_year()-1980,
    134            )
    135        )
    136
    137
    138# Exceptions seen
    

    the terminal shows AttributeError

    AttributeError: module 'src.person' has no attribute 'update_year_of_birth'
    
  • I add the function to person.py

    16def hello(person):
    17    return (
    18        f'Hi, my name is {person.get("first_name")} '
    19        f'{person.get("last_name")} '
    20        f'and I am {person.get("age")}'
    21    )
    22
    23
    24def update_year_of_birth():
    25    return None
    26
    27
    28class Person:
    

    the terminal shows TypeError

    TypeError: update_year_of_birth() takes 0 positional arguments but 2 were given
    
  • I add the two names in parentheses

    24def update_year_of_birth(person, new_year_of_birth):
    25    return None
    

    the terminal shows AssertionError

    AssertionError: None != {'first_name': 'john', 'last_name': 'smith', 'sex': 'M', 'age': 46}
    
  • I change the return statement

    24def update_year_of_birth(person, new_year_of_birth):
    25    return factory(
    26        first_name=person.get('first_name'),
    27        last_name=person.get('last_name'),
    28        sex=person.get('sex'),
    29        year_of_birth=new_year_of_birth,
    30    )
    31
    32
    33class Person:
    

    the test passes


  • time to remove some duplication. I add a variable for the original year of birth in test_update_factory_person_year_of_birth in test_person.py

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113
    114        person = src.person.factory(
    

    the test is still green

  • I use the variable in the call to src.person.factory

    112        original_year_of_birth = 1580
    113
    114        person = src.person.factory(
    115            first_name='john',
    116            last_name='smith',
    117            # year_of_birth=1580,
    118            year_of_birth=original_year_of_birth,
    119        )
    

    still green

  • I use the variable in the first assertion for the age

    114        person = src.person.factory(
    115            first_name='john',
    116            last_name='smith',
    117            # year_of_birth=1580,
    118            year_of_birth=original_year_of_birth,
    119        )
    120        # self.assertEqual(person.get('age'), 446)
    121        self.assertEqual(
    122            person.get('age'),
    123            this_year()-original_year_of_birth
    124        )
    125
    126        with self.assertRaises(KeyError):
    

    green

  • I use the variable in the second assertion for the age

    126        with self.assertRaises(KeyError):
    127            person['year_of_birth']
    128        self.assertEqual(
    129            person.setdefault('year_of_birth', 1980),
    130            1980
    131        )
    132        # self.assertEqual(person.get('age'), 446)
    133        self.assertEqual(
    134            person.get('age'),
    135            this_year()-original_year_of_birth
    136        )
    137
    138        self.assertEqual(
    

    still green

  • I remove the commented lines

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113
    114        person = src.person.factory(
    115            first_name='john',
    116            last_name='smith',
    117            year_of_birth=original_year_of_birth,
    118        )
    119        self.assertEqual(
    120            person.get('age'),
    121            this_year()-original_year_of_birth
    122        )
    123
    124        with self.assertRaises(KeyError):
    125            person['year_of_birth']
    126        self.assertEqual(
    127            person.setdefault('year_of_birth', 1980),
    128            1980
    129        )
    130        self.assertEqual(
    131            person.get('age'),
    132            this_year()-original_year_of_birth
    133        )
    134
    135        self.assertEqual(
    136            src.person.update_year_of_birth(person, 1980),
    137            dict(
    138                first_name=person.get('first_name'),
    139                last_name=person.get('last_name'),
    140                sex=person.get('sex'),
    141                age=this_year()-1980,
    142            )
    143        )
    144
    145
    146# Exceptions seen
    

    the test is still green


  • I add a variable for the new year of birth

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113        new_year_of_birth = 1980
    114
    115        person = src.person.factory(
    

    still green

  • I use the variable in the assertion for the call to the setdefault method

    125          with self.assertRaises(KeyError):
    126              person['year_of_birth']
    127          self.assertEqual(
    128              # person.setdefault('year_of_birth', 1980),
    129              person.setdefault('year_of_birth', new_year_of_birth),
    130              # 1980
    131              new_year_of_birth
    132          )
    133          self.assertEqual(
    

    the test is still green

  • I use the variable in the last assertion

    138        self.assertEqual(
    139            # src.person.update_year_of_birth(person, 1980),
    140            src.person.update_year_of_birth(
    141                person,
    142                new_year_of_birth
    143            ),
    144            dict(
    145                first_name=person.get('first_name'),
    146                last_name=person.get('last_name'),
    147                sex=person.get('sex'),
    148                # age=this_year()-1980,
    149                age=this_year()-new_year_of_birth,
    150            )
    151        )
    152
    153
    154# Exceptions seen
    

    still green

  • I remove the commented lines

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113        new_year_of_birth = 1980
    114
    115        person = src.person.factory(
    116            first_name='john',
    117            last_name='smith',
    118            year_of_birth=original_year_of_birth,
    119        )
    120        self.assertEqual(
    121            person.get('age'),
    122            this_year()-original_year_of_birth
    123        )
    124
    125        with self.assertRaises(KeyError):
    126            person['year_of_birth']
    127        self.assertEqual(
    128            person.setdefault('year_of_birth', new_year_of_birth),
    129            new_year_of_birth
    130        )
    131        self.assertEqual(
    132            person.get('age'),
    133            this_year()-original_year_of_birth
    134        )
    135
    136        self.assertEqual(
    137            src.person.update_year_of_birth(
    138                person,
    139                new_year_of_birth
    140            ),
    141            dict(
    142                first_name=person.get('first_name'),
    143                last_name=person.get('last_name'),
    144                sex=person.get('sex'),
    145                age=this_year()-new_year_of_birth,
    146            )
    147        )
    148
    149
    150# Exceptions seen
    

    green


  • I add a variable for the original age calculation

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113        original_age = this_year() - original_year_of_birth
    114        new_year_of_birth = 1980
    
  • I use the variable in the first assertion for the age

    121        person = src.person.factory(
    122            first_name='john',
    123            last_name='smith',
    124            year_of_birth=original_year_of_birth,
    125        )
    126        self.assertEqual(
    127            person.get('age'),
    128            # this_year()-original_year_of_birth
    129            original_age
    130        )
    

    the test is still green

  • I use the variable in the second assertion for the age

    127        with self.assertRaises(KeyError):
    128            person['year_of_birth']
    129        self.assertEqual(
    130            person.setdefault('year_of_birth', new_year_of_birth),
    131            new_year_of_birth
    132        )
    133        self.assertEqual(
    134            person.get('age'),
    135            # this_year()-original_year_of_birth
    136            original_age
    137        )
    

    still green

  • I remove the commented lines

    111    def test_update_factory_person_year_of_birth(self):
    112        original_year_of_birth = 1580
    113        original_age = this_year() - original_year_of_birth
    114        new_year_of_birth = 1980
    115
    116        person = src.person.factory(
    117            first_name='john',
    118            last_name='smith',
    119            year_of_birth=original_year_of_birth,
    120        )
    121        self.assertEqual(
    122            person.get('age'),
    123            original_age
    124        )
    125
    126        with self.assertRaises(KeyError):
    127            person['year_of_birth']
    128        self.assertEqual(
    129            person.setdefault('year_of_birth', new_year_of_birth),
    130            new_year_of_birth
    131        )
    132        self.assertEqual(
    133            person.get('age'),
    134            original_age
    135        )
    136
    137        self.assertEqual(
    138            src.person.update_year_of_birth(
    139                person,
    140                new_year_of_birth
    141            ),
    142            dict(
    143                first_name=person.get('first_name'),
    144                last_name=person.get('last_name'),
    145                sex=person.get('sex'),
    146                age=this_year()-new_year_of_birth,
    147            )
    148        )
    149
    150
    151# Exceptions seen
    

I had to make a new person with the same first name, last name, sex and the new year of birth to change the year of birth. How would I solve this problem with a class?


test_update_classy_person_year_of_birth


RED: make it fail


I add a new test

142            dict(
143                first_name=person.get('first_name'),
144                last_name=person.get('last_name'),
145                sex=person.get('sex'),
146                age=this_year()-new_year_of_birth,
147            )
148        )
149
150    def test_update_classy_person_year_of_birth(self):
151        person = src.person.Person(
152            first_name='john',
153            last_name='smith',
154            year_of_birth=1580,
155        )
156        self.assertEqual(person.get_age(), 0)
157
158
159# Exceptions seen

the terminal shows AssertionError

AssertionError: None != 0

the get_age method returns None. I want it to return the difference between this year and the year of birth


GREEN: make it pass


  • I add a calculation to the get_age method in person.py

    49    @staticmethod
    50    def get_age():
    51        return this_year() - self.year_of_birth
    

    the terminal shows NameError

    NameError: name 'this_year' is not defined
    
  • I add the function at the top of the file

    1import datetime
    2
    3
    4def this_year():
    5    return datetime.datetime.today().year
    6
    7
    8def factory(
    

    the terminal shows NameError

    NameError: name 'self' is not defined
    
  • I add self to the definition of the get_age method parentheses

    53    @staticmethod
    54    def get_age(self):
    55        return this_year() - self.year_of_birth
    

    the terminal shows TypeError

    TypeError: Person.get_age() missing 1 required positional argument: 'self'
    
  • I remove the staticmethod decorator from the get_age method

    47    def hello(self):
    48        return (
    49            f'Hi, my name is {self.first_name} '
    50            f'{self.last_name} and I am None'
    51        )
    52
    53    def get_age(self):
    54        return this_year() - self.year_of_birth
    

    the terminal shows TypeError

    AttributeError: 'Person' object has no attribute 'year_of_birth'
    
  • I add a class attribute to the __init__ method

    37class Person:
    38
    39    def __init__(
    40            self, first_name, last_name=None,
    41            year_of_birth=None, sex=None,
    42        ):
    43        self.first_name = first_name
    44        self.last_name = last_name
    45        self.year_of_birth = year_of_birth
    46        return None
    47
    48    def hello(self):
    49        return (
    50            f'Hi, my name is {self.first_name} '
    51            f'{self.last_name} and I am {self.get_age()}'
    52        )
    53
    54    def get_age(self):
    55        return this_year() - self.year_of_birth
    

    the terminal shows AssertionError

    AssertionError: 446 != 0
    
  • I change the expectation in test_person.py

    156        self.assertEqual(person.get_age(), 446)
    

    the terminal shows AssertionError

    E               - Hi, my name is john smith and I am None
    E               ?                                    ^^^^
    E               + Hi, my name is john smith and I am 446
    E               ?                                    ^^^
    

    test_update_factory_person_year_of_birth expects a number, the hello method returns None

  • I add a call to the get_age method in the hello method in person.py

    48    def hello(self):
    49        return (
    50            f'Hi, my name is {self.first_name} '
    51            f'{self.last_name} and I am {self.get_age()}'
    52        )
    

    the test passes


REFACTOR: make it better


  • I use the this_year function in the factory function in person.py

     8def factory(
     9        first_name, year_of_birth,
    10        last_name='doe', sex='M',
    11    ):
    12    return {
    13        'first_name': first_name,
    14        'last_name': last_name,
    15        'sex': sex,
    16        # 'age': datetime.datetime.today().year - year_of_birth,
    17        'age': this_year() - year_of_birth,
    18    }
    

    the test is still green

  • I remove the commented lines

     8def factory(
     9        first_name, year_of_birth,
    10        last_name='doe', sex='M',
    11    ):
    12    return {
    13        'first_name': first_name,
    14        'last_name': last_name,
    15        'sex': sex,
    16        'age': this_year() - year_of_birth,
    17    }
    
  • I can change the value of a class attribute after the object has been made, kind of like I did in test_setting_items_in_a_list. I add a statement to test_update_classy_person_year_of_birth in test_person.py

    156        self.assertEqual(person.get_age(), 446)
    157
    158        person.year_of_birth = 1980
    159
    160
    161# Exceptions seen
    
  • I add another assertion for the age calculation

    156        self.assertEqual(person.get_age(), 446)
    157
    158        person.year_of_birth = 1980
    159        self.assertEqual(person.get_age(), 446)
    160
    161
    162# Exceptions seen
    

    the terminal shows AssertionError

    AssertionError: 46 != 446
    
  • I change the expectation in the assertion

    159        self.assertEqual(person.get_age(), this_year()-1980)
    

    the test passes. Wait! That was a lot simpler than doing it with just functions

  • I add a variable to remove repetition

    156        self.assertEqual(person.get_age(), 446)
    157
    158        new_year_of_birth = 1980
    159        person.year_of_birth = 1980
    160        self.assertEqual(person.get_age(), this_year()-1980)
    161
    162
    163# Exceptions seen
    
  • I use the variable

    158        new_year_of_birth = 1980
    159        # person.year_of_birth = 1980
    160        person.year_of_birth = new_year_of_birth
    161        # self.assertEqual(person.get_age(), this_year()-1980)
    162        self.assertEqual(
    163            person.get_age(),
    164            this_year()-new_year_of_birth
    165        )
    166
    167
    168# Exceptions seen
    

    the test is still green

  • I remove the commented lines

    150    def test_update_classy_person_year_of_birth(self):
    151        person = src.person.Person(
    152            first_name='john',
    153            last_name='smith',
    154            year_of_birth=1580,
    155        )
    156        self.assertEqual(person.get_age(), 446)
    157
    158        new_year_of_birth = 1980
    159        person.year_of_birth = new_year_of_birth
    160        self.assertEqual(
    161            person.get_age(),
    162            this_year()-new_year_of_birth
    163        )
    164
    165
    166# Exceptions seen
    

    still green

Note

  • Classes have attributes that can be changed.

  • Since the age calculation uses the year_of_birth, and is done when I call it, not when the person is made, it will always calculate the right age.

  • It is easier to make changes to a person when I use a class than when I use only functions


test with random person


I want to add randomness to the test

  • I add a class attribute for last_name to the setUp method in test_person.py

    17    def setUp(self):
    18        self.random_year_of_birth = random.randint(
    19            this_year()-120, this_year()
    20        )
    21        self.random_first_name = choose('jane', 'joe', 'john', 'person')
    22        self.random_last_name = choose('doe', 'smith', 'blow', 'public')
    23
    24    def test_factory_takes_keyword_arguments(self):
    
  • self.random_first_name and self.random_last_name look the same. I add a class attribute for the values of the names passed to the choose function

    15class TestPerson(unittest.TestCase):
    16
    17    RANDOM_NAMES = (
    18        'jane', 'joe', 'john', 'person',
    19        'doe', 'smith', 'blow', 'public',
    20    )
    21
    22    def setUp(self):
    
  • I use RANDOM_NAMES in the calls to the choose function for random_first_name and random_last_name

    22    def setUp(self):
    23        self.random_year_of_birth = random.randint(
    24            this_year()-120, this_year()
    25        )
    26        # self.random_first_name = choose('jane', 'joe', 'john', 'person')
    27        self.random_first_name = choose(*self.RANDOM_NAMES)
    28        # self.random_last_name = choose('doe', 'smith', 'blow', 'public')
    29        self.random_last_name = choose(*self.RANDOM_NAMES)
    

    the tests are still green and there are now more names to choose from

  • I remove the commented lines

    22    def setUp(self):
    23        self.random_year_of_birth = random.randint(
    24            this_year()-120, this_year()
    25        )
    26        self.random_first_name = choose(*self.RANDOM_NAMES)
    27        self.random_last_name = choose(*self.RANDOM_NAMES)
    28
    29    def test_factory_takes_keyword_arguments(self):
    
  • I use self.random_last_name in test_factory_takes_keyword_arguments

    29    def test_factory_takes_keyword_arguments(self):
    30        a_person = dict(
    31            first_name=self.random_first_name,
    32            # last_name=choose('doe', 'smith', 'blow', 'public'),
    33            last_name=self.random_last_name,
    34            sex=choose('F', 'M'),
    35        )
    

    the test is still green

  • I remove the commented line

    29    def test_factory_takes_keyword_arguments(self):
    30        a_person = dict(
    31            first_name=self.random_first_name,
    32            last_name=self.random_last_name,
    33            sex=choose('F', 'M'),
    34        )
    

    still green


  • I add a class attribute for sex to the setUp method

    22    def setUp(self):
    23        self.random_year_of_birth = random.randint(
    24            this_year()-120, this_year()
    25        )
    26        self.random_first_name = choose(*self.RANDOM_NAMES)
    27        self.random_last_name = choose(*self.RANDOM_NAMES)
    28        self.random_sex = choose('M', 'F')
    29
    30    def test_factory_takes_keyword_arguments(self):
    
  • I use the new class attribute in test_factory_takes_keyword_arguments

    30    def test_factory_takes_keyword_arguments(self):
    31        a_person = dict(
    32            first_name=self.random_first_name,
    33            last_name=self.random_last_name,
    34            # sex=choose('F', 'M'),
    35            sex=self.random_sex,
    36        )
    

    green

  • I remove the commented line

    30    def test_factory_takes_keyword_arguments(self):
    31        a_person = dict(
    32            first_name=self.random_first_name,
    33            last_name=self.random_last_name,
    34            sex=self.random_sex,
    35        )
    

    still green


  • I make a random person with the factory function in the setUp method

    22    def setUp(self):
    23        self.random_year_of_birth = random.randint(
    24            this_year()-120, this_year()
    25        )
    26        self.random_first_name = choose(*self.RANDOM_NAMES)
    27        self.random_last_name = choose(*self.RANDOM_NAMES)
    28        self.random_sex = choose('M', 'F')
    29        self.random_factory_person = src.person.factory(
    30            first_name=self.random_first_name,
    31            last_name=self.random_last_name,
    32            sex=self.random_sex,
    33            year_of_birth=self.random_year_of_birth,
    34        )
    35
    36    def test_factory_takes_keyword_arguments(self):
    
  • I use the random_factory_person class attribute in the expectation of the assertion in test_factory_takes_keyword_arguments

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

    the test is still green

  • I no longer need the a_person variable, I can use the class attributes

    43        self.assertEqual(
    44            src.person.factory(
    45                # **a_person,
    46                first_name=self.random_first_name,
    47                last_name=self.random_last_name,
    48                sex=self.random_sex,
    49                year_of_birth=self.random_year_of_birth,
    50            ),
    51            # dict(
    52            #     **a_person,
    53            #     age=this_year()-self.random_year_of_birth,
    54            # )
    55            self.random_factory_person
    56        )
    

    green

  • I remove the commented lines and the a_person dictionary

    36    def test_factory_takes_keyword_arguments(self):
    37        self.assertEqual(
    38            src.person.factory(
    39                first_name=self.random_first_name,
    40                last_name=self.random_last_name,
    41                sex=self.random_sex,
    42                year_of_birth=self.random_year_of_birth,
    43            ),
    44            self.random_factory_person
    45        )
    46
    47    def test_factory_w_default_arguments(self):
    

    green again. Do I still need test_factory_takes_keyword_arguments?


  • I add an assertion with the random_factory_person class attribute to test_factory_person_greeting

    72        john = src.person.factory(
    73            first_name='john',
    74            last_name='smith',
    75            year_of_birth=1580,
    76        )
    77
    78        self.assertEqual(
    79            src.person.hello(self.random_factory_person),
    80            (
    81                f'Hi, my name is {self.random_first_name} '
    82                f'{self.random_last_name} '
    83                f'and I am {this_year()-self.random_year_of_birth}'
    84            )
    85        )
    86
    87        for person in (joe, jane, john):
    

    the test is still green

  • I remove the 3 people I made with the factory function and the for loop with the assertion because they are no longer needed, the random person covers all their cases and more

    61    def test_factory_person_greeting(self):
    62        self.assertEqual(
    63            src.person.hello(self.random_factory_person),
    64            (
    65                f'Hi, my name is {self.random_first_name} '
    66                f'{self.random_last_name} '
    67                f'and I am {this_year()-self.random_year_of_birth}'
    68            )
    69        )
    70
    71    def test_classy_person_greeting(self):
    

    still


  • I have to make a random person with the Person class to do the same thing for test_classy_person_greeting. I will come back to it


  • I use the random_year_of_birth class attribute in test_update_factory_person_year_of_birth

     99    def test_update_factory_person_year_of_birth(self):
    100        # original_year_of_birth = 1580
    101        original_year_of_birth = self.random_year_of_birth
    

    the test is still green

  • I use the class attribute in the calculation

     99    def test_update_factory_person_year_of_birth(self):
    100        # original_year_of_birth = 1580
    101        original_year_of_birth = self.random_year_of_birth
    102        # original_age = this_year() - original_year_of_birth
    103        original_age = this_year() - self.random_year_of_birth
    104        new_year_of_birth = 1980
    

    still green

  • I point person to the self.random_factory_person class attribute

    104        new_year_of_birth = 1980
    105
    106        # person = src.person.factory(
    107        #     first_name='john',
    108        #     last_name='smith',
    109        #     year_of_birth=original_year_of_birth,
    110        # )
    111        person = self.random_factory_person
    112        self.assertEqual(
    

    still green

  • I use the self.random_factory_person class attribute in the first assertion

    111        person = self.random_factory_person
    112        self.assertEqual(
    113            # person.get('age'),
    114            self.random_factory_person.get('age'),
    115            original_age
    116        )
    117
    118        with self.assertRaises(KeyError):
    

    still green

  • I remove the commented lines and the original_year_of_birth variable

     99    def test_update_factory_person_year_of_birth(self):
    100        original_age = this_year() - self.random_year_of_birth
    101        new_year_of_birth = 1980
    102
    103        person = self.random_factory_person
    104        self.assertEqual(
    105            self.random_factory_person.get('age'),
    106            original_age
    107        )
    108
    109        with self.assertRaises(KeyError):
    
  • I use the class attribute in the assertRaises block

    109        with self.assertRaises(KeyError):
    110            # person['year_of_birth']
    111            self.random_factory_person['year_of_birth']
    112        self.assertEqual(
    

    still green

  • I use the self.random_factory_person class attribute in the assertion for the setdefault method

    112        self.assertEqual(
    113            # person.setdefault('year_of_birth', new_year_of_birth),
    114            self.random_factory_person.setdefault(
    115                'year_of_birth', new_year_of_birth
    116            ),
    117            new_year_of_birth
    118        )
    

    still green

  • I use self.random_factory_person in the second assertion for the age

    119        self.assertEqual(
    120            # person.get('age'),
    121            self.random_factory_person.get('age'),
    122            original_age
    123        )
    

    still green

  • I remove the commented lines

    109        with self.assertRaises(KeyError):
    110            self.random_factory_person['year_of_birth']
    111        self.assertEqual(
    112            self.random_factory_person.setdefault(
    113                'year_of_birth', new_year_of_birth
    114            ),
    115            new_year_of_birth
    116        )
    117        self.assertEqual(
    118            self.random_factory_person.get('age'),
    119            original_age
    120        )
    

    the test is still green

  • I use the class attribute in the call to src.person.update_year_of_birth in the assertion for the year_of_birth update

    122        self.assertEqual(
    123            src.person.update_year_of_birth(
    124                # person,
    125                self.random_factory_person,
    126                new_year_of_birth
    127            ),
    

    still green

  • I use the other class attributes in the expected dictionary of the assertion

    128            dict(
    129                # first_name=person.get('first_name'),
    130                first_name=self.random_first_name,
    131                # last_name=person.get('last_name'),
    132                last_name=self.random_last_name,
    133                # sex=person.get('sex'),
    134                sex=self.random_sex,
    135                age=this_year()-new_year_of_birth,
    136            )
    

    green

  • I remove the commented lines and the person variable

     99    def test_update_factory_person_year_of_birth(self):
    100        original_age = this_year() - self.random_year_of_birth
    101        new_year_of_birth = 1980
    102
    103        self.assertEqual(
    104            self.random_factory_person.get('age'),
    105            original_age
    106        )
    107
    108        with self.assertRaises(KeyError):
    109            self.random_factory_person['year_of_birth']
    110        self.assertEqual(
    111            self.random_factory_person.setdefault(
    112                'year_of_birth', new_year_of_birth
    113            ),
    114            new_year_of_birth
    115        )
    116        self.assertEqual(
    117            self.random_factory_person.get('age'),
    118            original_age
    119        )
    120
    121        self.assertEqual(
    122            src.person.update_year_of_birth(
    123                self.random_factory_person,
    124                new_year_of_birth
    125            ),
    126            dict(
    127                first_name=self.random_first_name,
    128                last_name=self.random_last_name,
    129                sex=self.random_sex,
    130                age=this_year()-new_year_of_birth,
    131            )
    132        )
    133
    134    def test_update_classy_person_year_of_birth(self):
    

    still green


  • this_year() - self.random_year_of_birth is in the tests a few times. I add a class attribute for it in the setUp method

    22    def setUp(self):
    23        self.random_year_of_birth = random.randint(
    24            this_year()-120, this_year()
    25        )
    26        self.original_age = this_year() - self.random_year_of_birth
    27        self.random_first_name = choose(*self.RANDOM_NAMES)
    28        self.random_last_name = choose(*self.RANDOM_NAMES)
    
  • I use the new class attribute in test_factory_w_default_arguments

    54            dict(
    55                first_name=self.random_first_name,
    56                last_name='doe',
    57                sex='M',
    58                # age=this_year()-self.random_year_of_birth,
    59                age=self.original_age,
    60            )
    

    the test is still green

  • I remove the commented line

    48    def test_factory_w_default_arguments(self):
    49        self.assertEqual(
    50            src.person.factory(
    51                first_name=self.random_first_name,
    52                year_of_birth=self.random_year_of_birth,
    53            ),
    54            dict(
    55                first_name=self.random_first_name,
    56                last_name='doe',
    57                sex='M',
    58                age=self.original_age,
    59            )
    60        )
    61
    62    def test_factory_person_greeting(self):
    

    green

  • I use the class attribute in test_factory_person_greeting

    65            (
    66                f'Hi, my name is {self.random_first_name} '
    67                f'{self.random_last_name} '
    68                # f'and I am {this_year()-self.random_year_of_birth}'
    69                f'and I am {self.original_age}'
    70            )
    

    the test is still green

  • I remove the commented line

    62    def test_factory_person_greeting(self):
    63        self.assertEqual(
    64            src.person.hello(self.random_factory_person),
    65            (
    66                f'Hi, my name is {self.random_first_name} '
    67                f'{self.random_last_name} '
    68                f'and I am {self.original_age}'
    69            )
    70        )
    71
    72    def test_classy_person_greeting(self):
    

    still green

  • I use self.original_age in test_update_factory_person_year_of_birth

    100    def test_update_factory_person_year_of_birth(self):
    101        # original_age = this_year() - self.random_year_of_birth
    102        original_age = self.original_age
    103        new_year_of_birth = 1980
    

    the test is still green

  • I use self.original_age in the first assertion

    103        new_year_of_birth = 1980
    104
    105        self.assertEqual(
    106            self.random_factory_person.get('age'),
    107            # original_age
    108            self.original_age
    109        )
    

    green

  • I use it in the second assertion for the age

    113        self.assertEqual(
    114            self.random_factory_person.setdefault(
    115                'year_of_birth', new_year_of_birth
    116            ),
    117            new_year_of_birth
    118        )
    119        self.assertEqual(
    120            self.random_factory_person.get('age'),
    121            # original_age
    122            self.original_age
    123        )
    124
    125        self.assertEqual(
    

    still green

  • I remove the commented line and the original_age variable

    100    def test_update_factory_person_year_of_birth(self):
    101        new_year_of_birth = 1980
    102
    103        self.assertEqual(
    104            self.random_factory_person.get('age'),
    105            self.original_age
    106        )
    107
    108        with self.assertRaises(KeyError):
    109            self.random_factory_person['year_of_birth']
    110        self.assertEqual(
    111            self.random_factory_person.setdefault(
    112                'year_of_birth', new_year_of_birth
    113            ),
    114            new_year_of_birth
    115        )
    116        self.assertEqual(
    117            self.random_factory_person.get('age'),
    118            self.original_age
    119        )
    120
    121        self.assertEqual(
    122            src.person.update_year_of_birth(
    123                self.random_factory_person,
    124                new_year_of_birth
    125            ),
    126            dict(
    127                first_name=self.random_first_name,
    128                last_name=self.random_last_name,
    129                sex=self.random_sex,
    130                age=this_year()-new_year_of_birth,
    131            )
    132        )
    133
    134    def test_update_classy_person_year_of_birth(self):
    

  • I add a random person made with the Person class to the setUp method

    30        self.random_factory_person = src.person.factory(
    31            first_name=self.random_first_name,
    32            last_name=self.random_last_name,
    33            sex=self.random_sex,
    34            year_of_birth=self.random_year_of_birth,
    35        )
    36        self.random_classy_person = src.person.Person(
    37            first_name=self.random_first_name,
    38            last_name=self.random_last_name,
    39            sex=self.random_sex,
    40            year_of_birth=self.random_year_of_birth,
    41        )
    42
    43    def test_factory_takes_keyword_arguments(self):
    
  • I add an assertion with the new class attribute to test_classy_person_greeting

     89        john = src.person.Person(
     90            first_name='john',
     91            last_name='smith',
     92            year_of_birth=1580,
     93        )
     94
     95        self.assertEqual(
     96            self.random_classy_person.hello(),
     97            (
     98                f'Hi, my name is {self.random_first_name} '
     99                f'{self.random_last_name} '
    100                f'and I am {self.original_age}'
    101            )
    102        )
    103
    104        for person in (joe, jane, john):
    

    still green

  • I remove the 3 people I made with the Person class and the for loop with its assertion because they are no longer needed, the random person covers those cases and more

    78    def test_classy_person_greeting(self):
    79        self.assertEqual(
    80            self.random_classy_person.hello(),
    81            (
    82                f'Hi, my name is {self.random_first_name} '
    83                f'{self.random_last_name} '
    84                f'and I am {self.original_age}'
    85            )
    86        )
    87
    88    def test_update_factory_person_year_of_birth(self):
    

    still green

  • the expected message in test_classy_person_greeting and test_factory_person_greeting are now the same. I add a method to remove the repetition

    60            dict(
    61                first_name=self.random_first_name,
    62                last_name='doe',
    63                sex='M',
    64                age=self.original_age
    65            )
    66        )
    67
    68    def expected_greeting(self):
    69        return (
    70            f'Hi, my name is {self.random_first_name} '
    71            f'{self.random_last_name} '
    72            f'and I am {self.original_age}'
    73        )
    74
    75    def test_factory_person_greeting(self):
    
  • I use the new method in test_factory_person_greeting

    75    def test_factory_person_greeting(self):
    76        self.assertEqual(
    77            src.person.hello(self.random_factory_person),
    78            # (
    79            #     f'Hi, my name is {self.random_first_name} '
    80            #     f'{self.random_last_name} '
    81            #     f'and I am {self.original_age}'
    82            # )
    83            self.expected_greeting()
    84        )
    85
    86    def test_classy_person_greeting(self):
    

    the test is still green

  • I use it in test_classy_person_greeting

    86    def test_classy_person_greeting(self):
    87        self.assertEqual(
    88            self.random_classy_person.hello(),
    89            # (
    90            #     f'Hi, my name is {self.random_first_name} '
    91            #     f'{self.random_last_name} '
    92            #     f'and I am {self.original_age}'
    93            # )
    94            self.expected_greeting()
    95        )
    96
    97    def test_update_factory_person_year_of_birth(self):
    

    still green

  • I remove the commented lines

    68    def expected_greeting(self):
    69        return (
    70            f'Hi, my name is {self.random_first_name} '
    71            f'{self.random_last_name} '
    72            f'and I am {self.original_age}'
    73        )
    74
    75    def test_factory_person_greeting(self):
    76        self.assertEqual(
    77            src.person.hello(self.random_factory_person),
    78            self.expected_greeting()
    79        )
    80
    81    def test_classy_person_greeting(self):
    82        self.assertEqual(
    83            self.random_classy_person.hello(),
    84            self.expected_greeting()
    85        )
    86
    87    def test_update_factory_person_year_of_birth(self):
    

    green

  • I use the random_classy_person class attribute in test_update_classy_person_year_of_birth

    121    def test_update_classy_person_year_of_birth(self):
    122        # person = src.person.Person(
    123        #     first_name='john',
    124        #     last_name='smith',
    125        #     year_of_birth=1580,
    126        # )
    127        person = self.random_classy_person
    128        self.assertEqual(person.get_age(), 446)
    

    the terminal shows AssertionError

    AssertionError: X != 446
    
  • I use self.original_age in the first assertion for the age

    127        person = self.random_classy_person
    128        # self.assertEqual(person.get_age(), 446)
    129        self.assertEqual(person.get_age(), self.original_age)
    130
    131        new_year_of_birth = 1980
    

    the test is green again

  • I remove the commented lines then use self.random_classy_person in the first assertion

    121    def test_update_classy_person_year_of_birth(self):
    122        person = self.random_classy_person
    123        self.assertEqual(
    124            # person.get_age(),
    125            self.random_classy_person.get_age(),
    126            self.original_age
    127        )
    128
    129        new_year_of_birth = 1980
    

    the test is still green

  • I use the new year of birth as the value for the year_of_birth attribute of self.random_classy_person

    129        new_year_of_birth = 1980
    130        # person.year_of_birth = new_year_of_birth
    131        self.random_classy_person.year_of_birth = new_year_of_birth
    132        self.assertEqual(
    133            person.get_age(),
    134            this_year()-new_year_of_birth
    135        )
    

    green

  • I use self.random_classy_person in the second assertion

    129        new_year_of_birth = 1980
    130        # person.year_of_birth = new_year_of_birth
    131        self.random_classy_person.year_of_birth = new_year_of_birth
    132        self.assertEqual(
    133            # person.get_age(),
    134            self.random_classy_person.get_age(),
    135            this_year()-new_year_of_birth
    136        )
    137
    138
    139# Exceptions seen
    

    the test is still green

  • I remove the commented lines and the person variable

    121    def test_update_classy_person_year_of_birth(self):
    122        self.assertEqual(
    123            self.random_classy_person.get_age(),
    124            self.original_age
    125        )
    126
    127        new_year_of_birth = 1980
    128        self.random_classy_person.year_of_birth = new_year_of_birth
    129        self.assertEqual(
    130            self.random_classy_person.get_age(),
    131            this_year()-new_year_of_birth
    132        )
    133
    134
    135# Exceptions seen
    

    still green


  • the new_year_of_birth variable is the same in test_update_factory_person_year_of_birth and test_update_classy_person_year_of_birth. I add a new class attribute to the setUp method because I want to use random numbers for it

    22    def setUp(self):
    23        self.random_year_of_birth = random.randint(
    24            this_year()-120, this_year()
    25        )
    26        self.random_new_year_of_birth = random.randint(
    27            this_year()-120, this_year()
    28        )
    29        self.original_age = this_year() - self.random_year_of_birth
    

    this is also doing the same thing two times, even though the results are different

  • I make a function to remove the repetition

    11def this_year():
    12    return datetime.datetime.now().year
    13
    14
    15def random_year_of_birth():
    16    return random.randint(
    17        this_year()-120, this_year()
    18    )
    19
    20
    21class TestPerson(unittest.TestCase):
    
  • I point self.random_year_of_birth to the result of calling the new function in the setUp method

    28    def setUp(self):
    29        # self.random_year_of_birth = random.randint(
    30        #     this_year()-120, this_year()
    31        # )
    32        self.random_year_of_birth = random_year_of_birth()
    33        self.random_new_year_of_birth = random.randint(
    

    the test is still green

  • I call the function for the new_year_of_birth attribute

    28    def setUp(self):
    29        # self.random_year_of_birth = random.randint(
    30        #     this_year()-120, this_year()
    31        # )
    32        self.random_year_of_birth = random_year_of_birth()
    33        # self.random_new_year_of_birth = random.randint(
    34        #     this_year()-120, this_year()
    35        # )
    36        self.random_new_year_of_birth = random_year_of_birth()
    37        self.original_age = this_year() - self.random_year_of_birth
    

    green

  • I remove the commented lines

    28    def setUp(self):
    29        self.random_year_of_birth = random_year_of_birth()
    30        self.random_new_year_of_birth = random_year_of_birth()
    31        self.original_age = this_year() - self.random_year_of_birth
    32        self.random_first_name = choose(*self.RANDOM_NAMES)
    
  • I use self.random_new_year_of_birth in test_update_factory_person_year_of_birth

    92    def test_update_factory_person_year_of_birth(self):
    93        # new_year_of_birth = 1980
    94        new_year_of_birth = self.random_new_year_of_birth
    

    still green

  • I use the class attribute in the assertion for the call to the setdefault method

    103        self.assertEqual(
    104            self.random_factory_person.setdefault(
    105                # 'year_of_birth', new_year_of_birth
    106                'year_of_birth', self.random_new_year_of_birth
    107            ),
    108            new_year_of_birth
    109        )
    

    the test is still green

  • I use the class attribute as the expectation of the assertion for the call to the setdefault method

    103        self.assertEqual(
    104            self.random_factory_person.setdefault(
    105                # 'year_of_birth', new_year_of_birth
    106                'year_of_birth', self.random_new_year_of_birth
    107            ),
    108            # new_year_of_birth
    109            self.random_new_year_of_birth
    110        )
    

    still green

  • I use the class attribute in the call to src.person.update_year_of_birth

    116        self.assertEqual(
    117            src.person.update_year_of_birth(
    118                self.random_factory_person,
    119                # new_year_of_birth
    120                self.random_new_year_of_birth
    121            ),
    

    still green

  • I use the class attribute in the calculation for the new age

    122            dict(
    123                first_name=self.random_first_name,
    124                last_name=self.random_last_name,
    125                sex=self.random_sex,
    126                # age=this_year()-new_year_of_birth,
    127                age=this_year()-self.random_new_year_of_birth,
    128            )
    

    the test is still green

  • I remove the commented lines and the new_year_of_birth variable

     92    def test_update_factory_person_year_of_birth(self):
     93        self.assertEqual(
     94            self.random_factory_person.get('age'),
     95            self.original_age
     96        )
     97
     98        with self.assertRaises(KeyError):
     99            self.random_factory_person['year_of_birth']
    100        self.assertEqual(
    101            self.random_factory_person.setdefault(
    102                'year_of_birth', self.random_new_year_of_birth
    103            ),
    104            self.random_new_year_of_birth
    105        )
    106        self.assertEqual(
    107            self.random_factory_person.get('age'),
    108            self.original_age
    109        )
    110
    111        self.assertEqual(
    112            src.person.update_year_of_birth(
    113                self.random_factory_person,
    114                self.random_new_year_of_birth
    115            ),
    116            dict(
    117                first_name=self.random_first_name,
    118                last_name=self.random_last_name,
    119                sex=self.random_sex,
    120                age=this_year()-self.random_new_year_of_birth,
    121            )
    122        )
    123
    124    def test_update_classy_person_year_of_birth(self):
    

    green around the rosie, a pocket full of posies

  • on to test_update_classy_person_year_of_birth. I point the new_year_of_birth variable to the class attribute

    124    def test_update_classy_person_year_of_birth(self):
    125        self.assertEqual(
    126            self.random_classy_person.get_age(),
    127            self.original_age
    128        )
    129
    130        # new_year_of_birth = 1980
    131        new_year_of_birth = self.random_new_year_of_birth
    132        self.random_classy_person.year_of_birth = new_year_of_birth
    

    the test is still green

  • I use the class attribute in the assignment of the new value

    130        # new_year_of_birth = 1980
    131        new_year_of_birth = self.random_new_year_of_birth
    132        # self.random_classy_person.year_of_birth = new_year_of_birth
    133        self.random_classy_person.year_of_birth = self.random_new_year_of_birth
    134        self.assertEqual(
    

    still green

  • I use the class attribute in the assertion

    130        # new_year_of_birth = 1980
    131        new_year_of_birth = self.random_new_year_of_birth
    132        # self.random_classy_person.year_of_birth = new_year_of_birth
    133        self.random_classy_person.year_of_birth = self.random_new_year_of_birth
    134        self.assertEqual(
    135            self.random_classy_person.get_age(),
    136            # this_year()-new_year_of_birth
    137            this_year()-self.random_new_year_of_birth
    138        )
    

    green

  • I remove the commented lines and the new_year_of_birth variable

    124    def test_update_classy_person_year_of_birth(self):
    125        self.assertEqual(
    126            self.random_classy_person.get_age(),
    127            self.original_age
    128        )
    129
    130        self.random_classy_person.year_of_birth = self.random_new_year_of_birth
    131        self.assertEqual(
    132            self.random_classy_person.get_age(),
    133            this_year()-self.random_new_year_of_birth
    134        )
    135
    136
    137# Exceptions seen
    

    the test is still green


  • There are two calculations that happen in the tests, one for the new age and another for the original age

    this_year() - self.random_year_of_birth
    this_year() - self.random_new_year_of_birth
    

    I add a function that does the calculation to test_person.py

    15def random_year_of_birth():
    16    return random.randint(
    17        this_year()-120, this_year()
    18    )
    19
    20
    21def get_age(year_of_birth):
    22    return this_year() - year_of_birth
    23
    24
    25class TestPerson(unittest.TestCase):
    
  • I point self.original_age in the setUp method to the result of calling the function with self.random_year_of_birth

    32    def setUp(self):
    33        self.random_year_of_birth = random_year_of_birth()
    34        self.random_new_year_of_birth = random_year_of_birth()
    35        # self.original_age = this_year() - self.random_year_of_birth
    36        self.original_age = get_age(self.random_year_of_birth)
    37        self.random_first_name = choose(*self.RANDOM_NAMES)
    

    the test is still green

  • I remove the commented line then add a new class attribute for the calculation of the new age

    32    def setUp(self):
    33        self.random_year_of_birth = random_year_of_birth()
    34        self.random_new_year_of_birth = random_year_of_birth()
    35        self.original_age = get_age(self.random_year_of_birth)
    36        self.new_age = get_age(self.random_new_year_of_birth)
    37        self.random_first_name = choose(*self.RANDOM_NAMES)
    
  • I use the new class attribute in test_update_factory_person_year_of_birth

    121            dict(
    122                first_name=self.random_first_name,
    123                last_name=self.random_last_name,
    124                sex=self.random_sex,
    125                # age=this_year()-self.random_new_year_of_birth,
    126                age=self.new_age,
    127            )
    

    the test is still green

  • I remove the commented line

     97    def test_update_factory_person_year_of_birth(self):
     98        self.assertEqual(
     99            self.random_factory_person.get('age'),
    100            self.original_age
    101        )
    102
    103        with self.assertRaises(KeyError):
    104            self.random_factory_person['year_of_birth']
    105        self.assertEqual(
    106            self.random_factory_person.setdefault(
    107                'year_of_birth', self.random_new_year_of_birth
    108            ),
    109            self.random_new_year_of_birth
    110        )
    111        self.assertEqual(
    112            self.random_factory_person.get('age'),
    113            self.original_age
    114        )
    115
    116        self.assertEqual(
    117            src.person.update_year_of_birth(
    118                self.random_factory_person,
    119                self.random_new_year_of_birth
    120            ),
    121            dict(
    122                first_name=self.random_first_name,
    123                last_name=self.random_last_name,
    124                sex=self.random_sex,
    125                age=self.new_age,
    126            )
    127        )
    128
    129    def test_update_classy_person_year_of_birth(self):
    

    green

  • use the class attribute in test_update_classy_person_year_of_birth

    136        self.assertEqual(
    137            self.random_classy_person.get_age(),
    138            # this_year()-self.random_new_year_of_birth
    139            self.new_age
    140        )
    

    still green

  • I remove the commented line

    129    def test_update_classy_person_year_of_birth(self):
    130        self.assertEqual(
    131            self.random_classy_person.get_age(),
    132            self.original_age
    133        )
    134
    135        self.random_classy_person.year_of_birth = self.random_new_year_of_birth
    136        self.assertEqual(
    137            self.random_classy_person.get_age(),
    138            self.new_age
    139        )
    140
    141
    142# Exceptions seen
    

I wonder what red and yellow look like, that was a lot of green.


test_class_w_default_arguments


In test_factory_w_default_arguments, I tested what happens when I call the factory function without giving a value for last_name and sex.

In those cases the functions uses default values of 'doe' for last_name and 'M' for sex.

I want to add a test for the Person class to make sure it does the same thing when I do not provide a value for last_name or sex when making a person.


RED: make it fail


  • I add a new test with a person made with the Person class without a value for last_name and sex and an assertion for the value of the last_name

    70            dict(
    71                first_name=self.random_first_name,
    72                last_name='doe',
    73                sex='M',
    74                age=self.original_age
    75            )
    76        )
    77
    78    def test_class_w_default_arguments(self):
    79        person = src.person.Person(
    80            first_name=self.random_first_name,
    81            year_of_birth=self.random_year_of_birth,
    82        )
    83        self.assertEqual(person.first_name, None)
    84
    85    def expected_greeting(self):
    

    the terminal shows AssertionError

    AssertionError: X != None
    

GREEN: make it pass


I use the class attribute for random_first_name to make the expectation match

83          self.assertEqual(person.first_name, self.random_first_name)

the test passes


REFACTOR: make it better


  • I add an assertion for the value of the last_name

    83        self.assertEqual(person.first_name, self.random_first_name)
    84        self.assertEqual(person.last_name, 'doe')
    85
    86    def expected_greeting(self):
    

    the terminal shows AssertionError

    AssertionError: None != 'doe'
    
  • I change the default value for last_name in the __init__ method of the Person class in person.py

    37class Person:
    38
    39    def __init__(
    40            self, first_name, last_name='doe',
    41            year_of_birth=None, sex=None,
    42        ):
    

    the test passes


  • I add another assertion for the value of sex in test_person.py

    83        self.assertEqual(person.first_name, self.random_first_name)
    84        self.assertEqual(person.last_name, 'doe')
    85        self.assertEqual(person.sex, 'M')
    86
    87    def expected_greeting(self):
    

    the terminal shows AttributeError

    AttributeError: 'Person' object has no attribute 'sex'
    
  • I add the class attribute to the __init__ method of the Person class in person.py

    39    def __init__(
    40            self, first_name, last_name='doe',
    41            year_of_birth=None, sex=None,
    42        ):
    43        self.first_name = first_name
    44        self.last_name = last_name
    45        self.year_of_birth = year_of_birth
    46        self.sex = sex
    47        return None
    

    the terminal shows AssertionError

    AssertionError: None != 'M'
    
  • I change the default value for sex

    39    def __init__(
    40            self, first_name, last_name='doe',
    41            year_of_birth=None, sex='M',
    42        ):
    

    the test passes


There is a problem with the year_of_birth, its default value is None, which means if I do not give a value for it when I make a person with the factory function or Person class, TypeError will be raised.

  • I remove year_of_birth from the call to the Person class in test_class_w_default_arguments in test_person.py

    78    def test_class_w_default_arguments(self):
    79        person = src.person.Person(
    80            first_name=self.random_first_name,
    81            # year_of_birth=self.random_year_of_birth,
    82        )
    

    the test is still green

  • I remove the commented line then add an assertion for the age

    78    def test_class_w_default_arguments(self):
    79        person = src.person.Person(self.random_first_name)
    80        self.assertEqual(person.first_name, self.random_first_name)
    81        self.assertEqual(person.last_name, 'doe')
    82        self.assertEqual(person.sex, 'M')
    83        self.assertEqual(person.get_age(), None)
    84
    85    def expected_greeting(self):
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for -: 'int' and 'NoneType'
    

    because the get_age method tries to subtract year_of_birth from this year, and year_of_birth is None

  • I add a default value for year_of_birth in the __init__ method of the Person class in person.py

    39    def __init__(
    40            self, first_name, last_name='doe',
    41            year_of_birth=this_year(), sex='M',
    42        ):
    

    the terminal shows AssertionError

    AssertionError: 0 != None
    
  • I change the expectation in test_person.py

    82        self.assertEqual(person.sex, 'M')
    83        self.assertEqual(person.get_age(), 0)
    84
    85    def expected_greeting(self):
    

    the test passes


  • I remove the value for year_of_birth in the call to src.person.factory in test_factory_w_default_arguments

    64    def test_factory_w_default_arguments(self):
    65        self.assertEqual(
    66            src.person.factory(
    67                first_name=self.random_first_name,
    68                # year_of_birth=self.random_year_of_birth,
    69            ),
    

    the terminal shows TypeError

    TypeError: factory() missing 1 required positional argument: 'year_of_birth'
    
  • I add a default value for year_of_birth to make it a choice in the factory function in person.py

     8def factory(
     9        first_name, year_of_birth=None,
    10        last_name='doe', sex='M',
    11    ):
    

    the terminal shows TypeError

    TypeError: unsupported operand type(s) for -: 'int' and 'NoneType'
    
  • I change the default value for year_of_birth to this year

     8def factory(
     9        first_name, year_of_birth=this_year(),
    10        last_name='doe', sex='M',
    11    ):
    

    the terminal shows AssertionError

    E       - {'age': 0, 'first_name': Y, 'last_name': 'doe', 'sex': 'M'}
    E       ?         ^
    E
    E       + {'age': X, 'first_name': Y, 'last_name': 'doe', 'sex': 'M'}
    E       ?         ^
    
  • I change the expectation in test_factory_w_default_arguments in test_person.py

    70            dict(
    71                first_name=self.random_first_name,
    72                last_name='doe',
    73                sex='M',
    74                # age=self.original_age
    75                age=0
    76            )
    

    the test passes

  • I remove the commented lines

    64    def test_factory_w_default_arguments(self):
    65        self.assertEqual(
    66            src.person.factory(self.random_first_name),
    67            dict(
    68                first_name=self.random_first_name,
    69                last_name='doe',
    70                sex='M',
    71                age=0
    72            )
    73        )
    74
    75    def test_class_w_default_arguments(self):
    

    the tests are still green


The tests so far have a problem, they check that the input and the output are the same, with no checks for what type of input it is. This means I can use a data type that is not a string for first_name and last_name and sex and the tests would still pass. I would only get TypeError when I use a value for year_of_birth that is not a number, though if I use a boolean, the calculation would still work.

You know enough to add tests for these problems, to make sure that the right inputs are always used. Send me your solutions when you do, I would love to see them.


test_attributes_and_methods_of_classes


I used the dir built-in function in lists and dictionaries to show their attributes and methods. I can also use it with the Person class


RED: make it fail


I add a new test

140          self.assertEqual(
141              self.random_classy_person.get_age(),
142              self.new_age
143          )
144
145      def test_attributes_and_methods_of_a_class(self):
146          self.assertEqual(
147              dir(src.person.Person),
148              []
149          )
150
151
152  # Exceptions seen

the terminal shows AssertionError

AssertionError: Lists differ: ['__class__', '__delattr__', '__dict__', '[377 chars]llo'] != []

GREEN: make it pass


I copy and paste the values from the terminal and remove the extra characters I do not need with the find and replace feature of the Integrated Development Environment (IDE)

145      def test_attributes_and_methods_of_a_class(self):
146          self.assertEqual(
147              dir(src.person.Person),
148              [
149                  '__class__',
150                  '__delattr__',
151                  '__dict__',
152                  '__dir__',
153                  '__doc__',
154                  '__eq__',
155                  '__firstlineno__',
156                  '__format__',
157                  '__ge__',
158                  '__getattribute__',
159                  '__getstate__',
160                  '__gt__',
161                  '__hash__',
162                  '__init__',
163                  '__init_subclass__',
164                  '__le__',
165                  '__lt__',
166                  '__module__',
167                  '__ne__',
168                  '__new__',
169                  '__reduce__',
170                  '__reduce_ex__',
171                  '__repr__',
172                  '__setattr__',
173                  '__sizeof__',
174                  '__static_attributes__',
175                  '__str__',
176                  '__subclasshook__',
177                  '__weakref__',
178                  'get_age',
179                  'hello'
180              ]
181          )
182
183
184  # Exceptions seen
185
186the test passes

The attributes I defined in the __init__ method are not in the list, because I called dir on src.person.Person which is the class definition, not on an instance (copy) of the class where I would have to provide values for the first_name, last_name, sex and year_of_birth attributes.

What is the difference between dir(src.person.Person) and dir(src.person.Person('jane'))?

The 3 methods I defined in the Person class in person.py

  • __init__

  • get_age

  • hello

are in the list, and there are others which I never defined, which leads to the question of where did they come from?


close the project

  • I close test_person.py and person.py in the editors

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

  • I deactivate the virtual environment

    deactivate
    

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

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

    cd ..
    

    the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory


code from the chapter

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


review

Tip

  • when I find myself writing or doing the same thing two times, I write a function

  • when I find I have two functions that use the same information, I write a class


what is next?

you have gone through a lot of things and know

Would you like to know where the extra attributes and methods of the Person class came from?


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