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
open the project
I change directory to the
personfoldercd personthe terminal shows I am in the
personfolder.../pumping_python/personI activate the virtual environment
source .venv/bin/activateAttention
on Windows without Windows Subsystem for Linux use
.venv/bin/activate.ps1NOTsource .venv/bin/activate.venv/scripts/activate.ps1the terminal shows
(.venv) .../pumping_python/personI use
pytest-watchto run the testspytest-watchthe 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.pyto 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 seenthe
factoryfunction will givejoea default value of'M'forsexbecause I did not give a value for itI 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# Exceptionsthe
factoryfunction will givejanea default value ofdoeforlast_namebecause I did not give a value for itI 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.pyin the editorI add the function to
person.py4def 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 NoneTypeError: hello() takes 0 positional arguments but 1 was givenI add a name to the definition
16def hello(person): 17 return Nonethe 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_greetingintest_person.pywith an f-string like I did in how to pass values72 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.py16def 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 35the first name, last name and ages are different
I change the string to an f-string with the value for
first_name16def 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.py16def 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 passTypeError: Person() takes no argumentsclasses have a constructor method that is used to make copies of the class. I add it
24class Person: 25 26 def __init__(): 27 return NoneTypeError: Person.__init__() got an unexpected keyword argument 'first_name'I add the name
24class Person: 25 26 def __init__(first_name): 27 return NoneTypeError: Person.__init__() got multiple values for argument 'first_name'The
__init__method takes the class as the first argument. I addselfthe way I do with all the tests in the book24class Person: 25 26 def __init__(self, first_name): 27 return NoneTypeError: 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
factoryfunctionI add
last_name26 def __init__(self, first_name, last_name): 27 return NoneTypeError: Person.__init__() got an unexpected keyword argument 'year_of_birth'still the same as making the
factoryfunctionI add
year_of_birthto the definition24class Person: 25 26 def __init__( 27 self, first_name, last_name, 28 year_of_birth, 29 ): 30 returnthe test passes
REFACTOR: make it better
I add the next person to
test_classy_person_greeting83 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 )TypeError: Person.__init__() got an unexpected keyword argument 'sex'I add
sexto the__init__method inperson.py26 def __init__( 27 self, first_name, last_name, 28 year_of_birth, sex, 29 ): 30 return NoneTypeError: Person.__init__() missing 1 required positional argument: 'sex'I did not provide a value for
sexwhen I madejoe, and thefactoryfunction has a default value of'M'for itI add a default value for
sexin the__init__method26 def __init__( 27 self, first_name, last_name, 28 year_of_birth, sex=None, 29 ): 30 return Nonethe terminal shows SyntaxError
SyntaxError: parameter without a default follows parameter with a defaultI 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_birth26def __init__( 27 self, first_name, last_name=None, 28 year_of_birth=None, sex=None, 29 ): 30 return Nonethe 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 seenthe test is still green
I copy the for loop with the assertion from test_factory_person_greeting and paste it in
test_classy_person_greeting94 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 seenthe terminal shows AttributeError
AttributeError: 'Person' object has no attribute 'get'I can use class attributes directly with no need for a
getmethod. I change the line for the subTest method100 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 thePersonclass inperson.py26 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 Nonethe terminal shows AttributeError
AttributeError: 'Person' object has no attribute 'get'the test calls the
hellofunction which takes in a dictionary and calls the get method, thePersonobject does not have a get methodI can call methods from outside a class the way I use a class attribute. I change the call in the assertion in
test_person.py102 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
Personclass inperson.py26 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 NoneTypeError: Person.hello() takes 0 positional arguments but 2 were giventhe 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 NoneTypeError: Person.hello() takes 0 positional arguments but 1 was givenI add a name to the definition
33 @staticmethod 34 def hello(person): 35 return Nonethe terminal shows AttributeError
AttributeError: 'Person' object has no attribute 'get'I change the assertion in
test_classy_person_greetingintest_person.pyto use thefirst_nameclass attribute102 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_nameto the__init__method of thePersonclass inperson.py26 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.py102 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
Personclass inperson.py34 @staticmethod 35 def hello(person): 36 return None 37 38 def get_age(): 39 return NoneTypeError: Person.get_age() takes 0 positional arguments but 1 was givenI add the staticmethod decorator
34 @staticmethod 35 def hello(person): 36 return None 37 38 @staticmethod 39 def get_age(): 40 return Nonethe 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
hellomethod38 @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_name34 @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 thePersonobject in the call to thehellomethod intest_person.py100 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 seenTypeError: Person.hello() missing 1 required positional argument: 'person'I remove the staticmethod decorator from the
hellomethod inperson.py26 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
personin the parentheses is for thePersonclass that thehellomethod is part of.When I wrapped the
hellofunction 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 Symbolfeature of the Integrated Development Environment (IDE) to change the name of the input parameter frompersontoselfto match Python convention34 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_birthI 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
factoryfunction that returns a dictionary withyear_of_birthas 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 youI 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_birthkey117 self.assertEqual(person.get('age'), 446) 118 119 person['year_of_birth'] 120 121 122# Exceptions seenKeyError: 'year_of_birth'there is no
year_of_birthkey in the dictionary returned by thefactoryfunctionI add KeyError to the list of Exceptions seen
122# Exceptions seen 123# AssertionError 124# NameError 125# AttributeError 126# TypeError 127# SyntaxError 128# KeyErrorI add assertRaises
117 self.assertEqual(person.get('age'), 446) 118 119 with self.assertRaises(KeyError): 120 person['year_of_birth'] 121 122 123# Exceptions seenthe test passes
I add a new value for
year_of_birthwith the setdefault method119 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 seenthe terminal shows AssertionError
AssertionError: 1980 != NoneI 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_birthwith the new valueI add an assertion for the age of
john smithagain121 self.assertEqual( 122 person.setdefault('year_of_birth', 1980), 123 1980 124 ) 125 self.assertEqual(person.get('age'), 46) 126 127# Exceptions seenthe terminal shows AssertionError
AssertionError: 446 != 46the
factoryfunction uses the value foryear_of_birthto 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 seenthe terminal shows AttributeError
AttributeError: module 'src.person' has no attribute 'update_year_of_birth'I add the function to
person.py16def 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:TypeError: update_year_of_birth() takes 0 positional arguments but 2 were givenI add the two names in parentheses
24def update_year_of_birth(person, new_year_of_birth): 25 return Nonethe 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_birthintest_person.py111 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.factory112 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 seenthe 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 seenstill 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 seengreen
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 = 1980I 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_agemethod inperson.py49 @staticmethod 50 def get_age(): 51 return this_year() - self.year_of_birthNameError: name 'this_year' is not definedI 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(NameError: name 'self' is not definedI add
selfto the definition of theget_agemethod parentheses53 @staticmethod 54 def get_age(self): 55 return this_year() - self.year_of_birthTypeError: Person.get_age() missing 1 required positional argument: 'self'I remove the staticmethod decorator from the
get_agemethod47 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_birthAttributeError: 'Person' object has no attribute 'year_of_birth'I add a class attribute to the
__init__method37class 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_birththe terminal shows AssertionError
AssertionError: 446 != 0I change the expectation in
test_person.py156 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_birthexpects a number, thehellomethod returns NoneI add a call to the
get_agemethod in thehellomethod inperson.py48 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_yearfunction in thefactoryfunction inperson.py8def 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_birthintest_person.py156 self.assertEqual(person.get_age(), 446) 157 158 person.year_of_birth = 1980 159 160 161# Exceptions seenI 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 seenthe terminal shows AssertionError
AssertionError: 46 != 446I 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 seenI 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 seenthe 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 seenstill 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_nameto the setUp method intest_person.py17 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_nameandself.random_last_namelook the same. I add a class attribute for the values of the names passed to thechoosefunction15class 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_NAMESin the calls to thechoosefunction forrandom_first_nameandrandom_last_name22 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_namein test_factory_takes_keyword_arguments29 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
sexto the setUp method22 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
factoryfunction in the setUp method22 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_personclass attribute in the expectation of the assertion in test_factory_takes_keyword_arguments43 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_personvariable, I can use the class attributes43 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_persondictionary36 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_personclass attribute totest_factory_person_greeting72 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
factoryfunction and the for loop with the assertion because they are no longer needed, the random person covers all their cases and more61 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
Personclass to do the same thing fortest_classy_person_greeting. I will come back to it
I use the
random_year_of_birthclass attribute intest_update_factory_person_year_of_birth99 def test_update_factory_person_year_of_birth(self): 100 # original_year_of_birth = 1580 101 original_year_of_birth = self.random_year_of_birththe 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 = 1980still green
I point
personto theself.random_factory_personclass attribute104 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_personclass attribute in the first assertion111 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_birthvariable99 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_personclass attribute in the assertion for the setdefault method112 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_personin the second assertion for the age119 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_birthin the assertion for theyear_of_birthupdate122 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
personvariable99 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_birthis in the tests a few times. I add a class attribute for it in the setUp method22 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_greeting65 ( 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_ageintest_update_factory_person_year_of_birth100 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 = 1980the test is still green
I use
self.original_agein the first assertion103 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_agevariable100 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
Personclass to the setUp method30 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_greeting89 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
Personclass and the for loop with its assertion because they are no longer needed, the random person covers those cases and more78 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_greetingandtest_factory_person_greetingare now the same. I add a method to remove the repetition60 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_greeting75 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_greeting86 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_personclass attribute intest_update_classy_person_year_of_birth121 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 != 446I use
self.original_agein the first assertion for the age127 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 = 1980the test is green again
I remove the commented lines then use
self.random_classy_personin the first assertion121 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 = 1980the test is still green
I use the new year of birth as the value for the
year_of_birthattribute ofself.random_classy_person129 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_personin the second assertion129 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 seenthe test is still green
I remove the commented lines and the
personvariable121 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 seenstill green
the
new_year_of_birthvariable is the same intest_update_factory_person_year_of_birthandtest_update_classy_person_year_of_birth. I add a new class attribute to the setUp method because I want to use random numbers for it22 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_birththis 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_birthto the result of calling the new function in the setUp method28 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_birthattribute28 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_birthgreen
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_birthintest_update_factory_person_year_of_birth92 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_birthstill 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_birth116 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_birthvariable92 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 thenew_year_of_birthvariable to the class attribute124 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_birththe 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_birthvariable124 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 seenthe 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_birthI add a function that does the calculation to
test_person.py15def 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_agein the setUp method to the result of calling the function withself.random_year_of_birth32 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_birth121 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_birth136 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
Personclass without a value forlast_nameandsexand an assertion for the value of thelast_name70 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_name83 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_namein the__init__method of thePersonclass inperson.py37class 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
sexintest_person.py83 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 thePersonclass inperson.py39 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 Nonethe terminal shows AssertionError
AssertionError: None != 'M'I change the default value for
sex39 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_birthfrom the call to thePersonclass intest_class_w_default_argumentsintest_person.py78 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):TypeError: unsupported operand type(s) for -: 'int' and 'NoneType'because the
get_agemethod tries to subtractyear_of_birthfrom this year, andyear_of_birthis NoneI add a default value for
year_of_birthin the__init__method of thePersonclass inperson.py39 def __init__( 40 self, first_name, last_name='doe', 41 year_of_birth=this_year(), sex='M', 42 ):the terminal shows AssertionError
AssertionError: 0 != NoneI change the expectation in
test_person.py82 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_birthin the call tosrc.person.factoryin test_factory_w_default_arguments64 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 ),TypeError: factory() missing 1 required positional argument: 'year_of_birth'I add a default value for
year_of_birthto make it a choice in thefactoryfunction inperson.py8def factory( 9 first_name, year_of_birth=None, 10 last_name='doe', sex='M', 11 ):TypeError: unsupported operand type(s) for -: 'int' and 'NoneType'I change the default value for
year_of_birthto this year8def 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.py70 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.pyandperson.pyin the editorsI click in the terminal and exit the tests with ctrl+c on the keyboard
I deactivate the virtual environment
deactivatethe terminal goes back to the command line,
(.venv)is no longer on the left side.../pumping_python/personI change directory to the parent of
personcd ..the terminal shows
.../pumping_pythonI am back in the
pumping_pythondirectory
code from the chapter
review
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