how to make a person with loops
I have a problem in person/tests/test_person.py
test_joe, test_jane, test_john and test_mary have the same three tests.
test_when_year_of_birth_is_not_an_integer has four tests that are basically the same, the only thing that changes are the values for the
year_of_birthparameter.
This means that if I add more people or more cases where year_of_birth is not an integer I would have to add more tests.
I want to use one test for each of the three tests for each person, and one test for every case where year_of_birth is not an integer. I can do this with for loops.
A for loop is a way to repeat the same command over an iterable (a collection of items), it is written like this
for item in collection:
do something
preview
I have these tests by the end of the chapter
1import datetime
2import src.person
3import unittest
4
5
6class TestPerson(unittest.TestCase):
7
8 people = (
9 (
10 'jane', 'doe', 'F', 1991,
11 True, True, True, True
12 ),
13 (
14 'joe', 'blow', 'M', 1996,
15 True, False, True, False
16 ),
17 (
18 'mary', 'public', 'F', 2000,
19 False, True, False, True
20 ),
21 (
22 'john', 'smith', 'M', 1980,
23 False, False, False, False
24 ),
25 )
26
27 @staticmethod
28 def calculate_age(year_of_birth):
29 return (
30 datetime.date.today().year
31 - year_of_birth
32 )
34 def test_factory_function(self):
35 for a_person in self.people:
36 with self.subTest(first_name=a_person[0]):
37 first_name = a_person[0]
38 last_name = a_person[1]
39 sex = a_person[2]
40 year_of_birth = a_person[3]
41
42 reality = src.person.factory(
43 first_name=first_name,
44 last_name=last_name,
45 sex=sex,
46 year_of_birth=year_of_birth,
47 )
48 my_expectation = (
49 f'{first_name}, {last_name},'
50 f' {sex}, {year_of_birth}'
51 )
52 assert reality == my_expectation
53 self.assertEqual(reality, my_expectation)
55 def test_say_hello_function(self):
56 for a_person in self.people:
57 with self.subTest(first_name=a_person[0]):
58 first_name = a_person[0]
59 last_name = a_person[1]
60 year_of_birth = a_person[3]
61
62 reality = src.person.say_hello(
63 first_name=first_name,
64 last_name=last_name,
65 year_of_birth=year_of_birth,
66 )
67 my_expectation = (
68 f'Hello, my name is {first_name}'
69 f' {last_name} and I am'
70 f' {self.calculate_age(year_of_birth)}.'
71 )
72 assert reality == my_expectation
73 self.assertEqual(reality, my_expectation)
75 def test_person_class(self):
76 for a_person in self.people:
77 with self.subTest(first_name=a_person[0]):
78 first_name = a_person[0]
79 last_name = a_person[1]
80 sex = a_person[2]
81 year_of_birth = a_person[3]
82 is_citizen = a_person[4]
83 passed_test = a_person[5]
84
85 person = src.person.Person(
86 first_name=first_name,
87 last_name=last_name,
88 sex=sex,
89 year_of_birth=year_of_birth,
90 is_citizen=is_citizen,
91 passed_test=passed_test,
92 )
93
94 reality = person.say_hello()
95 my_expectation = (
96 f'Hello, my name is {first_name}'
97 f' {last_name} and I am'
98 f' {self.calculate_age(year_of_birth)}.'
99 )
100 assert reality == my_expectation
101 self.assertEqual(reality, my_expectation)
102 self.assertEqual(person.can_vote(), is_citizen)
103 self.assertEqual(
104 person.can_get_license(), passed_test
105 )
107 def test_underage_citizen(self):
108 person = src.person.Person(
109 first_name='first_name',
110 last_name='last_name',
111 sex='M',
112 year_of_birth=datetime.date.today().year-17,
113 is_citizen=True,
114 passed_test=True,
115 )
116 self.assertEqual(person.can_vote(), False)
117 self.assertEqual(
118 person.can_get_license(), False
119 )
121 def test_when_person_is_too_old_to_be_alive(self):
122 with self.assertRaises(ValueError):
123 src.person.Person(
124 first_name='first_name',
125 last_name='last_name',
126 sex='F',
127 year_of_birth=datetime.date.today().year-121,
128 )
130 def test_when_year_of_birth_is_not_an_integer(self):
131 for year_of_birth in (
132 datetime.date.today().year-121,
133 None,
134 2026.0,
135 '2026',
136 (2026,),
137 ):
138 with self.subTest(i=year_of_birth):
139 with self.assertRaises(TypeError):
140 src.person.Person(
141 first_name='first_name',
142 last_name='last_name',
143 sex='M',
144 year_of_birth=year_of_birth,
145 )
146
147 def test_dir_person_class(self):
open the project
I open a terminal
I change directory to the project
cd personI open
test_person.pyfrom thetestsfolderI use pytest-watcher to run the tests automatically
uv run pytest-watcher . --nowthe terminal shows
tests/test_person.py ......... [100%] ================= 9 passed in W.XYs ==================
extract test_factory_function
The tests for the factory function in test_joe, test_jane, test_john and test_mary make a call to the factory function then compare the result with a string.
RED: make it fail
I add a test for the factory function to tests/test_person.py
6class TestPerson(unittest.TestCase):
7
8 @staticmethod
9 def calculate_age(year_of_birth):
10 return (
11 datetime.date.today().year
12 - year_of_birth
13 )
14
15 def test_factory_function(self):
16 people = (
17 ('joe', 'blow', 'M', 1996),
18 )
19 for person in people:
20 reality = src.person.factory(
21 first_name=person[0],
22 last_name=person[1],
23 sex=person[2],
24 year_of_birth=person[3],
25 )
26 my_expectation = None
27 self.assertEqual(reality, my_expectation)
28
29 def test_joe(self):
the terminal is my friend, and shows AssertionError
AssertionError: 'joe, blow, M, 1996' != None
GREEN: make it pass
I change my_expectation to match reality
15 def test_factory_function(self):
16 people = (
17 ('joe', 'blow', 'M', 1996),
18 )
19 for person in people:
20 reality = src.person.factory(
21 first_name=person[0],
22 last_name=person[1],
23 sex=person[2],
24 year_of_birth=person[3],
25 )
26 my_expectation = 'joe, blow, M, 1996'
27 self.assertEqual(reality, my_expectation)
28
29 def test_joe(self):
the test passes.
I made a tuple named
peoplethat contains a tuple forjoepeople = ( ('joe', 'blow', 'M', 1996), )I use a for loop to repeat the same commands for each item in the
peopletuple, in this case there is only one item - the tuple forjoefor person in ( ('joe', 'blow', 'M', 1996), ):I use the index of each item in
joefor the parameters when the test calls the factory functionfor person in people: ├── a_person = ('joe', 'blow', 'M', 1996) └── reality = src.person.factory( first_name=person[0], last_name=person[1], sex=person[2], year_of_birth=person[3], ) └── src/person/__init__.py └── def factory( first_name, last_name, sex, year_of_birth, ): ├── first_name = 'joe' ├── last_name = 'blow' ├── sex = 'M' ├── year_of_birth = 1996 └── return ( f'{first_name}, {last_name},' f' {sex}, {year_of_birth}' ) return 'joe, blow, M, 1996'
REFACTOR: make it better
I add
janeto the tuple ofpeople15 def test_factory_function(self): 16 people = ( 17 ('jane', 'doe', 'F', 1991), 18 ('joe', 'blow', 'M', 1996), 19 ) 20 for person in people:the terminal is my friend, and shows AssertionError
AssertionError: 'jane, doe, F, 1991' != 'joe, blow, M, 1996'because the result when the factory function is called with
'jane','doe','F'and'1991'as input is'jane, doe, F, 1991'not'joe, blow, M, 1996'.for person in ( ┌───┴── ('jane', 'doe', 'F', 1991), │ ('joe', 'blow', 'M', 1996), │ ): ├── a_person = ('jane', 'doe', 'F', 1991) └── reality = src.person.factory( first_name=person[0], last_name=person[1], sex=person[2], year_of_birth=person[3], ) └── src/person/__init__.py └── def factory( first_name, last_name, sex, year_of_birth, ): ├── first_name = 'jane' ├── last_name = 'doe' ├── sex = 'F' ├── year_of_birth = 1991 └── return ( f'{first_name}, {last_name},' f' {sex}, {year_of_birth}' ) return 'jane, doe, F, 1991'I change
my_expectationto matchrealityforjane15 def test_factory_function(self): 16 people = ( 17 ('jane', 'doe', 'F', 1991), 18 ('joe', 'blow', 'M', 1996), 19 ) 20 for person in people: 21 reality = src.person.factory( 22 first_name=person[0], 23 last_name=person[1], 24 sex=person[2], 25 year_of_birth=person[3], 26 ) 27 # my_expectation = 'joe, blow, M, 1996' 28 my_expectation = 'jane, doe, F, 1991' 29 self.assertEqual(reality, my_expectation) 30 31 def test_joe(self):the terminal is my friend, and shows AssertionError
AssertionError: 'joe, blow, M, 1996' != 'jane, doe, F, 1991'because the result when the factory function is called with
'joe','blow','M'and'1996'as input is'joe, blow, M, 1996'not'jane, doe, F, 1991'. The for loop goes through each item in thepeopletuple one at a time.I change
my_expectationin test_factory_function to an f-string27 # my_expectation = 'joe, blow, M, 1996' 28 # my_expectation = 'jane, doe, F, 1991' 29 my_expectation = ( 30 f'{person[0]}, {person[1]},' 31 f' {person[2]}, {person[3]}' 32 ) 33 self.assertEqual(reality, my_expectation) 34 35 def test_joe(self):the test passes.
I add variables for
person[0],person[1],person[2]andperson[3]15 def test_factory_function(self): 16 people = ( 17 ('jane', 'doe', 'F', 1991), 18 ('joe', 'blow', 'M', 1996), 19 ) 20 for person in people: 21 first_name = person[0] 22 last_name = person[1] 23 sex = person[2] 24 year_of_birth = person[3] 25 26 reality = src.person.factory( 27 first_name=person[0], 28 last_name=person[1], 29 sex=person[2], 30 year_of_birth=person[3], 31 )I use the variables for
person[0],person[1],person[2]andperson[3]26 reality = src.person.factory( 27 # first_name=person[0], 28 # last_name=person[1], 29 # sex=person[2], 30 # year_of_birth=person[3], 31 first_name=first_name, 32 last_name=last_name, 33 sex=sex, 34 year_of_birth=year_of_birth, 35 ) 36 # my_expectation = 'joe, blow, M, 1996' 37 # my_expectation = 'jane, doe, F, 1991' 38 my_expectation = ( 39 # f'{person[0]}, {person[1]},' 40 # f' {person[2]}, {person[3]}' 41 f'{first_name}, {last_name},' 42 f' {sex}, {year_of_birth}' 43 ) 44 self.assertEqual(reality, my_expectation) 45 46 def test_joe(self):the test is still green.
I add a tuple for
mary15 def test_factory_function(self): 16 people = ( 17 ('jane', 'doe', 'F', 1991), 18 ('joe', 'blow', 'M', 1996), 19 ('mary', 'public', 'F', 2000), 20 ) 21 for person in people:still green.
I add a tuple for
john15 def test_factory_function(self): 16 people = ( 17 ('jane', 'doe', 'F', 1991), 18 ('joe', 'blow', 'M', 1996), 19 ('mary', 'public', 'F', 2000), 20 ('john', 'smith', 'M', 1980), 21 ) 22 for person in people:green, showing that for each tuple in the
peopletuple, the assertion is True.I add a tuple for a person with a string as the
year_of_birth15 def test_factory_function(self): 16 people = ( 17 ('jane', 'doe', 'F', 1991), 18 ('joe', 'blow', 'M', 1996), 19 ('mary', 'public', 'F', 2000), 20 ('john', 'smith', 'M', 1980), 21 ('first_name', 'last_name', 'F', 'a string'), 22 ) 23 for person in people:the test is still green, because the factory function returns a string with the inputs it gets.
I remove the commented lines from test_factory_function
15 def test_factory_function(self): 16 people = ( 17 ('jane', 'doe', 'F', 1991), 18 ('joe', 'blow', 'M', 1996), 19 ('mary', 'public', 'F', 2000), 20 ('john', 'smith', 'M', 1980), 21 ('first_name', 'last_name', 'F', 'a string'), 22 ) 23 for person in people: 24 first_name = person[0] 25 last_name = person[1] 26 sex = person[2] 27 year_of_birth = person[3] 28 29 reality = src.person.factory( 30 first_name=first_name, 31 last_name=last_name, 32 sex=sex, 33 year_of_birth=year_of_birth, 34 ) 35 my_expectation = ( 36 f'{first_name}, {last_name},' 37 f' {sex}, {year_of_birth}' 38 ) 39 self.assertEqual(reality, my_expectation) 40 41 def test_joe(self):I remove the test for the factory function from test_joe since it is now a repetition
41 def test_joe(self): 42 first_name = 'joe' 43 last_name = 'blow' 44 sex = 'M' 45 year_of_birth = 1996 46 47 reality = src.person.say_hello( 48 first_name=first_name, 49 last_name=last_name, 50 year_of_birth=year_of_birth, 51 ) 52 my_expectation = ( 53 f'Hello, my name is {first_name}' 54 f' {last_name} and I am' 55 f' {self.calculate_age(year_of_birth)}.' 56 ) 57 assert reality == my_expectation 58 self.assertEqual(reality, my_expectation) 59 60 joe = src.person.Person( 61 first_name=first_name, 62 last_name=last_name, 63 sex=sex, 64 year_of_birth=year_of_birth, 65 ) 66 67 reality = joe.say_hello() 68 assert reality == my_expectation 69 self.assertEqual(reality, my_expectation) 70 self.assertEqual(joe.can_vote(), True) 71 self.assertEqual(joe.can_get_license(), False) 72 73 def test_jane(self):I remove the test for the factory function from test_jane since it is now a repetition
73 def test_jane(self): 74 first_name = 'jane' 75 last_name = 'doe' 76 sex = 'F' 77 year_of_birth = 1991 78 79 reality = src.person.say_hello( 80 first_name=first_name, 81 last_name=last_name, 82 year_of_birth=year_of_birth, 83 ) 84 my_expectation = ( 85 f'Hello, my name is {first_name}' 86 f' {last_name} and I am' 87 f' {self.calculate_age(year_of_birth)}.' 88 ) 89 assert reality == my_expectation 90 self.assertEqual(reality, my_expectation) 91 92 jane = src.person.Person( 93 first_name=first_name, 94 last_name=last_name, 95 sex=sex, 96 year_of_birth=year_of_birth, 97 passed_test=True, 98 ) 99 100 reality = jane.say_hello() 101 assert reality == my_expectation 102 self.assertEqual(reality, my_expectation) 103 self.assertEqual(jane.can_vote(), True) 104 self.assertEqual(jane.can_get_license(), True) 105 106 def test_john(self):I remove the test for the factory function from test_john since it is now a repetition
106 def test_john(self): 107 first_name = 'john' 108 last_name = 'smith' 109 sex = 'M' 110 year_of_birth = 1980 111 112 reality = src.person.say_hello( 113 first_name=first_name, 114 last_name=last_name, 115 year_of_birth=year_of_birth, 116 ) 117 my_expectation = ( 118 f'Hello, my name is {first_name}' 119 f' {last_name} and I am' 120 f' {self.calculate_age(year_of_birth)}.' 121 ) 122 assert reality == my_expectation 123 self.assertEqual(reality, my_expectation) 124 125 john = src.person.Person( 126 first_name=first_name, 127 last_name=last_name, 128 sex=sex, 129 year_of_birth=year_of_birth, 130 is_citizen=False, 131 ) 132 133 reality = john.say_hello() 134 assert reality == my_expectation 135 self.assertEqual(reality, my_expectation) 136 self.assertEqual(john.can_vote(), False) 137 self.assertEqual(john.can_get_license(), False) 138 139 def test_mary(self):I remove the test for the factory function from test_mary since it is now a repetition
139 def test_mary(self): 140 first_name = 'mary' 141 last_name = 'public' 142 sex = 'F' 143 year_of_birth = 2000 144 145 reality = src.person.say_hello( 146 first_name=first_name, 147 last_name=last_name, 148 year_of_birth=year_of_birth, 149 ) 150 my_expectation = ( 151 f'Hello, my name is {first_name}' 152 f' {last_name} and I am' 153 f' {self.calculate_age(year_of_birth)}.' 154 ) 155 assert reality == my_expectation 156 self.assertEqual(reality, my_expectation) 157 158 mary = src.person.Person( 159 first_name=first_name, 160 last_name=last_name, 161 sex=sex, 162 year_of_birth=year_of_birth, 163 is_citizen=False, 164 passed_test=True, 165 ) 166 167 reality = mary.say_hello() 168 assert reality == my_expectation 169 self.assertEqual(reality, my_expectation) 170 self.assertEqual(mary.can_vote(), False) 171 self.assertEqual(mary.can_get_license(), True) 172 173 def test_underage_citizen(self):I add a git commit message
git commit -am 'extract test_factory_function'
The for loop allows me to test any number of people with the same test. I no longer have to write one test for each person.
extract test_say_hello_function
The tests for the say_hello function in test_joe, test_jane, test_john and test_mary make a call to the say_hello function then compare the result with a string.
RED: make it fail
I add a test for the say_hello function
39 self.assertEqual(reality, my_expectation)
40
41 def test_say_hello_function(self):
42 people = (
43 ('jane', 'doe', 'F', 1991),
44 ('joe', 'blow', 'M', 1996),
45 ('mary', 'public', 'F', 2000),
46 ('john', 'smith', 'M', 1980),
47 ('first_name', 'last_name', 'F', 'a string'),
48 )
49 for person in people:
50 first_name = person[0]
51 last_name = person[1]
52 year_of_birth = person[3]
53
54 reality = src.person.say_hello(
55 first_name=first_name,
56 last_name=last_name,
57 year_of_birth=year_of_birth,
58 )
59 my_expectation = None
60 self.assertEqual(reality, my_expectation)
61
62 def test_joe(self):
the terminal is my friend, and shows AssertionError
AssertionError:
'Hello, my name is jane doe and I am 35.'
!= None
GREEN: make it pass
I change
my_expectationin test_say_hello_function to match the string in the terminal54 reality = src.person.say_hello( 55 first_name=first_name, 56 last_name=last_name, 57 year_of_birth=year_of_birth, 58 ) 59 my_expectation = ( 60 'Hello, my name is jane doe' 61 ' and I am 35.' 62 ) 63 self.assertEqual(reality, my_expectation) 64 65 def test_joe(self):the terminal is my friend, and shows AssertionError
AssertionError: 'Hello, my name is joe blow and I am 30.' != 'Hello, my name is jane doe and I am 35.'the test passed for
janebut fails forjoe.I change
my_expectationto an f-string in test_say_hello_function54 reality = src.person.say_hello( 55 first_name=first_name, 56 last_name=last_name, 57 year_of_birth=year_of_birth, 58 ) 59 # my_expectation = ( 60 # 'Hello, my name is jane doe' 61 # ' and I am 35.' 62 # ) 63 my_expectation = ( 64 f'Hello, my name is {first_name}' 65 f' {last_name} and I am' 66 f' {self.calculate_age(year_of_birth)}.' 67 ) 68 self.assertEqual(reality, my_expectation) 69 70 def test_joe(self):the terminal is my friend, and shows TypeError
E TypeErrorbecause the say_hello function calls the calculate_age function which raises TypeError when
year_of_birthis not an integerfor person in ( │ ('jane', 'doe', 'F', 1991), │ ('joe', 'blow', 'M', 1996), │ ('mary', 'public', 'F', 2000), │ ('john', 'smith', 'M', 1980), ┌───┴── ('first_name', 'last_name', 'F', 'a string'), │ ): ├── a_person = ('first_name', 'last_name', 'F', 'a string') ├── first_name = person[0] ├── last_name = person[1] ├── year_of_birth = person[3] └── reality = src.person.say_hello( first_name=first_name, last_name=last_name, year_of_birth=year_of_birth, ) └── src/person/__init__.py └── def say_hello( first_name, last_name, year_of_birth, ): ├── first_name = 'first_name' ├── last_name = 'last_name' ├── year_of_birth = 'a string' └── return ( ├── f'Hello, my name is {first_name}' ├── f' {last_name} and I am' └── f' {calculate_age(year_of_birth)}.' ) │ └── def calculate_age(year_of_birth): ├── year_of_birth = 'a string' └── if not isinstance( year_of_birth, int ): └── raise TypeError ...I add a message to the calculate_age function for when TypeError is raised, in
src/person/__init__.py44def calculate_age(year_of_birth): 45 if not isinstance(year_of_birth, int): 46 raise TypeError( 47 f"'{year_of_birth}' is not an integer" 48 )the terminal is my friend, and shows TypeError
TypeError: 'a string' is not an integerbetter.
I add a try statement to test_say_hello_function in
tests/test_person.py49 for person in people: 50 first_name = person[0] 51 last_name = person[1] 52 year_of_birth = person[3] 53 54 try: 55 reality = src.person.say_hello( 56 first_name=first_name, 57 last_name=last_name, 58 year_of_birth=year_of_birth, 59 ) 60 except TypeError: 61 pass 62 63 # my_expectation = ( 64 # 'Hello, my name is jane doe' 65 # ' and I am 35.' 66 # )the terminal is my friend, and shows TypeError
TypeError: unsupported operand type(s) for -: 'int' and 'str'because
my_expectationin test_say_hello_function makes a call to the calculate_age method of TestPerson which raises TypeError because I cannot do subtraction with a string and a number.for person in ( │ ('jane', 'doe', 'F', 1991), │ ('joe', 'blow', 'M', 1996), │ ('mary', 'public', 'F', 2000), │ ('john', 'smith', 'M', 1980), ┌───┴── ('first_name', 'last_name', 'F', 'a string'), │ ): ├── a_person = ('first_name', 'last_name', 'F', 'a string') ├── first_name = person[0] ├── last_name = person[1] ├── year_of_birth = person[3] │ ... └── my_expectation = ( ├── f'Hello, my name is {first_name}' ├── f' {last_name} and I am' └── f' {self.calculate_age(year_of_birth)}.' ) │ │ @staticmethod └── def calculate_age(year_of_birth): ├── year_of_birth = 'a string' └── return ( datetime.date.today().year - year_of_birth )I add an else clause to test_say_hello_function so that it only runs the assertion if the call to the say_hello function does not raise TypeError
54 try: 55 reality = src.person.say_hello( 56 first_name=first_name, 57 last_name=last_name, 58 year_of_birth=year_of_birth, 59 ) 60 except TypeError: 61 pass 62 else: 63 my_expectation = ( 64 f'Hello, my name is {first_name}' 65 f' {last_name} and I am' 66 f' {self.calculate_age(year_of_birth)}.' 67 ) 68 self.assertEqual(reality, my_expectation) 69 70 # my_expectation = ( 71 # 'Hello, my name is jane doe' 72 # ' and I am 35.' 73 # ) 74 75 def test_joe(self):the test passes.
REFACTOR: make it better
I want to test the error message to make sure test_say_hello_function only catches TypeError with this specific message. I can use the Exception in the except block as an object.
54 try: 55 reality = src.person.say_hello( 56 first_name=first_name, 57 last_name=last_name, 58 year_of_birth=year_of_birth, 59 ) 60 except TypeError as error: 61 pass 62 else:the test is still green
I add assertEqual to the except block with the dir built-in function to see the attributes and methods of the Exception in the except block
60 except TypeError as error: 61 self.assertEqual(dir(error), []) 62 else:the terminal is my friend, and shows AssertionError with the list of attributes and methods. Three of the names stand out because they do not have double underscores (
__) before and after -add_note,argsandwith_traceback.I change the assertion to see what is in
add_note60 except TypeError as error: 61 # self.assertEqual(dir(error), []) 62 self.assertEqual(error.add_note, None) 63 else:the terminal is my friend, and shows AssertionError
AssertionError: <built-in method add_note of TypeError object at 0xffff7ed65c43> != Nonebecause
add_noteis a method.-
60 except TypeError as error: 61 # self.assertEqual(dir(error), []) 62 self.assertEqual(error.add_note(), None) 63 else:the terminal is my friend, and shows TypeError
TypeError: BaseException.add_note() takes exactly one argument (0 given)this is not what I want, on to the next one.
I change the assertion to see what is in
args60 except TypeError as error: 61 # self.assertEqual(dir(error), []) 62 # self.assertEqual(error.add_note(), None) 63 self.assertEqual(error.args, None) 64 else:the terminal is my friend, and shows AssertionError
AssertionError: ("'a string' is not an integer",) != Nonefantastic!
argsis a tuple that has the error message as its first and only item.I use the index of the error message with an f-string
60 except TypeError as error: 61 # self.assertEqual(dir(error), []) 62 # self.assertEqual(error.add_note(), None) 63 self.assertEqual( 64 error.args[0], 65 f"'{year_of_birth}' is not an integer" 66 ) 67 else:the test passes.
I change the message for TypeError in the calculate_age function in
src/person/__init__.pyto test my change44def calculate_age(year_of_birth): 45 if not isinstance(year_of_birth, int): 46 raise TypeError('BOOM!!!') 47 raise TypeError( 48 f"'{year_of_birth}' is not an integer" 49 )the terminal is my friend, and shows AssertionError
AssertionError: 'BOOM!!!' != "'a string' is not an integer"I undo the change
44def calculate_age(year_of_birth): 45 if not isinstance(year_of_birth, int): 46 raise TypeError( 47 f"'{year_of_birth}' is not an integer" 48 ) 49 50 age = ( 51 datetime.date.today().year 52 - year_of_birth 53 ) 54 55 if age > 120: 56 raise ValueError 57 return age 58 59 60def say_hello( 61 first_name, last_name, year_of_birth, 62):the test is green again.
I remove the commented lines from test_say_hello_function in
tests/test_person.pydef test_say_hello_function(self): people = ( ('jane', 'doe', 'F', 1991), ('joe', 'blow', 'M', 1996), ('mary', 'public', 'F', 2000), ('john', 'smith', 'M', 1980), ('first_name', 'last_name', 'F', 'a string'), ) for person in people: first_name = person[0] last_name = person[1] year_of_birth = person[3] try: reality = src.person.say_hello( first_name=first_name, last_name=last_name, year_of_birth=year_of_birth, ) except TypeError as error: self.assertEqual( error.args[0], f"'{year_of_birth}' is not an integer" ) else: my_expectation = ( f'Hello, my name is {first_name}' f' {last_name} and I am' f' {self.calculate_age(year_of_birth)}.' ) self.assertEqual(reality, my_expectation) def test_joe(self):I remove the test for the say_hello function from test_joe since it is now a repetition, and move
my_expectationbelowrealityin the test for the say_hello method73 def test_joe(self): 74 first_name = 'joe' 75 last_name = 'blow' 76 sex = 'M' 77 year_of_birth = 1996 78 79 joe = src.person.Person( 80 first_name=first_name, 81 last_name=last_name, 82 sex=sex, 83 year_of_birth=year_of_birth, 84 ) 85 86 reality = joe.say_hello() 87 my_expectation = ( 88 f'Hello, my name is {first_name}' 89 f' {last_name} and I am' 90 f' {self.calculate_age(year_of_birth)}.' 91 ) 92 assert reality == my_expectation 93 self.assertEqual(reality, my_expectation) 94 self.assertEqual(joe.can_vote(), True) 95 self.assertEqual(joe.can_get_license(), False) 96 97 def test_jane(self):I remove the test for the say_hello function from test_jane since it is now a repetition, and move
my_expectationbelowrealityin the test for the say_hello method97 def test_jane(self): 98 first_name = 'jane' 99 last_name = 'doe' 100 sex = 'F' 101 year_of_birth = 1991 102 103 jane = src.person.Person( 104 first_name=first_name, 105 last_name=last_name, 106 sex=sex, 107 year_of_birth=year_of_birth, 108 passed_test=True, 109 ) 110 111 reality = jane.say_hello() 112 my_expectation = ( 113 f'Hello, my name is {first_name}' 114 f' {last_name} and I am' 115 f' {self.calculate_age(year_of_birth)}.' 116 ) 117 assert reality == my_expectation 118 self.assertEqual(reality, my_expectation) 119 self.assertEqual(jane.can_vote(), True) 120 self.assertEqual(jane.can_get_license(), True) 121 122 def test_john(self):I remove the test for the say_hello function from test_john since it is now a repetition, and move
my_expectationbelowrealityin the test for the say_hello method122 def test_john(self): 123 first_name = 'john' 124 last_name = 'smith' 125 sex = 'M' 126 year_of_birth = 1980 127 128 john = src.person.Person( 129 first_name=first_name, 130 last_name=last_name, 131 sex=sex, 132 year_of_birth=year_of_birth, 133 is_citizen=False, 134 ) 135 136 reality = john.say_hello() 137 my_expectation = ( 138 f'Hello, my name is {first_name}' 139 f' {last_name} and I am' 140 f' {self.calculate_age(year_of_birth)}.' 141 ) 142 assert reality == my_expectation 143 self.assertEqual(reality, my_expectation) 144 self.assertEqual(john.can_vote(), False) 145 self.assertEqual(john.can_get_license(), False) 146 147 def test_mary(self):I remove the test for the say_hello function from test_mary since it is now a repetition, and move
my_expectationbelowrealityin the test for the say_hello method147 def test_mary(self): 148 first_name = 'mary' 149 last_name = 'public' 150 sex = 'F' 151 year_of_birth = 2000 152 153 mary = src.person.Person( 154 first_name=first_name, 155 last_name=last_name, 156 sex=sex, 157 year_of_birth=year_of_birth, 158 is_citizen=False, 159 passed_test=True, 160 ) 161 162 reality = mary.say_hello() 163 my_expectation = ( 164 f'Hello, my name is {first_name}' 165 f' {last_name} and I am' 166 f' {self.calculate_age(year_of_birth)}.' 167 ) 168 assert reality == my_expectation 169 self.assertEqual(reality, my_expectation) 170 self.assertEqual(mary.can_vote(), False) 171 self.assertEqual(mary.can_get_license(), True) 172 173 def test_underage_citizen(self):I add a git commit message
git commit -am 'extract test_say_hello_function'
For each person in the people tuple, this test calls the say_hello function
If the call raises TypeError, it asserts that the error message is correct
If the error message is not correct it raises AssertionError
for person in ( │ ('jane', 'doe', 'F', 1991), │ ('joe', 'blow', 'M', 1996), │ ('mary', 'public', 'F', 2000), │ ('john', 'smith', 'M', 1980), ┌───┴── ('first_name', 'last_name', 'F', 'a string'), │ ): ├── a_person = ('first_name', 'last_name', 'F', 'a string') ├── first_name = person[0] ├── last_name = person[1] ├── year_of_birth = person[3] └── try: reality = src.person.say_hello( first_name=first_name, last_name=last_name, year_of_birth=year_of_birth, ) └── src/person/__init__.py └── def say_hello( first_name, last_name, year_of_birth, ): ├── first_name = 'first_name' ├── last_name = 'last_name' ├── year_of_birth = 'a string' └── return ( ├── f'Hello, my name is {first_name}' ├── f' {last_name} and I am' └── f' {calculate_age(year_of_birth)}.' ) │ └── def calculate_age(year_of_birth): ├── year_of_birth = 'a string' └── if not isinstance( year_of_birth, int ): ┌───────────────────────────────┴── raise TypeError( │ 'BOOM!!!' │ ) └── except TypeError as error: └── self.assertEqual( error.args[0], f"'{year_of_birth}' is not an integer" ) └── raise AssertionError else: ...If the error message is correct the test passes
for person in ( │ ('jane', 'doe', 'F', 1991), │ ('joe', 'blow', 'M', 1996), │ ('mary', 'public', 'F', 2000), │ ('john', 'smith', 'M', 1980), ┌───┴── ('first_name', 'last_name', 'F', 'a string'), │ ): ├── a_person = ('first_name', 'last_name', 'F', 'a string') ├── first_name = person[0] ├── last_name = person[1] ├── year_of_birth = person[3] └── try: reality = src.person.say_hello( first_name=first_name, last_name=last_name, year_of_birth=year_of_birth, ) └── src/person/__init__.py └── def say_hello( first_name, last_name, year_of_birth, ): ├── first_name = 'first_name' ├── last_name = 'last_name' ├── year_of_birth = 'a string' └── return ( ├── f'Hello, my name is {first_name}' ├── f' {last_name} and I am' └── f' {calculate_age(year_of_birth)}.' ) │ └── def calculate_age(year_of_birth): ├── year_of_birth = 'a string' └── if not isinstance( year_of_birth, int ): ┌───────────────────────────────┴── raise TypeError( │ f"'{year_of_birth}'" │ " is not an integer" │ ) └── except TypeError as error: └── self.assertEqual( error.args[0], f"'{year_of_birth}' is not an integer" ) else: ...
If the call to the say_hello function does not raise TypeError, it asserts that the result of the call matches the expectation
for person in ( │ ('jane', 'doe', 'F', 1991), │ ('joe', 'blow', 'M', 1996), │ ('mary', 'public', 'F', 2000), ┌───┴── ('john', 'smith', 'M', 1980), │ ('first_name', 'last_name', 'F', 'a string'), │ ): ├── a_person = ('john', 'smith', 'M', 1980), ├── first_name = person[0] ├── last_name = person[1] ├── year_of_birth = person[3] ├── try: │ reality = src.person.say_hello( │ first_name=first_name, │ last_name=last_name, │ year_of_birth=year_of_birth, │ ) │ └── src/person/__init__.py │ └── def say_hello( │ first_name, last_name, year_of_birth, │ ): │ ├── first_name = 'john' │ ├── last_name = 'smith' │ ├── year_of_birth = 1980 │ └── return ( │ ├── f'Hello, my name is {first_name}' │ ├── f' {last_name} and I am' │ └── f' {calculate_age(year_of_birth)}.' │ ) │ │ └── def calculate_age(year_of_birth): │ ├── ... │ └── return age │ except TypeError as error: │ ... └── else: ├── my_expectation = ( │ ├── f'Hello, my name is {first_name}' │ ├── f' {last_name} and I am' │ └── f' {self.calculate_age(year_of_birth)}.' │ ) │ │ │ @staticmethod │ └── def calculate_age(year_of_birth): │ ├── year_of_birth = 2000 │ └── return ( │ datetime.date.today().year │ - year_of_birth │ ) └── self.assertEqual(reality, my_expectation)
extract test_say_hello_method
The tests in test_joe, test_jane, test_john and test_mary make an instance of the Person class, then call its say_hello comparing the results in assertions.
RED: make it fail
I add a test for the Person class
71 self.assertEqual(reality, my_expectation)
72
73 def test_say_hello_method(self):
74 people = (
75 ('jane', 'doe', 'F', 1991),
76 ('joe', 'blow', 'M', 1996),
77 ('mary', 'public', 'F', 2000),
78 ('john', 'smith', 'M', 1980),
79 ('first_name', 'last_name', 'F', 'a string'),
80 )
81 for person in people:
82 first_name = person[0]
83 last_name = person[1]
84 sex = person[2]
85 year_of_birth = person[3]
86
87 a_person = src.person.Person(
88 first_name=first_name,
89 last_name=last_name,
90 sex=sex,
91 year_of_birth=year_of_birth,
92 )
93
94 self.assertEqual(
95 a_person.say_hello(),
96 None
97 )
98
99 def test_joe(self):
the terminal is my friend, and shows AssertionError
AssertionError:
'Hello, my name is jane doe and I am 35.'
!= None
GREEN: make it pass
I change the expectation of the assertion to match the string from the terminal
94 self.assertEqual( 95 a_person.say_hello(), 96 'Hello, my name is jane doe and I am 35.' 97 ) 98 99 def test_joe(self):the terminal is my friend, and shows AssertionError
AssertionError: 'Hello, my name is joe blow and I am 30.' != 'Hello, my name is jane doe and I am 35.'I change the expectation of the assertion to an f-string
94 self.assertEqual( 95 a_person.say_hello(), 96 ( 97 f'Hello, my name is {first_name}' 98 f' {last_name} and I am' 99 f' {self.calculate_age(year_of_birth)}.' 100 ) 101 ) 102 103 def test_joe(self):the terminal is my friend, and shows TypeError
TypeError: 'a string' is not an integerthis is the correct Exception for when the
year_of_birthis not an integer. I need a better way to know which item in thepeopletuple raised the Exception.
the subTest method
unittest.TestCase has a method that I can use to show what items in a loop cause a failure in a test. It gives me a way to name each sub test in each loop.
I add a call to the unittest.TestCase.subTest method
81 for person in people: 82 first_name = person[0] 83 last_name = person[1] 84 sex = person[2] 85 year_of_birth = person[3] 86 87 with self.subTest(first_name=first_name): 88 a_person = src.person.Person( 89 first_name=first_name, 90 last_name=last_name, 91 sex=sex, 92 year_of_birth=year_of_birth, 93 ) 94 self.assertEqual( 95 a_person.say_hello(), 96 ( 97 f'Hello, my name is {first_name}' 98 f' {last_name} and I am' 99 f' {self.calculate_age(year_of_birth)}.' 100 ) 101 ) 102 103 def test_joe(self):the terminal is my friend, and shows TypeError
SUBFAILED(first_name='first_name') ...test_say_hello_method - TypeError: 'a string' is not an integerSUBFAILED(first_name='first_name')shows the value I gave inwith self.subTest(first_name=first_name)as the label for the sub test. I can use any name and values I want.I add
year_of_birthto the call to the subTest method81 for person in people: 82 first_name = person[0] 83 last_name = person[1] 84 sex = person[2] 85 year_of_birth = person[3] 86 87 with self.subTest( 88 first_name=first_name, 89 year_of_birth=year_of_birth, 90 ): 91 a_person = src.person.Person( 92 first_name=first_name, 93 last_name=last_name, 94 sex=sex, 95 year_of_birth=year_of_birth, 96 )the terminal still shows AssertionError with the extra name and value.
SUBFAILED(first_name='first_name', year_of_birth='a string') ... test_say_hello_method - TypeError: 'a string' is not an integerI add a try statement for TypeError and this error message to test_say_hello_method
73 def test_say_hello_method(self): 74 people = ( 75 ('jane', 'doe', 'F', 1991), 76 ('joe', 'blow', 'M', 1996), 77 ('mary', 'public', 'F', 2000), 78 ('john', 'smith', 'M', 1980), 79 ('first_name', 'last_name', 'F', 'a string'), 80 ) 81 for person in people: 82 first_name = person[0] 83 last_name = person[1] 84 sex = person[2] 85 year_of_birth = person[3] 86 87 with self.subTest( 88 first_name=first_name, 89 year_of_birth=year_of_birth, 90 ): 91 try: 92 a_person = src.person.Person( 93 first_name=first_name, 94 last_name=last_name, 95 sex=sex, 96 year_of_birth=year_of_birth, 97 ) 98 except TypeError as error: 99 self.assertEqual( 100 error.args[0], 101 f"'{year_of_birth}' is not an integer" 102 ) 103 else: 104 self.assertEqual( 105 a_person.say_hello(), 106 ( 107 f'Hello, my name is {first_name}' 108 f' {last_name} and I am' 109 f' {self.calculate_age(year_of_birth)}.' 110 ) 111 ) 112 113 def test_joe(self):the test passes.
REFACTOR: make it better
I change the error message for if the value of the
year_of_birthparameter is not an integer in the calculate_age function, insrc/person/__init__.py44def calculate_age(year_of_birth): 45 if not isinstance(year_of_birth, int): 46 raise TypeError('BOOM') 47 raise TypeError( 48 f"'{year_of_birth}' is not an integer" 49 )the terminal is my friend, and shows ASsertionError
SUBFAILED(first_name='first_name', year_of_birth='a string') ... test_say_hello_method - AssertionError: 'BOOM' != "'a string' is not an integer" FAILED tests/test_person.py... test_say_hello_function - AssertionError: 'BOOM' != "'a string' is not an integer"I want test_say_hello_function to also show which person raises an Exception. I add a call to the subTest method from test_say_hello_function in
tests/test_person.py41 def test_say_hello_function(self): 42 people = ( 43 ('jane', 'doe', 'F', 1991), 44 ('joe', 'blow', 'M', 1996), 45 ('mary', 'public', 'F', 2000), 46 ('john', 'smith', 'M', 1980), 47 ('first_name', 'last_name', 'F', 'a string'), 48 ) 49 for person in people: 50 first_name = person[0] 51 last_name = person[1] 52 year_of_birth = person[3] 53 54 with self.subTest( 55 first_name=first_name, 56 year_of_birth=year_of_birth, 57 ): 58 try: 59 reality = src.person.say_hello( 60 first_name=first_name, 61 last_name=last_name, 62 year_of_birth=year_of_birth, 63 ) 64 except TypeError as error: 65 self.assertEqual( 66 error.args[0], 67 f"'{year_of_birth}' is not an integer" 68 ) 69 else: 70 my_expectation = ( 71 f'Hello, my name is {first_name}' 72 f' {last_name} and I am' 73 f' {self.calculate_age(year_of_birth)}.' 74 ) 75 self.assertEqual(reality, my_expectation) 76 77 def test_say_hello_method(self):the terminal shows AssertionError with the first name and year_of_birth of the person that raised the Exception
SUBFAILED(first_name='first_name', year_of_birth='a string') ... test_say_hello_method - AssertionError: 'BOOM' != "'a string' is not an integer" SUBFAILED(first_name='first_name', year_of_birth='a string') ... test_say_hello_function - AssertionError: 'BOOM' != "'a string' is not an integer"I change the error message in in the calculate_age function if the value of the
year_of_birthparameter is not an integer back to the correct message, insrc/person/__init__.py44def calculate_age(year_of_birth): 45 if not isinstance(year_of_birth, int): 46 raise TypeError( 47 f"'{year_of_birth}' is not an integer" 48 ) 49 ...the tests are green again.
I remove the assertions for the say_hello method from test_joe
104 def test_joe(self): 105 first_name = 'joe' 106 last_name = 'blow' 107 sex = 'M' 108 year_of_birth = 1996 109 110 joe = src.person.Person( 111 first_name=first_name, 112 last_name=last_name, 113 sex=sex, 114 year_of_birth=year_of_birth, 115 ) 116 self.assertEqual(joe.can_vote(), True) 117 self.assertEqual(joe.can_get_license(), False) 118 119 def test_jane(self):I remove the assertions for the say_hello method from test_jane
132 def test_jane(self): 133 first_name = 'jane' 134 last_name = 'doe' 135 sex = 'F' 136 year_of_birth = 1991 137 138 jane = src.person.Person( 139 first_name=first_name, 140 last_name=last_name, 141 sex=sex, 142 year_of_birth=year_of_birth, 143 passed_test=True, 144 ) 145 self.assertEqual(jane.can_vote(), True) 146 self.assertEqual(jane.can_get_license(), True) 147 148 def test_john(self):I remove the assertions for the say_hello method from test_john
148 def test_john(self): 149 first_name = 'john' 150 last_name = 'smith' 151 sex = 'M' 152 year_of_birth = 1980 153 154 john = src.person.Person( 155 first_name=first_name, 156 last_name=last_name, 157 sex=sex, 158 year_of_birth=year_of_birth, 159 is_citizen=False, 160 ) 161 self.assertEqual(john.can_vote(), False) 162 self.assertEqual(john.can_get_license(), False) 163 164 def test_mary(self):I remove the assertions for the say_hello method from test_mary
164 def test_mary(self): 165 first_name = 'mary' 166 last_name = 'public' 167 sex = 'F' 168 year_of_birth = 2000 169 170 mary = src.person.Person( 171 first_name=first_name, 172 last_name=last_name, 173 sex=sex, 174 year_of_birth=year_of_birth, 175 is_citizen=False, 176 passed_test=True, 177 ) 178 self.assertEqual(mary.can_vote(), False) 179 self.assertEqual(mary.can_get_license(), True) 180 181 def test_underage_citizen(self):I add a git commit message
git commit -am 'extract test_say_hello_method'
For each person in the people tuple, this test makes an instance of the Person class which makes an age attribute by calling the calculate_age with the given year_of_birth parameter.
If the call raises TypeError, it asserts that the error message is correct
If the error message is not correct it raises AssertionError
for person in ( │ ('jane', 'doe', 'F', 1991), │ ('joe', 'blow', 'M', 1996), │ ('mary', 'public', 'F', 2000), │ ('john', 'smith', 'M', 1980), ┌───┴── ('first_name', 'last_name', 'F', 'a string'), │ ): ├── a_person = ('first_name', 'last_name', 'F', 'a string') ├── first_name = person[0] ├── last_name = person[1] ├── sex = person[2] ├── year_of_birth = person[3] └── with self.subTest( first_name=first_name, year_of_birth=year_of_birth, ): └── try: └── a_person = src.person.Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── src/person/__init__.py └── class Person: └── def __init__( self, first_name, last_name, sex, year_of_birth=None, is_citizen=True, passed_test=False, ): ├── self.first_name = first_name ├── self.last_name = last_name ├── self.year_of_birth = year_of_birth ├── self.sex = sex ├── self.is_citizen = is_citizen ├── self.passed_test = passed_test └── self.age = calculate_age( year_of_birth ) └── def calculate_age( year_of_birth ): ├── year_of_birth = 'a string' └── if not isinstance( year_of_birth, int ): ┌───────────────────────────────┴── raise TypeError( │ 'BOOM!!!' │ ) └── except TypeError as error: └── self.assertEqual( error.args[0], f"'{year_of_birth}' is not an integer" ) └── raise AssertionError else: ...If the error message is correct the test passes
for person in ( │ ('jane', 'doe', 'F', 1991), │ ('joe', 'blow', 'M', 1996), │ ('mary', 'public', 'F', 2000), │ ('john', 'smith', 'M', 1980), ┌───┴── ('first_name', 'last_name', 'F', 'a string'), │ ): ├── a_person = ('first_name', 'last_name', 'F', 'a string') ├── first_name = person[0] ├── last_name = person[1] ├── sex = person[2] ├── year_of_birth = person[3] └── with self.subTest( first_name=first_name, year_of_birth=year_of_birth, ): └── try: └── a_person = src.person.Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── src/person/__init__.py └── class Person: └── def __init__( self, first_name, last_name, sex, year_of_birth=None, is_citizen=True, passed_test=False, ): ├── self.first_name = first_name ├── self.last_name = last_name ├── self.year_of_birth = year_of_birth ├── self.sex = sex ├── self.is_citizen = is_citizen ├── self.passed_test = passed_test └── self.age = calculate_age( year_of_birth ) └── def calculate_age( year_of_birth ): ├── year_of_birth = 'a string' └── if not isinstance( year_of_birth, int ): ┌───────────────────────────────┴── raise TypeError( │ f"'{year_of_birth}'" │ " is not an integer" │ ) └── except TypeError as error: └── self.assertEqual( error.args[0], f"'{year_of_birth}' is not an integer" ) else: ...
If the call to the calculate_age function does not raise TypeError, it asserts that the result of the call to the say_hello method matches the expectation
for person in ( │ ('jane', 'doe', 'F', 1991), ┌───┴── ('joe', 'blow', 'M', 1996), │ ('mary', 'public', 'F', 2000), │ ('john', 'smith', 'M', 1980), │ ('first_name', 'last_name', 'F', 'a string'), │ ): ├── a_person = ('first_name', 'last_name', 'F', 'a string') ├── first_name = person[0] ├── last_name = person[1] ├── sex = person[2] ├── year_of_birth = person[3] └── with self.subTest( first_name=first_name, year_of_birth=year_of_birth, ): ├── try: │ └── a_person = src.person.Person( │ first_name=first_name, │ last_name=last_name, │ sex=sex, │ year_of_birth=year_of_birth, │ ) │ └── src/person/__init__.py │ └── class Person: │ └── def __init__( │ self, first_name, last_name, │ sex, year_of_birth=None, │ is_citizen=True, │ passed_test=False, │ ): │ ├── self.first_name = first_name │ ├── self.last_name = last_name │ ├── self.year_of_birth = year_of_birth │ ├── self.sex = sex │ ├── self.is_citizen = is_citizen │ ├── self.passed_test = passed_test │ └── self.age = calculate_age( │ year_of_birth │ ) │ └── def calculate_age( │ year_of_birth │ ): │ ├── year_of_birth = 1996 │ ├── ... │ └── return age │ except TypeError as error: │ ... └── else: └── self.assertEqual( ├── a_person.say_hello(), │ └── src/person/__init__.py │ └── class Person: │ │ ... │ └── def say_hello(self): │ └── return ( │ ├── 'Hello, my name is' │ ├── f' {self.first_name}' │ ├── f' {self.last_name} and I am' │ └── f' {self.age}.' │ ) └── ( ├── f'Hello, my name is {first_name}' ├── f' {last_name} and I am' └── f' {self.calculate_age(year_of_birth)}.' ) │ ) │ @staticmethod └── def calculate_age(year_of_birth): ├── year_of_birth = 1996 └── return ( datetime.date.today().year - year_of_birth )
extract people class attribute
test_factory_function, test_say_hello_function and test_say_hello_method all use the same tuple of persons.
I add a class attribute for the
peopletuple to the TestPerson class6class TestPerson(unittest.TestCase): 7 8 people = ( 9 ('jane', 'doe', 'F', 1991), 10 ('joe', 'blow', 'M', 1996), 11 ('mary', 'public', 'F', 2000), 12 ('john', 'smith', 'M', 1980), 13 ('first_name', 'last_name', 'F', 'a string'), 14 ) 15 16 @staticmethod 17 def calculate_age(year_of_birth):I use the class attribute for
peoplein the for loop in test_factory_function23 def test_factory_function(self): 24 # people = ( 25 # ('jane', 'doe', 'F', 1991), 26 # ('joe', 'blow', 'M', 1996), 27 # ('mary', 'public', 'F', 2000), 28 # ('john', 'smith', 'M', 1980), 29 # ('first_name', 'last_name', 'F', '2026'), 30 # ) 31 # for person in people: 32 for person in self.people: 33 first_name = person[0]the test is still green.
I remove the commented lines from test_factory_function
23 def test_factory_function(self): 24 for person in self.people: 25 first_name = person[0] 26 last_name = person[1] 27 sex = person[2] 28 year_of_birth = person[3] 29 30 reality = src.person.factory( 31 first_name=first_name, 32 last_name=last_name, 33 sex=sex, 34 year_of_birth=year_of_birth, 35 ) 36 my_expectation = ( 37 f'{first_name}, {last_name},' 38 f' {sex}, {year_of_birth}' 39 ) 40 self.assertEqual(reality, my_expectation) 41 42 def test_say_hello_function(self):I use the class attribute for
peoplein the for loop in test_say_hello_function42 def test_say_hello_function(self): 43 # people = ( 44 # ('jane', 'doe', 'F', 1991), 45 # ('joe', 'blow', 'M', 1996), 46 # ('mary', 'public', 'F', 2000), 47 # ('john', 'smith', 'M', 1980), 48 # ('first_name', 'last_name', 'F', 'a string'), 49 # ) 50 # for person in people: 51 for person in self.people: 52 first_name = person[0]still green.
I remove the commented lines from test_say_hello_function
42 def test_say_hello_function(self): 43 for person in self.people: 44 first_name = person[0] 45 last_name = person[1] 46 year_of_birth = person[3] 47 48 with self.subTest( 49 first_name=first_name, 50 year_of_birth=year_of_birth, 51 ): 52 try: 53 reality = src.person.say_hello( 54 first_name=first_name, 55 last_name=last_name, 56 year_of_birth=year_of_birth, 57 ) 58 except TypeError as error: 59 self.assertEqual( 60 error.args[0], 61 f"'{year_of_birth}' is not an integer" 62 ) 63 else: 64 my_expectation = ( 65 f'Hello, my name is {first_name}' 66 f' {last_name} and I am' 67 f' {self.calculate_age(year_of_birth)}.' 68 ) 69 self.assertEqual(reality, my_expectation) 70 71 def test_say_hello_method(self):I use the class attribute for
peoplein the for loop in test_say_hello_method71 def test_say_hello_method(self): 72 # people = ( 73 # ('jane', 'doe', 'F', 1991), 74 # ('joe', 'blow', 'M', 1996), 75 # ('mary', 'public', 'F', 2000), 76 # ('john', 'smith', 'M', 1980), 77 # ('first_name', 'last_name', 'F', 'a string'), 78 # ) 79 # for person in people: 80 for person in self.people: 81 first_name = person[0]green.
I remove the commented lines from test_say_hello_method
71 def test_say_hello_method(self): 72 for person in self.people: 73 first_name = person[0] 74 last_name = person[1] 75 sex = person[2] 76 year_of_birth = person[3] 77 78 with self.subTest( 79 first_name=first_name, 80 year_of_birth=year_of_birth, 81 ): 82 try: 83 a_person = src.person.Person( 84 first_name=first_name, 85 last_name=last_name, 86 sex=sex, 87 year_of_birth=year_of_birth, 88 ) 89 except TypeError as error: 90 self.assertEqual( 91 error.args[0], 92 f"'{year_of_birth}' is not an integer" 93 ) 94 else: 95 self.assertEqual( 96 a_person.say_hello(), 97 ( 98 f'Hello, my name is {first_name}' 99 f' {last_name} and I am' 100 f' {self.calculate_age(year_of_birth)}.' 101 ) 102 ) 103 104 def test_joe(self):I add a git commit message
git commit -am 'extract people class attribute'
extract test_can_person_vote
The tests in test_joe, test_jane, test_joe, test_mary and test_underage_citizen make an instance of the Person class, then call its can_vote method comparing the results in assertions.
The can_vote method of the Person class sends two parameters (age and response) when it calls the check_age method to return False or True for if a person cannot vote or can vote.
The inputs are
is the person younger than 18?
is the person a citizen?
The truth table for the can_vote is
age < 18 |
is citizen |
can vote |
|---|---|---|
True |
True |
False |
True |
False |
False |
False |
True |
True |
False |
False |
False |
RED: make it fail
I add a test with an assertion for if a person is younger than 18 AND is a citizen
age < 18 |
is citizen |
can vote |
|---|---|---|
True |
True |
False |
94 else:
95 self.assertEqual(
96 a_person.say_hello(),
97 (
98 f'Hello, my name is {first_name}'
99 f' {last_name} and I am'
100 f' {self.calculate_age(year_of_birth)}.'
101 )
102 )
103
104 def test_can_person_vote(self):
105 truth_table = (
106 (datetime.date.today().year-17, True),
107 )
108 for row in truth_table:
109 with self.subTest(row=row):
110 a_person = src.person.Person(
111 first_name='first_name',
112 last_name='last_name',
113 sex='F',
114 year_of_birth=row[0],
115 is_citizen=row[1],
116 )
117 self.assertEqual(a_person.can_vote(), True)
118
119 def test_joe(self):
the terminal is my friend, and shows AssertionError
SUBFAILED(row=(2009, True)) ...test_can_person_vote -
AssertionError: False != True
GREEN: make it pass
I change True to False in the assertion for if a person is younger than 18 AND is a citizen
104 def test_can_person_vote(self):
105 truth_table = (
106 (datetime.date.today().year-17, True),
107 )
108 for row in truth_table:
109 with self.subTest(row=row):
110 a_person = src.person.Person(
111 first_name='first_name',
112 last_name='last_name',
113 sex='F',
114 year_of_birth=row[0],
115 is_citizen=row[1],
116 )
117 self.assertEqual(a_person.can_vote(), False)
118
119 def test_joe(self):
the test passes.
REFACTOR: make it better
I add a tuple to the
truth_tabletuple for if a person is younger than18AND is NOT a citizenage < 18
is citizen
can vote
True
False
False
104 def test_can_person_vote(self): 105 truth_table = ( 106 (datetime.date.today().year-17, True), 107 (datetime.date.today().year-17, False), 108 ) 109 for row in truth_table:the test is still green.
I add a tuple to the
truth_tabletuple for if a person is NOT younger than18AND is a citizenage < 18
is citizen
can vote
False
True
True
104 def test_can_person_vote(self): 105 truth_table = ( 106 (datetime.date.today().year-17, True), 107 (datetime.date.today().year-17, False), 108 (datetime.date.today().year-18, True), 109 ) 110 for row in truth_table:the terminal is my friend, and shows AssertionError
SUBFAILED(row=(2008, True)) ...test_can_person_vote - AssertionError: True != FalseI add the expectation to the tuples for the rows in the
truth_tabletuple104 def test_can_person_vote(self): 105 truth_table = ( 106 (datetime.date.today().year-17, True, False), 107 (datetime.date.today().year-17, False, False), 108 (datetime.date.today().year-18, True, True), 109 ) 110 for row in truth_table:I use the index of the new values as the expectation of the assertion
110 for row in truth_table: 111 with self.subTest(row=row): 112 a_person = src.person.Person( 113 first_name='first_name', 114 last_name='last_name', 115 sex='F', 116 year_of_birth=row[0], 117 is_citizen=row[1], 118 ) 119 self.assertEqual( 120 a_person.can_vote(), row[3] 121 ) 122 123def test_joe(self):the terminal is my friend, and shows IndexError
SUBFAILED(row=(2009, True, False)) ...test_can_person_vote - IndexError: tuple index out of range SUBFAILED(row=(2009, False, False)) ...test_can_person_vote - IndexError: tuple index out of range SUBFAILED(row=(2008, True, True)) ...test_can_person_vote - IndexError: tuple index out of rangeI change the index to the right number
119 self.assertEqual( 120 a_person.can_vote(), row[2] 121 )the test passes.
I add a tuple to the
truth_tabletuple for if a person is NOT younger than18AND is NOT a citizenage < 18
is citizen
can vote
False
False
False
104 def test_can_person_vote(self): 105 truth_table = ( 106 (datetime.date.today().year-17, True, False), 107 (datetime.date.today().year-17, False, False), 108 (datetime.date.today().year-18, True, True), 109 (datetime.date.today().year-18, False, False), 110 ) 111 for row in truth_table: 112 with self.subTest(row=row): 113 a_person = src.person.Person( 114 first_name='first_name', 115 last_name='last_name', 116 sex='F', 117 year_of_birth=row[0], 118 is_citizen=row[1], 119 ) 120 self.assertEqual( 121 a_person.can_vote(), row[2] 122 ) 123 124 def test_joe(self):still green.
I remove the assertion for the can_vote method from test_joe
124 def test_joe(self): 125 first_name = 'joe' 126 last_name = 'blow' 127 sex = 'M' 128 year_of_birth = 1996 129 130 joe = src.person.Person( 131 first_name=first_name, 132 last_name=last_name, 133 sex=sex, 134 year_of_birth=year_of_birth, 135 ) 136 self.assertEqual(joe.can_get_license(), False) 137 138 def test_jane(self):I remove the assertion for the can_vote method from test_jane
138 def test_jane(self): 139 first_name = 'jane' 140 last_name = 'doe' 141 sex = 'F' 142 year_of_birth = 1991 143 144 jane = src.person.Person( 145 first_name=first_name, 146 last_name=last_name, 147 sex=sex, 148 year_of_birth=year_of_birth, 149 passed_test=True, 150 ) 151 self.assertEqual(jane.can_get_license(), True) 152 153 def test_john(self):I remove the assertion for the can_vote method from test_john
153 def test_john(self): 154 first_name = 'john' 155 last_name = 'smith' 156 sex = 'M' 157 year_of_birth = 1980 158 159 john = src.person.Person( 160 first_name=first_name, 161 last_name=last_name, 162 sex=sex, 163 year_of_birth=year_of_birth, 164 is_citizen=False, 165 ) 166 self.assertEqual(john.can_get_license(), False) 167 168 def test_mary(self):I remove the assertion for the can_vote method from test_mary
168 def test_mary(self): 169 first_name = 'mary' 170 last_name = 'public' 171 sex = 'F' 172 year_of_birth = 2000 173 174 mary = src.person.Person( 175 first_name=first_name, 176 last_name=last_name, 177 sex=sex, 178 year_of_birth=year_of_birth, 179 is_citizen=False, 180 passed_test=True, 181 ) 182 self.assertEqual(mary.can_get_license(), True) 183 184 def test_underage_citizen(self):I remove the assertion for the can_vote method from test_underage_citizen
184 def test_underage_citizen(self): 185 person = src.person.Person( 186 first_name='first_name', 187 last_name='last_name', 188 sex='M', 189 year_of_birth=datetime.date.today().year-17, 190 is_citizen=True, 191 passed_test=True, 192 ) 193 self.assertEqual( 194 person.can_get_license(), False 195 ) 196 197 def test_when_person_is_too_old_to_be_alive(self):I add a git commit message
git commit -am 'extract test_can_person_vote'
extract test_can_person_get_license
The tests in test_joe, test_jane, test_joe, test_mary and test_underage_citizen make an instance of the Person class, then call its can_get_license method comparing the results in assertions.
The can_get_license method of the Person class also sends two parameters when it calls the check_age method to return False or True for if a person cannot get a license or can get a license.
The inputs are
is the person younger than 18?
did the person pass the test?
The truth table for the can_get_license is
age < 18 |
passed test |
can get license |
|---|---|---|
True |
True |
False |
True |
False |
False |
False |
True |
True |
False |
False |
False |
RED: make it fail
I add a test with an assertion for if a person is NOT younger than 18 AND has NOT passed the test.
age < 18 |
passed test |
can get license |
|---|---|---|
False |
False |
False |
120 self.assertEqual(
121 a_person.can_vote(), row[2]
122 )
123
124 def test_can_person_get_license(self):
125 truth_table = (
126 (datetime.date.today().year-18, False, False),
127 )
128 for row in truth_table:
129 with self.subTest(row=row):
130 a_person = src.person.Person(
131 first_name='first_name',
132 last_name='last_name',
133 sex='F',
134 year_of_birth=row[0],
135 passed_test=row[1],
136 )
137 self.assertEqual(
138 a_person.can_get_license(), True
139 )
140
141 def test_joe(self):
the terminal is my friend, and shows AssertionError
SUBFAILED(row=(2008, False, False)) ...test_can_person_get_license
- AssertionError: False != True
GREEN: make it pass
I change the expectation of the assertion
137 self.assertEqual(
138 a_person.can_get_license(), False
139 )
the test passes.
REFACTOR: make it better
I add a tuple to the
truth_tabletuple for if a person is NOT younger than18AND has passed the testage < 18
passed test
can get license
False
True
True
124 def test_can_person_get_license(self): 125 truth_table = ( 126 (datetime.date.today().year-18, True, True), 127 (datetime.date.today().year-18, False, False), 128 ) 129 for row in truth_table:the terminal is my friend, and shows AssertionError
SUBFAILED(row=(2008, True, True)) ... test_can_person_get_license - AssertionError: True != FalseI use the index to change the expectation to the last item of the
rowtuple129 for row in truth_table: 130 with self.subTest(row=row): 131 a_person = src.person.Person( 132 first_name='first_name', 133 last_name='last_name', 134 sex='F', 135 year_of_birth=row[0], 136 is_citizen=row[1], 137 ) 138 self.assertEqual( 139 a_person.can_get_license(), row[-1] 140 ) 141 142 def test_joe(self):the test passes.
I add a tuple to the
truth_tabletuple for if a person is younger than18AND has NOT passed the testage < 18
passed test
can get license
True
False
False
124 def test_can_person_get_license(self): 125 truth_table = ( 126 (datetime.date.today().year-17, False, False), 127 (datetime.date.today().year-18, True, True), 128 (datetime.date.today().year-18, False, False), 129 ) 130 for row in truth_table:the test is still green.
I add a tuple to the
truth_tabletuple for if a person is younger than18AND has passed the testage < 18
passed test
can get license
True
True
False
124 def test_can_person_get_license(self): 125 truth_table = ( 126 (datetime.date.today().year-17, True, False), 127 (datetime.date.today().year-17, False, False), 128 (datetime.date.today().year-18, True, True), 129 (datetime.date.today().year-18, False, False), 130 ) 131 for row in truth_table:still green.
I remove test_joe, test_jane, test_john, test_mary and test_underage_citizen since they are now repetitions of test_can_person_get_license
124 def test_can_person_get_license(self): 125 truth_table = ( 126 (datetime.date.today().year-17, True, False), 127 (datetime.date.today().year-17, False, False), 128 (datetime.date.today().year-18, True, True), 129 (datetime.date.today().year-18, False, False), 130 ) 131 for row in truth_table: 132 with self.subTest(row=row): 133 a_person = src.person.Person( 134 first_name='first_name', 135 last_name='last_name', 136 sex='F', 137 year_of_birth=row[0], 138 is_citizen=row[1], 139 ) 140 self.assertEqual( 141 a_person.can_get_license(), row[-1] 142 ) 143 144 def test_when_person_is_too_old_to_be_alive(self):I add a git commit message
git commit -am \ 'extract test_can_person_get_license'
extract truth_table class attribute
The truth_table tuple is used in both test_can_person_vote and test_can_person_get_license
.
I add a class attribute for the
truth_tabletuple to the TestPerson class6class TestPerson(unittest.TestCase): 7 8 people = ( 9 ('jane', 'doe', 'F', 1991), 10 ('joe', 'blow', 'M', 1996), 11 ('mary', 'public', 'F', 2000), 12 ('john', 'smith', 'M', 1980), 13 ('first_name', 'last_name', 'F', 'a string'), 14 ) 15 truth_table = ( 16 (datetime.date.today().year-17, True, False), 17 (datetime.date.today().year-17, False, False), 18 (datetime.date.today().year-18, True, True), 19 (datetime.date.today().year-18, False, False), 20 ) 21 22 @staticmethod 23 def calculate_age(year_of_birth):I use the class attribute for
truth_tablein the for loop in test_can_person_votethe test is still green.
I remove the commented lines from test_can_person_vote
110I use the class attribute for
truth_tablein the for loop in test_can_person_votestill green.
I remove the commented lines from test_can_person_vote
110
use assertRaises with test_when_person_is_too_old_to_be_alive
test_when_year_of_birth_is_not_an_integer
I change the Exception raised in the calculate_age function if the value of the
year_of_birthparameter is not an integer
close the project
I close
test_person.pyandsrc/person/__init__.pyI use q on the keyboard to leave the tests. The terminal goes back to the command line.
I change directory to the parent of
personcd ..
review
I can use the try statement to make sure a program can make a decision when it runs into an Exception.
I can use the try statement in a test to confirm that a program raises an Exception when certain conditions are met.
I can use the raise statement to make an Exception happen to stop a program from running past a certain point.
My tests still have problems:
The attribute tests - test_dir_person_class and test_dir_person_instance catch changes to the attributes and methods of the Person class and they are a problem to maintain. There has to be a better way.
test_joe, test_jane, test_john and test_mary all still have the same three tests. There has to be a better way.
test_when_year_of_birth_is_not_an_integer has four tests that are basically the same, the only thing that changes are the values for the
year_of_birthparameter. There has to be a better way.
code from the chapter
what is next?
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.