test person with datetime
The person project has a problem with the calculation of the ages. It only shows the right age if the program is run in 2026, because the year is hardcoded. If I run it in a different year or change the year on my computer, the ages will be wrong and the tests for say_hello will fail.
I want the calculation to always be right, which means the program should always know the correct year.
I can use the datetime module from The Python Standard Library. You can think of it as a toolbox with different tools I can use to do things with dates and times. I can also use assertions to make sure I get the right year of birth for the calculations.
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 @staticmethod
9 def calculate_age(year_of_birth):
10 return (
11 datetime.date.today().year
12 - year_of_birth
13 )
14
15 def test_joe(self):
16 first_name = 'joe'
17 last_name = 'blow'
18 sex = 'M'
19 year_of_birth = 1996
20
21 reality = src.person.factory(
22 first_name=first_name,
23 last_name=last_name,
24 sex=sex,
25 year_of_birth=year_of_birth,
26 )
27 my_expectation = (
28 f'{first_name}, {last_name},'
29 f' {sex}, {year_of_birth}'
30 )
31 assert reality == my_expectation
32 self.assertEqual(reality, my_expectation)
33
34 reality = src.person.say_hello(
35 first_name=first_name,
36 last_name=last_name,
37 year_of_birth=year_of_birth,
38 )
39 my_expectation = (
40 f'Hello, my name is {first_name}'
41 f' {last_name} and I am'
42 f' {self.calculate_age(year_of_birth)}.'
43 )
44 assert reality == my_expectation
45 self.assertEqual(reality, my_expectation)
46
47 joe = src.person.Person(
48 first_name=first_name,
49 last_name=last_name,
50 sex=sex,
51 year_of_birth=year_of_birth,
52 )
53
54 reality = joe.say_hello()
55 assert reality == my_expectation
56 self.assertEqual(reality, my_expectation)
57
58 def test_jane(self):
59 first_name = 'jane'
60 last_name = 'doe'
61 sex = 'F'
62 year_of_birth = 1991
63
64 reality = src.person.factory(
65 first_name=first_name,
66 last_name=last_name,
67 sex=sex,
68 year_of_birth=year_of_birth,
69 )
70 my_expectation = (
71 f'{first_name}, {last_name},'
72 f' {sex}, {year_of_birth}'
73 )
74 assert reality == my_expectation
75 self.assertEqual(reality, my_expectation)
76
77 reality = src.person.say_hello(
78 first_name=first_name,
79 last_name=last_name,
80 year_of_birth=year_of_birth,
81 )
82 my_expectation = (
83 f'Hello, my name is {first_name}'
84 f' {last_name} and I am'
85 f' {self.calculate_age(year_of_birth)}.'
86 )
87 assert reality == my_expectation
88 self.assertEqual(reality, my_expectation)
89
90 jane = src.person.Person(
91 first_name=first_name,
92 last_name=last_name,
93 sex=sex,
94 year_of_birth=year_of_birth,
95 )
96
97 reality = jane.say_hello()
98 assert reality == my_expectation
99 self.assertEqual(reality, my_expectation)
100
101 def test_john(self):
102 first_name = 'john'
103 last_name = 'smith'
104 sex = 'M'
105 year_of_birth = 1980
106 # year_of_birth = 1580
107 # raises AssertionError
108 # because older than 120
109
110 reality = src.person.factory(
111 first_name=first_name,
112 last_name=last_name,
113 sex=sex,
114 year_of_birth=year_of_birth,
115 )
116 my_expectation = (
117 f'{first_name}, {last_name},'
118 f' {sex}, {year_of_birth}'
119 )
120 assert reality == my_expectation
121 self.assertEqual(reality, my_expectation)
122
123 reality = src.person.say_hello(
124 first_name=first_name,
125 last_name=last_name,
126 year_of_birth=year_of_birth,
127 )
128 my_expectation = (
129 f'Hello, my name is {first_name}'
130 f' {last_name} and I am'
131 f' {self.calculate_age(year_of_birth)}.'
132 )
133 assert reality == my_expectation
134 self.assertEqual(reality, my_expectation)
135
136 john = src.person.Person(
137 first_name=first_name,
138 last_name=last_name,
139 sex=sex,
140 year_of_birth=year_of_birth,
141 )
142
143 reality = john.say_hello()
144 assert reality == my_expectation
145 self.assertEqual(reality, my_expectation)
146
147 def test_mary(self):
148 first_name = 'mary'
149 last_name = 'public'
150 sex = 'F'
151 year_of_birth = 2000
152
153 reality = src.person.factory(
154 first_name=first_name,
155 last_name=last_name,
156 sex=sex,
157 year_of_birth=year_of_birth,
158 )
159 my_expectation = (
160 f'{first_name}, {last_name},'
161 f' {sex}, {year_of_birth}'
162 )
163 assert reality == my_expectation
164 self.assertEqual(reality, my_expectation)
165
166 reality = src.person.say_hello(
167 first_name=first_name,
168 last_name=last_name,
169 year_of_birth=year_of_birth,
170 )
171 my_expectation = (
172 f'Hello, my name is {first_name}'
173 f' {last_name} and I am'
174 f' {self.calculate_age(year_of_birth)}.'
175 )
176 assert reality == my_expectation
177 self.assertEqual(reality, my_expectation)
178
179 mary = src.person.Person(
180 first_name=first_name,
181 last_name=last_name,
182 sex=sex,
183 year_of_birth=year_of_birth,
184 )
185
186 reality = mary.say_hello()
187 assert reality == my_expectation
188 self.assertEqual(reality, my_expectation)
189
190 def test_when_year_of_birth_is_not_an_integer(self):
191 src.person.Person(
192 first_name='first_name',
193 last_name='last_name',
194 sex='M',
195 # year_of_birth=None, # fails
196 # year_of_birth=2026.0, # fails
197 # year_of_birth='2026', # fails
198 # year_of_birth=(2026,), # fails
199 )
200
201 def test_dir_person_class(self):
202 reality = dir(src.person.Person)
203 my_expectation = [
204 '__class__',
205 '__delattr__',
206 '__dict__',
207 '__dir__',
208 '__doc__',
209 '__eq__',
210 '__firstlineno__',
211 '__format__',
212 '__ge__',
213 '__getattribute__',
214 '__getstate__',
215 '__gt__',
216 '__hash__',
217 '__init__',
218 '__init_subclass__',
219 '__le__',
220 '__lt__',
221 '__module__',
222 '__ne__',
223 '__new__',
224 '__reduce__',
225 '__reduce_ex__',
226 '__repr__',
227 '__setattr__',
228 '__sizeof__',
229 '__static_attributes__',
230 '__str__',
231 '__subclasshook__',
232 '__weakref__',
233 'say_hello'
234 ]
235 assert reality == my_expectation
236 self.assertEqual(reality, my_expectation)
237
238 def test_dir_person_instance(self):
239 an_instance_of_person = src.person.Person(
240 first_name='first_name',
241 last_name='last_name',
242 sex='M',
243 year_of_birth=2026,
244 )
245
246 reality = dir(an_instance_of_person)
247 my_expectation = [
248 '__class__',
249 '__delattr__',
250 '__dict__',
251 '__dir__',
252 '__doc__',
253 '__eq__',
254 '__firstlineno__',
255 '__format__',
256 '__ge__',
257 '__getattribute__',
258 '__getstate__',
259 '__gt__',
260 '__hash__',
261 '__init__',
262 '__init_subclass__',
263 '__le__',
264 '__lt__',
265 '__module__',
266 '__ne__',
267 '__new__',
268 '__reduce__',
269 '__reduce_ex__',
270 '__repr__',
271 '__setattr__',
272 '__sizeof__',
273 '__static_attributes__',
274 '__str__',
275 '__subclasshook__',
276 '__weakref__',
277 'first_name',
278 'last_name',
279 'say_hello',
280 'sex',
281 'year_of_birth',
282 ]
283 assert reality == my_expectation
284 self.assertEqual(reality, my_expectation)
285
286
287# Exceptions seen
288# AssertionError
289# NameError
290# TypeError
291# AttributeError
292# SyntaxError
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%] =================== 6 passed in M.NOs ====================
test_dir_datetime
I want to see what comes with the datetime module.
RED: make it fail
I add test_dir_datetime to
test_person.py262 self.assertEqual(reality, my_expectation) 263 264 def test_dir_datetime(self): 265 reality = dir(datetime) 266 my_expectation = [] 267 self.assertEqual(reality, my_expectation) 268 269 270# Exceptions seenthe terminal is my friend, and shows NameError
NameError: name 'datetime' is not defined. Did you forget to import 'datetime'?
GREEN: make it pass
I add an import statement for datetime
1import datetime 2import src.person 3import unittest 4 5 6class TestPerson(unittest.TestCase):the terminal is my friend, and shows AssertionError
AssertionError: Lists differ: [ 'MAXYEAR', 'MINYEAR', 'UTC', '__all__', '[179 chars]nfo' ] != []it also shows the entire difference between the lists
I copy (ctrl/command+c) the values from the terminal, paste (ctrl/command+v) them as
my_expectationand remove the extra characters263 self.assertEqual(reality, my_expectation) 264 265 def test_dir_datetime(self): 266 reality = dir(datetime) 267 my_expectation = [ 268 'MAXYEAR', 269 'MINYEAR', 270 'UTC', 271 '__all__', 272 '__builtins__', 273 '__cached__', 274 '__doc__', 275 '__file__', 276 '__loader__', 277 '__name__', 278 '__package__', 279 '__spec__', 280 'date', 281 'datetime', 282 'datetime_CAPI', 283 'time', 284 'timedelta', 285 'timezone', 286 'tzinfo' 287 ] 288 self.assertEqual(reality, my_expectation) 289 290 291# Exceptions seenthe test passes because when
import datetimeruns, Python brings in an object (everything in Python is an object) for the datetime module from The Python Standard Library so I can use it intest_person.pyasdatetime.This means that there is a file or folder on the computer named
datetimethat got added when I installed Python.Caution
Your list of attributes and methods may be different depending on your Python version
test_dir_datetime_date
A few names stand out in the list of attributes and methods of datetime
date- I assume this handles datestime- I assume this handles timedatetime- I assume a combination of date and time
What I want is something that will give me the current year.
RED: make it fail
I add test_dir_datetime_date to test_person.py
288 self.assertEqual(reality, my_expectation)
289
290 def test_dir_datetime_date(self):
291 reality = dir(datetime.date)
292 my_expectation = []
293 self.assertEqual(reality, my_expectation)
294
295
296# Exceptions seen
the terminal is my friend, and shows AssertionError
AssertionError:
Lists differ: [
'__add__', '__class__', '__delattr__',
'_[585 chars]ear'
] != []
with a message about how to see the entire difference
Diff is 787 characters long.
Set self.maxDiff to None to see it.
GREEN: make it pass
I set self.maxDiff to None
288 self.assertEqual(reality, my_expectation) 289 290 def test_dir_datetime_date(self): 291 reality = dir(datetime.date) 292 my_expectation = [] 293 self.maxDiff = None 294 self.assertEqual(reality, my_expectation) 295 296 297# Exceptions seenI copy (ctrl/command+c) the values from the terminal, paste (ctrl/command+v) them as
my_expectationand remove the extra characters290 def test_dir_datetime_date(self): 291 reality = dir(datetime.date) 292 my_expectation = [ 293 '__add__', '__class__', '__delattr__', 294 '__dir__', '__doc__', '__eq__', 295 '__format__', '__ge__', '__getattribute__', 296 '__getstate__', '__gt__', '__hash__', 297 '__init__', '__init_subclass__', '__le__', 298 '__lt__', '__ne__', '__new__', '__radd__', 299 '__reduce__', '__reduce_ex__', 300 '__replace__', '__repr__', '__rsub__', 301 '__setattr__', '__sizeof__', '__str__', 302 '__sub__', '__subclasshook__', 'ctime', 303 'day', 'fromisocalendar', 'fromisoformat', 304 'fromordinal', 'fromtimestamp', 305 'isocalendar', 'isoformat', 'isoweekday', 306 'max', 'min', 'month', 'replace', 307 'resolution', 'strftime', 'strptime', 308 'timetuple', 'today', 'toordinal', 309 'weekday', 'year' 310 ] 311 self.maxDiff = None 312 self.assertEqual(reality, my_expectation) 313 314 315# Exceptions seenthe test passes.
test_dir_datetime_date_year
I see year in the list of attributes and methods of datetime.date.
RED: make it fail
I add a test for the year attribute of the date attribute of the datetime module in test_person.py
312 self.assertEqual(reality, my_expectation)
313
314 def test_dir_datetime_date_year(self):
315 reality = dir(datetime.date.year)
316 my_expectation = []
317 self.assertEqual(reality, my_expectation)
318
319
320# Exceptions seen
the terminal is my friend, and shows AssertionError with only attributes that start and end with double underscore (__)
GREEN: make it pass
I change the value of
realitytodatetime.date.year314 def test_dir_datetime_date_year(self): 315 # reality = dir(datetime.date.year) 316 reality = datetime.date.year 317 my_expectation = [] 318 self.assertEqual(reality, my_expectation) 319 320 321# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: <attribute 'year' of 'datetime.date' objects> != []I try calling
date314 def test_dir_datetime_date_year(self): 315 # reality = dir(datetime.date.year) 316 # reality = datetime.date.year 317 reality = datetime.date().year 318 my_expectation = [] 319 self.assertEqual(reality, my_expectation) 320 321 322# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: function missing required argument 'year' (pos 1)I want something that automatically knows the date and gives me the year.
test_dir_datetime_date_today
I also saw today in the list of attributes and methods of datetime.date.
RED: make it fail
I change test_dir_datetime_date_year to a test for the today attribute of the date attribute of the datetime module in test_person.py
312 self.assertEqual(reality, my_expectation)
313
314 def test_dir_datetime_date_today(self):
315 # reality = dir(datetime.date.year)
316 # reality = datetime.date.year
317 # reality = datetime.date().year
318 reality = datetime.date.today
319 my_expectation = []
320 self.assertEqual(reality, my_expectation)
321
322
323 # Exceptions seen
the terminal is my friend, and shows AssertionError
AssertionError:
<built-in method today
of type object at 0xffff0fab2345>
!= []
GREEN: make it pass
I change the value of
realityto a call todatetime.date.today314 def test_dir_datetime_date_today(self): 315 # reality = dir(datetime.date.year) 316 # reality = datetime.date.year 317 # reality = datetime.date().year 318 # reality = datetime.date.today 319 reality = datetime.date.today() 320 my_expectation = [] 321 self.assertEqual(reality, my_expectation) 322 323 324# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: datetime.date(YYYY, MM, DD) != []where
YYYYis the current year,MMis the current month andDDis the current date. Progress!When I called
datetime.date()it asked for theyearargument, and the result of the call isdatetime.date(YYYY, MM, DD)which looks like a call todatetime.date(). I wonder if it also has ayearattribute314 def test_dir_datetime_date_today(self): 315 # reality = dir(datetime.date.year) 316 # reality = datetime.date.year 317 # reality = datetime.date().year 318 # reality = datetime.date.today 319 # reality = datetime.date.today() 320 reality = dir(datetime.date.today()) 321 my_expectation = [] 322 self.assertEqual(reality, my_expectation) 323 324 325# Exceptions seenthe terminal is my friend, and shows AssertionError with a message about setting self.maxDiff to see the full difference
I set self.maxDiff to None
314 def test_dir_datetime_date_today(self): 315 # reality = dir(datetime.date.year) 316 # reality = datetime.date.year 317 # reality = datetime.date().year 318 # reality = datetime.date.today 319 # reality = datetime.date.today() 320 reality = dir(datetime.date.today()) 321 my_expectation = [] 322 self.maxDiff = None 323 self.assertEqual(reality, my_expectation) 324 325 326# Exceptions seenthe terminal shows the entire difference between
realityandmy_expectationand there is ayearattribute because they are the same as the attributes and methods of datetime.dateI change
my_expectation314 def test_dir_datetime_date_today(self): 315 # reality = dir(datetime.date.year) 316 # reality = datetime.date.year 317 # reality = datetime.date().year 318 # reality = datetime.date.today 319 # reality = datetime.date.today() 320 reality = dir(datetime.date.today()) 321 # my_expectation = [] 322 my_expectation = dir(datetime.date) 323 self.maxDiff = None 324 self.assertEqual(reality, my_expectation) 325 326 327# Exceptions seenthe test passes.
test_datetime_date_today_year
It looks like I have a way to get the current year.
RED: make it fail
I add test_datetime_date_today_year to test the year attribute of the result of a call to the today method of the date class of the datetime module (datetime.date.today().year) in test_person.py
324 self.assertEqual(reality, my_expectation)
325
326 def test_datetime_date_today_year(self):
327 reality = datetime.date.today().year
328 my_expectation = 1900
329 self.assertEqual(reality, my_expectation)
330
331
332 # Exceptions seen
the terminal is my friend, and shows AssertionError
AssertionError: YYYY != 1900
where YYYY is the current year.
GREEN: make it pass
I change
my_expectationto matchrealityand the test passes.I remove all the datetime tests now that I know datetime.date.today().year works
256 'first_name', 257 'last_name', 258 'say_hello', 259 'sex', 260 'year_of_birth', 261 ] 262 assert reality == my_expectation 263 self.assertEqual(reality, my_expectation) 264 265 266# Exceptions seen
I have a way to automatically get the current year that will always be correct.
test age with current year
I change the age calculation in
my_expectationof say_hello in test_joe with datetime.date.today().year27 reality = src.person.say_hello( 28 first_name=first_name, 29 last_name=last_name, 30 year_of_birth=year_of_birth, 31 ) 32 my_expectation = ( 33 f'Hello, my name is {first_name}' 34 f' {last_name} and I am' 35 # f' {2026-year_of_birth}.' 36 f' {datetime.date.today().year-year_of_birth}.' 37 ) 38 assert reality == my_expectation 39 self.assertEqual(reality, my_expectation) 40 41 joe = src.person.Person(the test is still green.
I change the age calculation in
my_expectationof say_hello in test_jane with datetime.date.today().year71 reality = src.person.say_hello( 72 first_name=first_name, 73 last_name=last_name, 74 year_of_birth=year_of_birth, 75 ) 76 my_expectation = ( 77 f'Hello, my name is {first_name}' 78 f' {last_name} and I am' 79 # f' {2026-year_of_birth}.' 80 f' {datetime.date.today().year-year_of_birth}.' 81 ) 82 assert reality == my_expectation 83 self.assertEqual(reality, my_expectation) 84 85 jane = src.person.Person(still green.
I change the age calculation in
my_expectationof say_hello in test_john with datetime.date.today().year115 reality = src.person.say_hello( 116 first_name=first_name, 117 last_name=last_name, 118 year_of_birth=year_of_birth, 119 ) 120 my_expectation = ( 121 f'Hello, my name is {first_name}' 122 f' {last_name} and I am' 123 # f' {2026-year_of_birth}.' 124 f' {datetime.date.today().year-year_of_birth}.' 125 ) 126 assert reality == my_expectation 127 self.assertEqual(reality, my_expectation) 128 129 john = src.person.Person(green.
I change the age calculation in
my_expectationof say_hello in test_mary with datetime.date.today().year159 reality = src.person.say_hello( 160 first_name=first_name, 161 last_name=last_name, 162 year_of_birth=year_of_birth, 163 ) 164 my_expectation = ( 165 f'Hello, my name is {first_name}' 166 f' {last_name} and I am' 167 # f' {2026-year_of_birth}.' 168 f' {datetime.date.today().year-year_of_birth}.' 169 ) 170 assert reality == my_expectation 171 self.assertEqual(reality, my_expectation) 172 173 mary = src.person.Person(still green.
I open a new terminal then make sure I am in the
personfoldercd personI add a git commit message in the new terminal
git commit -am \ 'use datetime to calculate age'
extract this_year attribute
Each test calls datetime.date.today() to get the year attribute.
I go back to the terminal where the tests are running
I add a class attribute to TestPerson for the current year
6class TestPerson(unittest.TestCase): 7 8 this_year = datetime.date.today().year 9 10 def test_joe(self):I use the attribute for
datetime.date.today().yearin test_joe29 reality = src.person.say_hello( 30 first_name=first_name, 31 last_name=last_name, 32 year_of_birth=year_of_birth, 33 ) 34 my_expectation = ( 35 f'Hello, my name is {first_name}' 36 f' {last_name} and I am' 37 # f' {2026-year_of_birth}.' 38 # f' {datetime.date.today().year-year_of_birth}.' 39 f' {self.this_year-year_of_birth}.' 40 ) 41 assert reality == my_expectation 42 self.assertEqual(reality, my_expectation) 43 44 joe = src.person.Person(still green.
I use the attribute for
datetime.date.today().yearin test_jane74 reality = src.person.say_hello( 75 first_name=first_name, 76 last_name=last_name, 77 year_of_birth=year_of_birth, 78 ) 79 my_expectation = ( 80 f'Hello, my name is {first_name}' 81 f' {last_name} and I am' 82 # f' {2026-year_of_birth}.' 83 # f' {datetime.date.today().year-year_of_birth}.' 84 f' {self.this_year-year_of_birth}.' 85 ) 86 assert reality == my_expectation 87 self.assertEqual(reality, my_expectation) 88 89 jane = src.person.Person(green.
I use the attribute for
datetime.date.today().yearin test_john119 reality = src.person.say_hello( 120 first_name=first_name, 121 last_name=last_name, 122 year_of_birth=year_of_birth, 123 ) 124 my_expectation = ( 125 f'Hello, my name is {first_name}' 126 f' {last_name} and I am' 127 # f' {2026-year_of_birth}.' 128 # f' {datetime.date.today().year-year_of_birth}.' 129 f' {self.this_year-year_of_birth}.' 130 ) 131 assert reality == my_expectation 132 self.assertEqual(reality, my_expectation) 133 134 john = src.person.Person(still green.
I use the attribute for
datetime.date.today().yearin test_mary164 reality = src.person.say_hello( 165 first_name=first_name, 166 last_name=last_name, 167 year_of_birth=year_of_birth, 168 ) 169 my_expectation = ( 170 f'Hello, my name is {first_name}' 171 f' {last_name} and I am' 172 # f' {2026-year_of_birth}.' 173 # f' {datetime.date.today().year-year_of_birth}.' 174 f' {self.this_year-year_of_birth}.' 175 ) 176 assert reality == my_expectation 177 self.assertEqual(reality, my_expectation) 178 179 mary = src.person.Person(the test is still green.
I add a git commit message in the other terminal
git commit -am \ 'extract this_year attribute'
extract calculate_age method
Each test does a calculation for the age. I can make a method to remove the repetition.
RED: make it fail
I go back to the terminal where the tests are running
I add a method to TestPerson to calculate the age
6class TestPerson(unittest.TestCase): 7 8 this_year = datetime.date.today().year 9 10 def calculate_age(year_of_birth): 11 return self.this_year - year_of_birth 12 13 def test_joe(self):I use the method for
self.this_year-year_of_birthin test_joe32 reality = src.person.say_hello( 33 first_name=first_name, 34 last_name=last_name, 35 year_of_birth=year_of_birth, 36 ) 37 my_expectation = ( 38 f'Hello, my name is {first_name}' 39 f' {last_name} and I am' 40 # f' {2026-year_of_birth}.' 41 # f' {datetime.date.today().year-year_of_birth}.' 42 # f' {self.this_year-year_of_birth}.' 43 f' {self.calculate_age(year_of_birth)}.' 44 ) 45 assert reality == my_expectation 46 self.assertEqual(reality, my_expectation) 47 48 joe = src.person.Person(the terminal is my friend, and shows TypeError
TypeError: TestPerson.calculate_age() takes 1 positional argument but 2 were given
GREEN: make it pass
I add self to the parentheses of calculate_age
6 class TestPerson(unittest.TestCase):
7
8 this_year = datetime.date.today().year
9
10 # def calculate_age(year_of_birth):
11 def calculate_age(self, year_of_birth):
12 return self.this_year - year_of_birth
13
14 def test_joe(self):
the test passes.
REFACTOR: make it better
I remove the commented lines from test_joe
33 reality = src.person.say_hello( 34 first_name=first_name, 35 last_name=last_name, 36 year_of_birth=year_of_birth, 37 ) 38 my_expectation = ( 39 f'Hello, my name is {first_name}' 40 f' {last_name} and I am' 41 f' {self.calculate_age(year_of_birth)}.' 42 ) 43 assert reality == my_expectation 44 self.assertEqual(reality, my_expectation) 45 46 joe = src.person.Person(I use the method for
self.this_year-year_of_birthin test_jane76 reality = src.person.say_hello( 77 first_name=first_name, 78 last_name=last_name, 79 year_of_birth=year_of_birth, 80 ) 81 my_expectation = ( 82 f'Hello, my name is {first_name}' 83 f' {last_name} and I am' 84 # f' {2026-year_of_birth}.' 85 # f' {datetime.date.today().year-year_of_birth}.' 86 # f' {self.this_year-year_of_birth}.' 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(the test is still green.
I remove the commented lines from test_jane
76 reality = src.person.say_hello( 77 first_name=first_name, 78 last_name=last_name, 79 year_of_birth=year_of_birth, 80 ) 81 my_expectation = ( 82 f'Hello, my name is {first_name}' 83 f' {last_name} and I am' 84 f' {self.calculate_age(year_of_birth)}.' 85 ) 86 assert reality == my_expectation 87 self.assertEqual(reality, my_expectation) 88 89 jane = src.person.Person(I use the method for
self.this_year-year_of_birthin test_john119 reality = src.person.say_hello( 120 first_name=first_name, 121 last_name=last_name, 122 year_of_birth=year_of_birth, 123 ) 124 my_expectation = ( 125 f'Hello, my name is {first_name}' 126 f' {last_name} and I am' 127 # f' {2026-year_of_birth}.' 128 # f' {datetime.date.today().year-year_of_birth}.' 129 # f' {self.this_year-year_of_birth}.' 130 f' {self.calculate_age(year_of_birth)}.' 131 ) 132 assert reality == my_expectation 133 self.assertEqual(reality, my_expectation) 134 135 john = src.person.Person(still green.
I remove the commented lines from test_john
119 reality = src.person.say_hello( 120 first_name=first_name, 121 last_name=last_name, 122 year_of_birth=year_of_birth, 123 ) 124 my_expectation = ( 125 f'Hello, my name is {first_name}' 126 f' {last_name} and I am' 127 f' {self.calculate_age(year_of_birth)}.' 128 ) 129 assert reality == my_expectation 130 self.assertEqual(reality, my_expectation) 131 132 john = src.person.Person(I use the method for
self.this_year-year_of_birthin test_mary162 reality = src.person.say_hello( 163 first_name=first_name, 164 last_name=last_name, 165 year_of_birth=year_of_birth, 166 ) 167 my_expectation = ( 168 f'Hello, my name is {first_name}' 169 f' {last_name} and I am' 170 # f' {2026-year_of_birth}.' 171 # f' {datetime.date.today().year-year_of_birth}.' 172 # f' {self.this_year-year_of_birth}.' 173 f' {self.calculate_age(year_of_birth)}.' 174 ) 175 assert reality == my_expectation 176 self.assertEqual(reality, my_expectation) 177 178 mary = src.person.Person(green.
I remove the commented lines from test_mary
162 reality = src.person.say_hello( 163 first_name=first_name, 164 last_name=last_name, 165 year_of_birth=year_of_birth, 166 ) 167 my_expectation = ( 168 f'Hello, my name is {first_name}' 169 f' {last_name} and I am' 170 f' {self.calculate_age(year_of_birth)}.' 171 ) 172 assert reality == my_expectation 173 self.assertEqual(reality, my_expectation) 174 175 mary = src.person.Person(The this_year class attribute is now used in only one place the calculate_age method. I can call what it points to directly
6class TestPerson(unittest.TestCase): 7 8 this_year = datetime.date.today().year 9 10 # def calculate_age(year_of_birth): 11 def calculate_age(self, year_of_birth): 12 # return self.this_year - year_of_birth 13 return ( 14 datetime.date.today().year 15 - year_of_birth 16 ) 17 18 def test_joe(self):I add the staticmethod decorator since calculate_age no longer uses anything from the TestPerson class
10 # def calculate_age(year_of_birth): 11 @staticmethod 12 def calculate_age(self, year_of_birth):the terminal is my friend, and shows TypeError
TypeError: TestPerson.calculate_age() missing 1 required positional argument: 'year_of_birth'because a method of an instance takes the instance of the class (
self) it belongs to as the first argument.I remove
selffrom the parentheses10 # def calculate_age(year_of_birth): 11 @staticmethod 12 # def calculate_age(self, year_of_birth): 13 def calculate_age(year_of_birth):the test is green again.
I remove the commented lines and this_year attribute since it is no longer used
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_joe(self):this works because Python follows the following path when
self.calculate_age(year_of_birth)is calledself.calculate_age(year_of_birth) └── class TestPerson(unittest.TestCase) │ @staticmethod └── def calculate_age(year_of_birth): └── return ( datetime.date.today().year - year_of_birth )when
datetime.date.today()runs, I imagine Python follows this pathdatetime.date.today() datetime └── class date └── today() └── return self.date(YYYY, MM, DD)using substitution for the return statement
return ( datetime.date.today().year - year_of_birth ) return ( datetime.date(YYYY, MM, DD).year - year_of_birth ) return (YYYY - year_of_birth)where
YYYYis the current year.I add a git commit message in the other terminal
git commit -am \ 'extract calculate_age method'
add calculate_age function
The tests use the right calculation for the age, and the solution still uses a fixed value (2026)
RED: make it fail
I go back to the terminal where the tests are running.
I open
person/__init__.pyfrom thesrcfolderI add a function to calculate the age, with the same body as the calculate_age method, in
src/person/__init__.py12 def say_hello(self): 13 return ( 14 f'Hello, my name is {self.first_name}' 15 f' {self.last_name} and I am' 16 f' {2026-self.year_of_birth}.' 17 ) 18 19 20def calculate_age(year_of_birth): 21 return ( 22 datetime.date.today().year 23 - year_of_birth 24 ) 25 26 27def say_hello( 28 first_name, last_name, year_of_birth, 29):I use the function in the say_hello method of the Person class
12 def say_hello(self): 13 return ( 14 f'Hello, my name is {self.first_name}' 15 f' {self.last_name} and I am' 16 # f' {2026-self.year_of_birth}.' 17 f' {calculate_age(self.year_of_birth)}.' 18 ) 19 20 21def calculate_age(year_of_birth):the terminal is my friend, and shows NameError
NameError: name 'datetime' is not defined. Did you forget to import 'datetime'?I did.
GREEN: make it pass
I add an import statement at the top of
src/person/__init__.py1import datetime 2 3 4class Person:all tests are green again.
I remove the commented line
12 def say_hello(self): 13 return ( 14 f'Hello, my name is {self.first_name}' 15 f' {self.last_name} and I am' 16 f' {calculate_age(self.year_of_birth)}.' 17 ) 18 19 20def calculate_age(year_of_birth):I use the function in the say_hello function
30def say_hello( 31 first_name, last_name, year_of_birth, 32): 33 return ( 34 f'Hello, my name is {first_name}' 35 f' {last_name} and I am' 36 # f' {2026-year_of_birth}.' 37 f' {calculate_age(year_of_birth)}.' 38 ) 39 40 41def factory( 42 first_name, last_name, 43 sex, year_of_birth, 44 ):still green.
I remove the commented line
30def say_hello( 31 first_name, last_name, year_of_birth, 32): 33 return ( 34 f'Hello, my name is {first_name}' 35 f' {last_name} and I am' 36 f' {calculate_age(year_of_birth)}.' 37 ) 38 39 40def factory(I change the calculation in the calculate_age method to make sure the tests work, in
test_person.py8 @staticmethod 9 def calculate_age(year_of_birth): 10 return 1900 - year_of_birth 11 return ( 12 datetime.date.today().year 13 - year_of_birth 14 ) 15 16 def test_joe(self):The terminal is my friend, and shows AssertionError for all four people.
The ages of the expectations are all negative numbers, this is a problem.
The results of the call all have the right age. Lovely!
I change the calculation in the calculate_age method back
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_joe(self):green again.
I add a git commit message in the other terminal
git commit -am \ 'add calculate_age function'
assert person is alive
I want the calculate_age function to make sure that the age of the person is not more than 120 because I do not know that there are any people alive older than that, yet. For example john smith has a year_of_birth of 1580 which makes him too old to be alive.
RED: make it fail
I go back to the terminal where the tests are running.
I add a variable with an assert statement to calculate_age function in
src/person/__init__.py23def calculate_age(year_of_birth): 24 # return ( 25 age = ( 26 datetime.date.today().year 27 - year_of_birth 28 ) 29 assert age <= 120 30 return age 31 32 33def say_hello( 34 first_name, last_name, year_of_birth, 35):the terminal is my friend, and shows AssertionError
E AssertionErroranother problem, the error message does not tell me much. At least the
short test summary infoshows me what test the error happened inFAILED ...::TestPerson::test_john - AssertionError
GREEN: make it pass
I change the value of year_of_birth in test_john in test_person.py
23 def test_john(self):
24 first_name = 'john'
25 last_name = 'smith'
26 sex = 'M'
27 # year_of_birth = 1580
28 year_of_birth = 1980
the test passes.
REFACTOR: make it better
I add a comment about the bad
year_of_birth23 def test_john(self): 24 first_name = 'john' 25 last_name = 'smith' 26 sex = 'M' 27 year_of_birth = 1980 28 # year_of_birth = 1580 29 # raises AssertionError 30 # because older than 120I remove the commented line from the calculate_age function in
src/person/__init__.py23def calculate_age(year_of_birth): 24 age = ( 25 datetime.date.today().year 26 - year_of_birth 27 ) 28 assert age <= 120 29 return age 30 31 32def say_hello( 33 first_name, last_name, year_of_birth, 34):I add a git commit message in the other terminal
git commit -am 'assert person is alive'
test_when_year_of_birth_is_not_an_integer
I want the Person class to make sure that the value for year_of_birth is an integer (whole number without decimals).
RED: make it fail
I go back to the terminal where the tests are running.
I add a new test for when
year_of_birthis not an integer186 reality = mary.say_hello() 187 assert reality == my_expectation 188 self.assertEqual(reality, my_expectation) 189 190 def test_when_year_of_birth_is_not_an_integer(self): 191 person = src.person.Person( 192 first_name='first_name', 193 last_name='last_name', 194 sex='M', 195 ) 196 197 def test_dir_person_class(self):the terminal is my friend, and shows TypeError
TypeError: Person.__init__() missing 1 required positional argument: 'year_of_birth'
GREEN: make it pass
I make
year_of_birthan optional argument in the Person class insrc/person/__init__.py4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 # sex, year_of_birth, 9 sex, year_of_birth=None, 10 ):TypeError: unsupported operand type(s) for -: 'int' and 'NoneType'because I cannot do Arithmetic with None.
I add an assertion with the isinstance built-in function to make sure the function only gets integers
24def calculate_age(year_of_birth): 25 assert isinstance(year_of_birth, int) 26 age = ( 27 datetime.date.today().year 28 - year_of_birth 29 ) 30 assert age <= 120 31 return age 32 33 34def say_hello( 35 first_name, last_name, year_of_birth, 36):the terminal is my friend, and shows AssertionError
E AssertionErrorthe error message is still a problem. The
short test summary infoshows me what test the error happened inFAILED ...test_when_year_of_birth_is_not_an_integer - AssertionErrorI add a comment, then change
year_of_birthfrom the default value to a boolean in test_when_year_of_birth_is_not_an_integer, intest_person.py190 def test_when_year_of_birth_is_not_an_integer(self): 191 person = src.person.Person( 192 first_name='first_name', 193 last_name='last_name', 194 sex='M', 195 # year_of_birth=None, # fails 196 year_of_birth=False, 197 ) 198 199 def test_dir_person_class(self):the terminal shows AssertionError for the age being greater than
120. Wait a minute! I was expecting that to fail atassert isinstance(year_of_birth, int). This means a boolean is also an integer.I change
year_of_birthto a float190 def test_when_year_of_birth_is_not_an_integer(self): 191 person = src.person.Person( 192 first_name='first_name', 193 last_name='last_name', 194 sex='M', 195 # year_of_birth=None, # fails 196 year_of_birth=2026.0, 197 ) 198 199 def test_dir_person_class(self):the terminal shows AssertionError
I add a comment then change
year_of_birthto a string190 def test_when_year_of_birth_is_not_an_integer(self): 191 person = src.person.Person( 192 first_name='first_name', 193 last_name='last_name', 194 sex='M', 195 # year_of_birth=None, # fails 196 # year_of_birth=2026.0, # fails 197 year_of_birth='2026', 198 ) 199 200 def test_dir_person_class(self):the terminal shows AssertionError
I add a comment then change
year_of_birthto a tuple190 def test_when_year_of_birth_is_not_an_integer(self): 191 person = src.person.Person( 192 first_name='first_name', 193 last_name='last_name', 194 sex='M', 195 # year_of_birth=None, # fails 196 # year_of_birth=2026.0, # fails 197 # year_of_birth='2026', # fails 198 year_of_birth=(2026,), 199 ) 200 201 def test_dir_person_class(self):I add a comment
190 def test_when_year_of_birth_is_not_an_integer(self): 191 person = src.person.Person( 192 first_name='first_name', 193 last_name='last_name', 194 sex='M', 195 # year_of_birth=None, # fails 196 # year_of_birth=2026.0, # fails 197 # year_of_birth='2026', # fails 198 # year_of_birth=(2026,), # fails 199 ) 200 201 def test_dir_person_class(self):the test is green because there is no assertion or calls that cause AssertionError.
I remove the commented line from the Person class in
src/person/__init__.py4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth=None, 9 ):I add a git commit message in the other terminal
git commit -am 'add test_when_year_of_birth_is_not_an_integer'
close the project
I close
test_person.pyandsrc/person/__init__.pyI click in the terminal where the tests are running
I use q on the keyboard to leave the tests. The terminal goes back to the command line.
I change directory to the parent of
personcd ..the terminal shows
...\pumping_pythonI am back in the
pumping_pythondirectory.
review
I can use the datetime library to automatically get the current year for the calculation of a person’s age.
I can use assertions to make sure certain conditions are met before a program does something.
My tests have a new problem - when they cause an Exception the test stops in a RED state. My solution was to add notes and comment out the problems, which means the only way to know that the code causes the Exception is to remove the comments. There has to be a better way
test_joe, test_jane, test_john and test_mary also still have the problem where they are the same three tests. 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.