how to make a person
This is an exercise in making dictionaries with functions. I think they are the 2 most important things in Python
preview
These are the tests I have by the end of the chapter
1import datetime
2import random
3import src.person
4import unittest
5
6
7def choose(*choices):
8 return random.choice(choices)
9
10
11def this_year():
12 return datetime.datetime.now().year
13
14
15class TestPerson(unittest.TestCase):
16
17 def setUp(self):
18 self.random_year_of_birth = random.randint(
19 this_year()-120, this_year()
20 )
21 self.random_first_name = choose('jane', 'joe', 'john', 'person')
22
23 def test_factory_takes_keyword_arguments(self):
24 a_person = dict(
25 first_name=self.random_first_name,
26 last_name=choose('doe', 'smith', 'blow', 'public'),
27 sex=choose('F', 'M'),
28 )
29
30 self.assertEqual(
31 src.person.factory(
32 **a_person,
33 year_of_birth=self.random_year_of_birth,
34 ),
35 dict(
36 **a_person,
37 age=this_year()-self.random_year_of_birth,
38 )
39 )
40
41 def test_factory_w_default_arguments(self):
42 self.assertEqual(
43 src.person.factory(
44 first_name=self.random_first_name,
45 year_of_birth=self.random_year_of_birth,
46 ),
47 dict(
48 first_name=self.random_first_name,
49 last_name='doe',
50 sex='M',
51 age=this_year()-self.random_year_of_birth,
52 )
53 )
54
55
56# Exceptions seen
57# AssertionError
58# NameError
59# AttributeError
60# TypeError
61# SyntaxError
start the project
I open a terminal to run makePythonTdd.sh with
personas the name of the project./makePythonTdd.sh personAttention
on Windows without Windows Subsystem for Linux use makePythonTdd.ps1 instead of makePythonTdd.sh
./makePythonTdd.ps1 personit makes the folders and files that are needed, installs packages, runs the first test, and the terminal shows AssertionError
E AssertionError: True is not false tests/test_person.py:7: AssertionErrorI hold ctrl (Windows/Linux) or option/command (MacOS) on the keyboard and use the mouse to click on
tests/test_person.py:7to put the cursor on line 7 in the editorthen I change True to False in the assertion
7 self.assertFalse(False)the test passes
I change the name of the class to match the CapWords format to follow Python convention
4class TestPerson(unittest.TestCase):
test_factory_takes_keyword_arguments
RED: make it fail
I change
test_failuretotest_factory_takes_keyword_arguments1import unittest 2 3 4class TestPerson(unittest.TestCase): 5 6 def test_factory_takes_keyword_arguments(self): 7 self.assertEqual( 8 src.person.factory(), 9 None 10 ) 11 12 13# Exceptions seen 14# AssertionErrorNameError: name 'src' is not definedI add NameError to the list of Exceptions seen
13# Exceptions seen 14# AssertionError 15# NameError
GREEN: make it pass
I add an import statement for the
personmodule at the top oftest_person.py1import src.person 2import unittestthe terminal shows AttributeError
AttributeError: module 'src.person' has no attribute 'factory'there is nothing in
person.pywith that nameI add AttributeError to the list of Exceptions seen
14# Exceptions seen 15# AssertionError 16# NameError 17# AttributeErrorI open
person.pyfrom thesrcfolder in the editorI add a function to
person.py1def factory(): 2 return Nonethe test passes
I want the function to take in a keyword argument called
first_name. I add it to the test intest_person.py7 def test_factory_takes_keyword_arguments(self): 8 self.assertEqual( 9 src.person.factory( 10 first_name='first_name', 11 ), 12 None 13 ) 14 15 16# Exceptions seenTypeError: factory() got an unexpected keyword argument 'first_name'the test calls the
factoryfunction with input, but the definition inperson.pydoes not take any inputI add TypeError to the list of Exceptions seen in
test_person.py16# Exceptions seen 17# AssertionError 18# NameError 19# AttributeError 20# TypeErrorI add
first_nameas an input parameter to the function inperson.py1def factory(first_name): 2 return Nonethe test passes
I want the function to take in a keyword argument called
last_name. I add it to the test intest_person.py7 def test_factory_takes_keyword_arguments(self): 8 self.assertEqual( 9 src.person.factory( 10 first_name='first_name', 11 last_name='last_name', 12 ), 13 None 14 )TypeError: factory() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?the test calls the
factoryfunction with 2 inputs, but the definition inperson.pyonly takes 1 inputI add
last_nameto the function definition inperson.py1def factory(first_name, last_name): 2 return Nonethe test passes
I want the function to take in a keyword argument called
sex. I add it to the test intest_person.py7 def test_factory_takes_keyword_arguments(self): 8 self.assertEqual( 9 src.person.factory( 10 first_name='first_name', 11 last_name='last_name', 12 sex='M', 13 ), 14 None 15 )TypeError: factory() got an unexpected keyword argument 'sex'the test calls the
factoryfunction with 3 inputs, but the definition inperson.pyonly takes 2 inputsI add
sexas an input parameter to thefactoryfunction inperson.py1def factory( 2 first_name, last_name, 3 sex, 4 ): 5 return Nonethe test passes
I want the function to take in a keyword argument for
year_of_birthand give it the result of calling another function. I add it to the test intest_person.py7 def test_factory_takes_keyword_arguments(self): 8 self.assertEqual( 9 src.person.factory( 10 first_name='first_name', 11 last_name='last_name', 12 sex='M', 13 year_of_birth=this_year(), 14 ), 15 None 16 )NameError: name 'this_year' is not definedthere is nothing with that name in this file
I add a definition for the
this_yearfunction above the class definition intest_person.py1import src.person 2import unittest 3 4 5def this_year(): 6 return None 7 8 9class TestPerson(unittest.TestCase):TypeError: factory() got an unexpected keyword argument 'year_of_birth'the test calls the
factoryfunction with 4 inputs, but the definition inperson.pyonly takes 3 inputsI add the name to the function definition in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return Nonethe test passes
I want the
factoryfunction to return a dictionary as output, I change the expectation of the assertion intest_person.py11 def test_factory_takes_keyword_arguments(self): 12 self.assertEqual( 13 src.person.factory( 14 first_name='first_name', 15 last_name='last_name', 16 sex='M', 17 year_of_birth=this_year(), 18 ), 19 dict() 20 )the terminal shows AssertionError
AssertionError: None != {}I change None to a dictionary in the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return {}the test passes because
{}anddict()are two ways to make the empty dictionary
I want the expected dictionary in the test to have a key called
first_namewith the same value as what is given when thefactoryfunction is called. I add the key intest_person.py11 def test_factory_takes_keyword_arguments(self): 12 self.assertEqual( 13 src.person.factory( 14 first_name='first_name', 15 last_name='last_name', 16 sex='M', 17 year_of_birth=this_year(), 18 ), 19 dict( 20 first_name='first_name', 21 ) 22 )the terminal shows AssertionError
AssertionError: {} != {'first_name': 'first_name'}I change the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return {'first_name': 'first_name'}the test passes
I typed
'first_name'two times in the test, which means I have to make a change in 2 places when I want a different value. I add a variable to remove the repetition intest_person.py11 def test_factory_takes_keyword_arguments(self): 12 first_name = 'first_name' 13 14 self.assertEqual(I use the variable as the value for
first_namein the call tosrc.person.factoryin the assertion14 self.assertEqual( 15 src.person.factory( 16 # first_name='first_name', 17 first_name=first_name, 18 last_name='last_name', 19 sex='M', 20 year_of_birth=this_year(), 21 ),the test is still green
I use the variable as the value for the
first_namekey in the expected dictionary22 dict( 23 # first_name='first_name', 24 first_name=first_name, 25 )still green
I remove the commented lines
11 def test_factory_takes_keyword_arguments(self): 12 first_name = 'first_name' 13 14 self.assertEqual( 15 src.person.factory( 16 first_name=first_name, 17 last_name='last_name', 18 sex='M', 19 year_of_birth=this_year(), 20 ), 21 dict( 22 first_name=first_name, 23 ) 24 ) 25 26 27# Exceptions seenI now only need to change the value of
first_namein one placeI change
'first_name'to'jane'11 def test_factory_takes_keyword_arguments(self): 12 first_name = 'jane'the terminal shows AssertionError
AssertionError: {'first_name': 'first_name'} != {'first_name': 'jane'}I change the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return {'first_name': 'jane'}and the test is green again
I want the expected dictionary to have a key called
last_namewith the same value as what is given in the call to thefactoryfunction. I add it to the expectation intest_person.py14 self.assertEqual( 15 src.person.factory( 16 first_name=first_name, 17 last_name='last_name', 18 sex='M', 19 year_of_birth=this_year() 20 ), 21 dict( 22 first_name=first_name, 23 last_name='last_name', 24 ) 25 )the terminal shows AssertionError
E - {'first_name': 'jane'} E + {'first_name': 'jane', 'last_name': 'last_name'}I change the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': 'jane', 7 'last_name': 'last_name', 8 }the test passes
'last_name'happens two times in the test, I add a variable to remove the repetition like I did with'first_name'intest_person.py11 def test_factory_takes_keyword_arguments(self): 12 first_name = 'jane' 13 last_name = 'last_name' 14 15 self.assertEqual(I use the new variable in the call to
src.person.factoryin the assertion15 self.assertEqual( 16 src.person.factory( 17 first_name=first_name, 18 # last_name='last_name', 19 last_name=last_name, 20 sex='M', 21 year_of_birth=this_year(), 22 ),the test is still green
I use the variable in the expected dictionary
23 dict( 24 first_name=first_name, 25 # last_name='last_name', 26 last_name=last_name, 27 )still green
I remove the commented lines
11 def test_factory_takes_keyword_arguments(self): 12 first_name = 'jane' 13 last_name = 'last_name' 14 15 self.assertEqual( 16 src.person.factory( 17 first_name=first_name, 18 last_name=last_name, 19 sex='M', 20 year_of_birth=this_year(), 21 ), 22 dict( 23 first_name=first_name, 24 last_name=last_name, 25 ) 26 ) 27 28 29# Exceptions seenthe test is still green
I change the value from
'last_name'to'doe'11 def test_factory_takes_keyword_arguments(self): 12 first_name = 'jane' 13 last_name = 'doe'the terminal shows AssertionError
E - {'first_name': 'jane', 'last_name': 'last_name'} E ? ^^^^^^^^ E E + {'first_name': 'jane', 'last_name': 'doe'} E ? ^^the values for the
last_namekey are different in the 2 dictionariesI change the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': 'jane', 7 'last_name': 'doe', 8 }the test passes
I add a key called
sexto the dictionary with the same value as what is given in the call to thefactoryfunction intest_person.py15 self.assertEqual( 16 src.person.factory( 17 first_name=first_name, 18 last_name=last_name, 19 sex='M', 20 year_of_birth=this_year(), 21 ), 22 dict( 23 first_name=first_name, 24 last_name=last_name, 25 sex='M', 26 ) 27 )the terminal shows AssertionError
E - {'first_name': 'jane', 'last_name': 'doe'} E + {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'} E ? ++++++++++++I add a new key to the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': 'jane', 7 'last_name': 'doe', 8 'sex': 'M', 9 }the test passes
I add a variable to remove the repetition of the value for
sexintest_person.py11 def test_factory_takes_keyword_arguments(self): 12 first_name = 'jane' 13 last_name = 'doe' 14 sex = 'M' 15 16 self.assertEqual(I use the variable in the call to
src.person.factoryin the assertion16 self.assertEqual( 17 src.person.factory( 18 first_name=first_name, 19 last_name=last_name, 20 # sex='M', 21 sex=sex, 22 year_of_birth=this_year(), 23 ),the test is still green
I use the variable in the expected dictionary
24 dict( 25 first_name=first_name, 26 last_name=last_name, 27 # sex='M', 28 sex=sex, 29 )still green
I remove the commented lines
11 def test_factory_takes_keyword_arguments(self): 12 first_name = 'jane' 13 last_name = 'doe' 14 sex = 'M' 15 16 self.assertEqual( 17 src.person.factory( 18 first_name=first_name, 19 last_name=last_name, 20 sex=sex, 21 year_of_birth=this_year(), 22 ), 23 dict( 24 first_name=first_name, 25 last_name=last_name, 26 sex=sex, 27 ) 28 ) 29 30 31# Exceptions seenI change the value of the
sexvariable11 def test_factory_takes_keyword_arguments(self): 12 first_name = 'jane' 13 last_name = 'doe' 14 sex = 'F'the terminal shows AssertionError
E - {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'} E ? ^ E E + {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'} E ? ^I change the return statement in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': 'jane', 7 'last_name': 'doe', 8 'sex': 'F', 9 }and the test is green again
I want the
factoryfunction to return the age of the person it makes. I add a key to the expectation intest_person.py16 self.assertEqual( 17 src.person.factory( 18 first_name=first_name, 19 last_name=last_name, 20 sex=sex, 21 year_of_birth=this_year(), 22 ), 23 dict( 24 first_name=first_name, 25 last_name=last_name, 26 sex=sex, 27 age=this_year()-this_year(), 28 ) 29 )TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'I cannot do arithmetic with None and I want the value for this year
I add an import statement for the datetime module at the top of
test_person.py1import datetime 2import src.person 3import unittest 4 5 6def this_year(): 7 return Nonedatetime is a module from the Python standard library that is used for dates and times
I change the return statement in the
this_yearfunction6def this_year(): 7 return datetime.datetime.now().yearTip
I can also use the today method to get the same value
6def this_year(): 7 return datetime.datetime.today().yearThe terminal shows AssertionError
E - {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'} E + {'age': 0, 'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'} E ? ++++++++++the new dictionary has a value for the
'age'key.Here is what
datetime.datetime.now().yearordatetime.datetime.today().yearmeansdatetimeis the datetime module.datetimeis a call to the datetime object in the datetime module. Wait a minute, that is the same name again. Do I have to remember all this?.now()is a call to the now method of the datetime.datetime object from the datetime module, it returns a datetime.datetime object. Oh boy.today()is a call to the today method of the datetime.datetime object from the datetime module, it returns a datetime.datetime object.yearasks for the value of theyearclass attribute of the datetime.datetime object returned by the now method or today method of the datetime.datetime object from the datetime module
that was a lot of words, they become clearer in the chapters on classes and AttributeError.
I add a key for
ageto the return statement inperson.py4def factory( 5 first_name, last_name, 6 sex, year_of_birth, 7 ): 8 return { 9 'first_name': 'jane', 10 'last_name': 'doe', 11 'sex': 'F', 12 'age': 0, 13 }the test passes
I add a variable for
year_of_birthintest_person.py12 def test_factory_takes_keyword_arguments(self): 13 first_name = 'jane' 14 last_name = 'doe' 15 sex = 'F' 16 year_of_birth = this_year() 17 18 self.assertEqual(I use the variable in the call to
src.person.factoryin the assertion18 self.assertEqual( 19 src.person.factory( 20 first_name=first_name, 21 last_name=last_name, 22 sex=sex, 23 # year_of_birth=this_year(), 24 year_of_birth=year_of_birth, 25 ),the test is still green
I use the variable in the expected dictionary
26 dict( 27 first_name=first_name, 28 last_name=last_name, 29 sex=sex, 30 # age=this_year()-this_year(), 31 age=this_year()-year_of_birth, 32 )still green
I remove the commented lines
12 def test_factory_takes_keyword_arguments(self): 13 first_name = 'jane' 14 last_name = 'doe' 15 sex = 'F' 16 year_of_birth = this_year() 17 18 self.assertEqual( 19 src.person.factory( 20 first_name=first_name, 21 last_name=last_name, 22 sex=sex, 23 year_of_birth=year_of_birth, 24 ), 25 dict( 26 first_name=first_name, 27 last_name=last_name, 28 sex=sex, 29 age=this_year()-year_of_birth, 30 ) 31 ) 32 33 34# Exceptions seen
REFACTOR: make it better
the only difference between the call to the
factoryfunction and the expected dictionary in the assertion is that one has a year of birth and the other does a calculation with the year of birth. The other things are the same. I add a dictionary to remove the repetition12 def test_factory_takes_keyword_arguments(self): 13 first_name = 'jane' 14 last_name = 'doe' 15 sex = 'F' 16 year_of_birth = this_year() 17 a_person = dict( 18 first_name=first_name, 19 last_name=last_name, 20 sex=sex, 21 ) 22 23 self.assertEqual(I use the new variable with a starred expression in the call to
src.function.factorylike I did in test_functions_w_unknown_arguments23 self.assertEqual( 24 src.person.factory( 25 # first_name=first_name, 26 # last_name=last_name, 27 # sex=sex, 28 **a_person, 29 year_of_birth=year_of_birth, 30 ),the test is still green
I do the same thing in the expectation
31 dict( 32 # first_name=first_name, 33 # last_name=last_name, 34 # sex=sex, 35 **a_person, 36 age=this_year()-year_of_birth, 37 )still green
I remove the commented lines
23 self.assertEqual( 24 src.person.factory( 25 **a_person, 26 year_of_birth=year_of_birth, 27 ), 28 dict( 29 **a_person, 30 age=this_year()-year_of_birth, 31 ) 32 )green
I use the values of
first_name,last_nameand thesexvariables in the dictionary12 def test_factory_takes_keyword_arguments(self): 13 first_name = 'jane' 14 last_name = 'doe' 15 sex = 'F' 16 year_of_birth = this_year() 17 a_person = dict( 18 # first_name=first_name, 19 # last_name=last_name, 20 # sex=sex, 21 first_name='jane', 22 last_name='doe', 23 sex='F', 24 )still green
I remove the commented lines and the
first_name,last_nameandsexvariables since they are no longer used12 def test_factory_takes_keyword_arguments(self): 13 year_of_birth = this_year() 14 a_person = dict( 15 first_name='jane', 16 last_name='doe', 17 sex='F', 18 ) 19 20 self.assertEqual( 21 src.person.factory( 22 **a_person, 23 year_of_birth=year_of_birth, 24 ), 25 dict( 26 **a_person, 27 age=this_year()-year_of_birth, 28 ) 29 ) 30 31 32# Exceptions seengreen
test factory with random values
I want to use random values in the test. I add an import statement at the top of test_person.py
1import datetime
2import random
3import src.person
4import unittest
random is a module from the Python standard library that is used to make fake random numbers
RED: make it fail
I use a random integer for the
year_of_birthvariable13 def test_factory_takes_keyword_arguments(self): 14 # year_of_birth = this_year() 15 year_of_birth = random.randint( 16 this_year()-120, this_year() 17 ) 18 a_person = dict(Here is what the new line means
randomis the random module.randintis a call to the randint method from the random module. Okay, this one does not use the same name againthis_year()-120returns this year minus120this_year()returnsdatetime.datetime.now().yearwhich is the value for the current yearrandom.randint(this_year()-120, this_year())gives me a random number from 120 years ago, up to and also the current year which is returned by the call tothis_year()
I use ctrl+s (Windows/Linux) or command+s (MacOS) to save the file a few times to run the tests. When the age is not
0, the terminal shows AssertionErrorE - {'age': 0, 'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'} E ? ^ E E + {'age': X, 'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'} E ? ^Note
Xis for the random age
GREEN: make it pass
I add the age calculation from
test_person.pyto the return statement inperson.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': 'jane', 7 'last_name': 'doe', 8 'sex': 'F', 9 'age': this_year() - year_of_birth, 10 }NameError: name 'this_year' is not definedbecause I used a name that is not defined in
person.pyI use the return statement of the
this_year()function fromtest_person.pyto changethis_year()inperson.py8 return { 9 'first_name': 'jane', 10 'last_name': 'doe', 11 'sex': 'F, 12 'age': datetime.datetime.now().year - year_of_birth, 13 }NameError: name 'datetime' is not defined. Did you forget to import 'datetime'I add an import statement for the datetime module at the top of
person.py1import datetime 2 3 4def factory(the test passes
REFACTOR: make it better
I remove the commented
# year_of_birth = this_year()line fromtest_person.pyI add randomness to the
sexkey intest_person.py13 def test_factory_takes_keyword_arguments(self): 14 year_of_birth = random.randint( 15 this_year()-120, this_year() 16 ) 17 a_person = dict( 18 first_name='jane', 19 last_name='doe', 20 # sex='F', 21 sex=random.choice(('F', 'M')), 22 ) 23 24 self.assertEqual(randomis the random module.choiceis a call to the random.choice method from the random module, it returns a random value from the iterable it is given in parentheses('F', 'M')is a tuple with values for the random.choice method to choose from randomlyrandom.choice(('F', 'M'))randomly gives meForMevery time the test runs
I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes when
sexis randomly'F'.When
sexis randomly'M', the terminal shows AssertionErrorE - {'age': X, 'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'} E ? ^ E E + {'age': X, 'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'} E ? ^I add the
sexinput parameter instead of a value that does not change to the return statement inperson.py8 return { 9 'first_name': 'jane', 10 'last_name': 'doe', 11 'sex': sex, 12 'age': datetime.datetime.now().year - year_of_birth, 13 }the test passes with no more random failures
I remove
# sex = 'F'fromtest_person.py
I use random.choice with the
last_namekey17 a_person = dict( 18 first_name='jane', 19 # last_name='doe', 20 last_name=random.choice(( 21 'doe', 'smith', 'blow', 'public', 22 )), 23 sex=random.choice(('F', 'M')), 24 )I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes when
last_nameis'doe'.When
last_nameis notdoe, the terminal shows AssertionErrorE - {'age': X, 'first_name': 'jane', 'last_name': 'doe', 'sex': A} E ? ^^^ E E + {'age': X, 'first_name': 'jane', 'last_name': Z, 'sex': A} E ? ^Note
Zis for the random last name andAis for the random sex valueI use the
last_nameinput parameter as the value for the'last_name'key in the return statement inperson.py8 return { 9 'first_name': 'jane', 10 'last_name': last_name, 11 'sex': sex, 12 'age': datetime.datetime.today().year - year_of_birth, 13 }and the test is green again
I remove
# last_name = 'doe'then add randomness to thefirst_namekey intest_person.py17 a_person = dict( 18 # first_name='jane', 19 first_name=random.choice(( 20 'jane', 'joe', 'john', 'person', 21 )), 22 last_name=random.choice(( 23 'doe', 'smith', 'blow', 'public', 24 )), 25 sex=random.choice(('F', 'M')), 26 )I use ctrl+s (Windows/Linux) or command+s (MacOS) to run the test a few times and it passes when
first_nameis'jane'.When
first_nameis not'jane'the terminal shows AssertionErrorE - {'age': X, 'first_name': 'jane', 'last_name': Z, 'sex': A} E ? ^^^^^^ E E + {'age': X, 'first_name': Y, 'last_name': Z, 'sex': A} E ? ^Note
Yis for the random first nameI add the
first_nameinput parameter to the return statement inperson.py4def factory( 5 first_name, last_name, 6 sex, year_of_birth, 7 ): 8 return { 9 'first_name': first_name, 10 'last_name': last_name, 11 'sex': sex, 12 'age': datetime.datetime.today().year - year_of_birth, 13 }the test passes
I remove the commented line
# first_name = 'jane'fromtest_person.pyI add a function for the calls to the random.choice method with a starred expression like in test_functions_w_unknown_arguments
4import unittest 5 6 7def choose(*choices): 8 return random.choice(choices) 9 10 11def this_year():I use the new function in the test
21 a_person = dict( 22 # first_name=random.choice(( 23 # 'jane', 'joe', 'john', 'person', 24 # )), 25 # last_name=random.choice(( 26 # 'doe', 'smith', 'blow', 'public', 27 # )), 28 # sex=random.choice(('F', 'M')), 29 first_name=choose('jane', 'joe', 'john', 'person'), 30 last_name=choose('doe', 'smith', 'blow', 'public'), 31 sex=choose('F', 'M'), 32 ) 33 34 self.assertEqual(the test is still green
I remove the commented lines
17 def test_factory_takes_keyword_arguments(self): 18 year_of_birth = random.randint( 19 this_year()-120, this_year() 20 ) 21 a_person = dict( 22 first_name=choose('jane', 'joe', 'john', 'person'), 23 last_name=choose('doe', 'smith', 'blow', 'public'), 24 sex=choose('F', 'M'), 25 ) 26 27 self.assertEqual( 28 src.person.factory( 29 **a_person, 30 year_of_birth=year_of_birth, 31 ), 32 dict( 33 **a_person, 34 age=this_year()-year_of_birth, 35 ) 36 ) 37 38 39# Exceptions seenstill green
test_factory_w_default_arguments
I want to see what happens when I try to make a person without a value for the last_name argument
RED: make it fail
I copy
test_factory_takes_keyword_argumentsand paste it below intest_person.pyI change the name of the new test to
test_factory_w_default_arguments, then comment out thelast_namekey-value pair in thea_persondictionary27 self.assertEqual( 28 src.person.factory( 29 **a_person, 30 year_of_birth=year_of_birth, 31 ), 32 dict( 33 **a_person, 34 age=this_year()-year_of_birth, 35 ) 36 ) 37 38 def test_factory_w_default_arguments(self): 39 year_of_birth = random.randint( 40 this_year()-120, this_year() 41 ) 42 a_person = dict( 43 first_name=choose('jane', 'joe', 'john', 'person'), 44 # last_name=choose('doe', 'smith', 'blow', 'public'), 45 sex=choose('F', 'M'), 46 ) 47 48 self.assertEqual( 49 src.person.factory( 50 **a_person, 51 year_of_birth=year_of_birth, 52 ), 53 dict( 54 **a_person, 55 age=this_year()-year_of_birth, 56 ) 57 ) 58 59 60# Exceptions seenTypeError: factory() missing 1 required positional argument: 'last_name'
GREEN: make it pass
I add a default value for
last_namein thefactoryfunction inperson.pyto make it a choice4def factory( 5 first_name, last_name=None, 6 sex, year_of_birth, 7 ):the terminal shows SyntaxError
SyntaxError: parameter without a default follows parameter with a defaultI cannot put a parameter that does NOT have a default value after a parameter that has a default value
I add SyntaxError to the list of Exceptions seen in
test_person.py64# Exceptions seen 65# AssertionError 66# NameError 67# AttributeError 68# TypeError 69# SyntaxErrorI add a default value for
sexin thefactoryfunction inperson.py4def factory( 5 first_name, last_name=None, 6 sex=None, year_of_birth, 7 ):the terminal shows SyntaxError
SyntaxError: parameter without a default follows parameter with a defaultI add a default value for
year_of_birth4def factory( 5 first_name, last_name=None, 6 sex=None, year_of_birth=None, 7 ):the terminal shows AssertionError
E - {'age': X, 'first_name': Y, 'last_name': None, 'sex': 'F'} E ? ------------------- E E + {'age': X, 'first_name': Y, 'sex': 'F'Note
Xis for the random age,Yis for the random first namethe
factoryfunction returns a dictionary with a key called'last_name', the test does not expect a dictionary with a key called'last_name'I add a key-value pair for
last_namein the expectation oftest_factory_w_default_argumentsintest_person.py57 dict( 58 **a_person, 59 last_name='doe', 60 age=this_year()-year_of_birth, 61 )the terminal shows AssertionError
E - {'age': X, 'first_name': Y, 'last_name': None, 'sex': A} E ? ^ - E E + {'age': X, 'first_name': Y, 'last_name': 'doe', 'sex': A} E ? ^^ +the
factoryfunction returns a dictionary with a value of None forlast_nameand the test expects'doe'I change the default value for
last_namein thefactoryfunction inperson.py4def factory( 5 first_name, last_name='doe', 6 sex=None, year_of_birth=None, 7 ):the test passes
Note
When the
factoryfunction is called with no value for thelast_nameargument, it uses'doe'because that is the default value in the function definition, it is the same as calling it withlast_name='doe'src.person.factory( first_name=first_name, sex=sex, year_of_birth=year_of_birth, last_name='doe', )see test_functions_w_default_arguments for more
I remove the commented line from
test_factory_w_default_argumentsintest_person.pyI comment out the
sexkey intest_factory_w_default_argumentsto see what happens when I call thefactoryfunction without it38 def test_factory_w_default_arguments(self): 39 year_of_birth = random.randint( 40 this_year()-120, this_year() 41 ) 42 a_person = dict( 43 first_name=choose('jane', 'joe', 'john', 'person'), 44 # sex=choose('F', 'M'), 45 )the terminal shows AssertionError
E - {'age': X, 'first_name': Y, 'last_name': 'doe', 'sex': None} E ? ------------- E E + {'age': X, 'first_name': Y, 'last_name': 'doe'}the
factoryfunction returns a dictionary with a key called'sex', the test does not expect a dictionary with a key called'sex'I add a key-value pair for
sexin the expectation oftest_factory_w_default_argumentsintest_person.py52 dict( 53 **a_person, 54 last_name='doe', 55 sex='M', 56 age=this_year()-year_of_birth, 57 )the terminal shows AssertionError
E - {'age': X, 'first_name': Y, 'last_name': Z, 'sex': None} E ? ^^^^ E E + {'age': X, 'first_name': Y, 'last_name': Z, 'sex': 'M'} E ? ^^^the
factoryfunction returns a dictionary with a value of None forsexand the test expects'M'I change the default value for
sexin thefactoryfunction inperson.py4def factory( 5 first_name, last_name='doe', 6 sex='M', year_of_birth=None, 7 ):the test passes
Note
When the
factoryfunction is called with no value for thesexargument, it uses'M'because that is the default value in the function definition, it is the same as calling it withsex='M'src.person.factory( first_name=first_name, year_of_birth=year_of_birth, last_name='doe', sex='M', )since the values are the same as the default values, I can call the function without them
src.person.factory( first_name=first_name, year_of_birth=year_of_birth, )see test_functions_w_default_arguments for more
I remove the commented line
# sex=choose('F', 'M'),fromtest_factory_w_default_argumentsintest_person.pyI do not need the
a_persondictionary intest_factory_takes_keyword_argumentsbecause it has only one key. I can use a variable42 a_person = dict( 43 first_name=choose('jane', 'joe', 'john', 'person'), 44 ) 45 first_name = choose('jane', 'joe', 'john', 'person') 46 47 self.assertEqual(I use the variable in the call to
src.person.factoryin the assertion47 self.assertEqual( 48 src.person.factory( 49 **a_person, 50 first_name=first_name, 51 year_of_birth=year_of_birth, 52 ),TypeError: src.person.factory() got multiple values for keyword argument 'first_name'because the
**a_persondictionary has a key calledfirst_name, the call tosrc.person.factorygets called with the same name two timesI comment out
a_person,in the call tosrc.person.factory47 self.assertEqual( 48 src.person.factory( 49 # **a_person, 50 first_name=first_name, 51 year_of_birth=year_of_birth, 52 ),the terminal randomly shows AssertionError
E - {'age': X, 'first_name': Y, 'last_name': 'doe', 'sex': 'M'} E ? ^ E E + {'age': X, 'first_name': B, 'last_name': 'doe', 'sex': 'M'} E ? ^because the values for
first_namerandomly change in both dictionariesI use the
first_namevariable in the expectation53 dict( 54 **a_person, 55 first_name=first_name, 56 last_name='doe', 57 sex='M', 58 age=this_year()-year_of_birth, 59 )TypeError: dict() got multiple values for keyword argument 'first_name'I comment out
**a_person,in the dictionary53 dict( 54 # **a_person, 55 first_name=first_name, 56 last_name='doe', 57 sex='M', 58 age=this_year()-year_of_birth, 59 )the test passes
I remove the commented lines and the
a_persondictionary38 def test_factory_w_default_arguments(self): 39 year_of_birth = random.randint( 40 this_year()-120, this_year() 41 ) 42 first_name = choose('jane', 'joe', 'john', 'person') 43 44 self.assertEqual( 45 src.person.factory( 46 first_name=first_name, 47 year_of_birth=year_of_birth, 48 ), 49 dict( 50 first_name=first_name, 51 last_name='doe', 52 sex='M', 53 age=this_year()-year_of_birth, 54 ) 55 ) 56 57 58# Exceptions seen
REFACTOR: make it better
first_nameandyear_of_birthare made the same way in both tests. I add the setUp method to theTestPersonclass with a class attribute (variable) to remove the repetition of theyear_of_birthvariable15class TestPerson(unittest.TestCase): 16 17 def setUp(self): 18 self.random_year_of_birth = random.randint( 19 this_year()-120, this_year() 20 ) 21 22 def test_factory_takes_keyword_arguments(self):I can use the class attribute directly
I point
year_of_birthintest_factory_takes_keyword_argumentsto the class attribute22 def test_factory_takes_keyword_arguments(self): 23 # year_of_birth = random.randint( 24 # this_year()-120, this_year() 25 # ) 26 year_of_birth = self.random_year_of_birth 27 a_person = dict(the test is still green
I use the class attribute in the call to
src.person.factoryin the assertion33 self.assertEqual( 34 src.person.factory( 35 **a_person, 36 # year_of_birth=year_of_birth, 37 year_of_birth=self.random_year_of_birth, 38 ),still green
I use the class attribute in the expectation
39 dict( 40 **a_person, 41 # age=this_year()-year_of_birth, 42 age=this_year()-self.random_year_of_birth, 43 )green
I remove the commented lines and the
year_of_birthvariable22 def test_factory_takes_keyword_arguments(self): 23 a_person = dict( 24 first_name=choose('jane', 'joe', 'john', 'person'), 25 last_name=choose('doe', 'smith', 'blow', 'public'), 26 sex=choose('F', 'M'), 27 ) 28 29 self.assertEqual( 30 src.person.factory( 31 **a_person, 32 year_of_birth=self.random_year_of_birth, 33 ), 34 dict( 35 **a_person, 36 age=this_year()-self.random_year_of_birth, 37 ) 38 ) 39 40 def test_factory_w_default_arguments(self):I point the
year_of_birthvariable intest_factory_w_default_argumentsto the class attribute40 def test_factory_w_default_arguments(self): 41 # year_of_birth = random.randint( 42 # this_year()-120, this_year() 43 # ) 44 year_of_birth = self.random_year_of_birth 45 first_name = choose('jane', 'joe', 'john', 'person')the test is still green
I use the class attribute in the call to
src.person.factoryin the assertion47 self.assertEqual( 48 src.person.factory( 49 first_name=first_name, 50 # year_of_birth=year_of_birth, 51 year_of_birth=self.random_year_of_birth, 52 ),still green
I use the class attribute in the expectation of the assertion
53 dict( 54 first_name=first_name, 55 last_name='doe', 56 sex='M', 57 # age=this_year()-year_of_birth, 58 age=this_year()-self.random_year_of_birth, 59 )green
I remove the commented lines and the
year_of_birthvariable40 def test_factory_w_default_arguments(self): 41 first_name = choose('jane', 'joe', 'john', 'person') 42 43 self.assertEqual( 44 src.person.factory( 45 first_name=first_name, 46 year_of_birth=self.random_year_of_birth, 47 ), 48 dict( 49 first_name=first_name, 50 last_name='doe', 51 sex='M', 52 age=this_year()-self.random_year_of_birth, 53 ) 54 ) 55 56 57# Exceptions seenthe tests are still green
I add a class attribute (variable) for
first_nameto the setUp method to remove repetition of the variable17 def setUp(self): 18 self.random_year_of_birth = random.randint( 19 this_year()-120, this_year() 20 ) 21 self.random_first_name = choose('jane', 'joe', 'john', 'person') 22 23 def test_factory_takes_keyword_arguments(self):I can use the class attribute directly
I use the class attribute as the value for the
first_namekey in thea_persondictionary intest_factory_takes_keyword_arguments23 def test_factory_takes_keyword_arguments(self): 24 a_person = dict( 25 # first_name=choose('jane', 'joe', 'john', 'person'), 26 first_name=self.random_first_name, 27 last_name=choose('doe', 'smith', 'blow', 'public'), 28 sex=choose('F', 'M'), 29 )the test is still green
I remove the commented line
21 def test_factory_takes_keyword_arguments(self): 22 a_person = dict( 23 first_name=self.random_first_name, 24 last_name=choose('doe', 'smith', 'blow', 'public'), 25 sex=choose('F', 'M'), 26 ) 27 28 self.assertEqual( 29 src.person.factory( 30 **a_person, 31 year_of_birth=self.random_year_of_birth, 32 ), 33 dict( 34 **a_person, 35 age=this_year()-self.random_year_of_birth, 36 ) 37 ) 38 39 def test_factory_w_default_arguments(self):still green
I point the
first_namevariable intest_factory_w_default_argumentsto the class attribute41 def test_factory_w_default_arguments(self): 42 # first_name = choose('jane', 'joe', 'john', 'person') 43 first_name = self.random_first_name 44 45 self.assertEqual(green
I use the class attribute in the call to
src.person.factoryin the assertion45 self.assertEqual( 46 src.person.factory( 47 # first_name=first_name, 48 first_name=self.random_first_name, 49 year_of_birth=self.random_year_of_birth, 50 ),still green
I use the class attribute in the expectation of the assertion
53 dict( 54 # first_name=first_name, 55 first_name=self.random_first_name, 56 last_name='doe', 57 sex='M', 58 age=this_year()-self.random_year_of_birth, 59 )the test is still green
I remove the commented lines and the
first_namevariable41 def test_factory_w_default_arguments(self): 42 self.assertEqual( 43 src.person.factory( 44 first_name=self.random_first_name, 45 year_of_birth=self.random_year_of_birth, 46 ), 47 dict( 48 first_name=self.random_first_name, 49 last_name='doe', 50 sex='M', 51 age=this_year()-self.random_year_of_birth, 52 ) 53 ) 54 55 56# Exceptions seenthe tests are still green
test_person_tests
I want to write the solution without looking at the tests
RED: make it fail
I close
test_person.pyin the editorthen I delete all the text in
person.py. The terminal shows AttributeErrorAttributeError: module 'src.person' has no attribute 'factory'there is nothing in
person.pywith the namefactory
GREEN: make it pass
I add the name
1factoryNameError: name 'factory' is not definedI point it to None to define it
1factory = NoneTypeError: 'NoneType' object is not callablefactorypoints to None which is not callable like a functionI make
factorya function1def factory(): 2 return NoneTypeError: factory() got an unexpected keyword argument 'first_name'the test called the function with a keyword argument
I add
first_nameto the function definition1def factory(first_name): 2 return NoneTypeError: factory() got an unexpected keyword argument 'year_of_birth'the test called the function with another keyword argument
I add
year_of_birthto the function definition1def factory(first_name, year_of_birth): 2 return Nonethe terminal shows AssertionError
AssertionError: None != {'first_name': X, 'last_name': 'doe', 'sex': 'M', 'age': A}the tests expect a dictionary and the
factoryfunction returns NoneI make the function return a dictionary instead of None
1def factory(first_name, year_of_birth): 2 return { 3 'first_name': 'john', 4 'last_name': 'doe', 5 'sex': 'M', 6 'age': 55, 7 }the terminal shows AssertionError
Attention
Some of your values will be different because the test uses random values
E - {'age': 55, 'first_name': 'john', 'last_name': 'doe', 'sex': 'M'} E ? ^^ ^^ E E + {'age': X, 'first_name': Y, 'last_name': 'doe', 'sex': 'M'} E ? ^ ^I use the
first_nameinput parameter in the return statement1def factory(first_name, year_of_birth): 2 return { 3 'first_name': first_name, 4 'last_name': 'doe', 5 'sex': 'M', 6 'age': 55, 7 }the first name matches and when the ages are different, the terminal shows AssertionError
E - {'age': 55, 'first_name': Y, 'last_name': 'doe', 'sex': A} E ? ^^ E E + {'age': X, 'first_name': Y, 'last_name': 'doe', 'sex': A} E ? ^sometimes the terminal shows TypeError
TypeError: factory() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?I use the
year_of_birthinput parameter in the return statement for the value ofage1def factory(first_name, year_of_birth): 2 return { 3 'first_name': first_name, 4 'last_name': 'doe', 5 'sex': 'M', 6 'age': year_of_birth, 7 }the terminal shows AssertionError
E - {'age': ABCD, 'first_name': 'john', 'last_name': 'doe', 'sex': 'M'} E ? ^^^^ E E + {'age': X, 'first_name': 'john', 'last_name': 'doe', 'sex': 'M'} E ? ^the value the
factoryfunction returned for theagekey has 4 digits (a year), and the test expects the difference between that value and the current yearI add an import statement for the datetime module at the top of the file
1import datetime 2 3 4def factory(the terminal still shows AssertionError
I use the datetime module to get the current year for the
agecalculation4 return { 5 'first_name': first_name, 6 'last_name': 'doe', 7 'sex': 'M', 8 'age': datetime.datetime.today().year - year_of_birth, 9 }TypeError: factory() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?the test called the function with another keyword argument
I add a new input parameter to the function
4def factory( 5 first_name, year_of_birth, 6 last_name, 7 ): 8 return {TypeError: factory() missing 1 required positional argument: 'last_name'the test called the function with another argument and Python took it is a positional argument for
last_nameI add a default value for
last_nameso Python does not take it is a positional argument when a name is not given4def factory( 5 first_name, year_of_birth, 6 last_name=None, 7 ): 8 return {TypeError: factory() got an unexpected keyword argument 'sex'another keyword argument
I add the name to the definition of the function
4def factory( 5 first_name, year_of_birth, 6 last_name=None, sex, 7 ):the terminal shows SyntaxError
SyntaxError: parameter without a default follows parameter with a defaultI cannot put a parameter that does NOT have a default value after a parameter that has a default value
I add a default value for
sex1def factory( 2 first_name, year_of_birth, 3 last_name=None, sex=None, 4 ):the terminal shows AssertionError
E - {'age': X, 'first_name': Y, 'last_name': 'doe', 'sex': 'M'} E ? ^^^ E E + {'age': X, 'first_name': Y, 'last_name': Z, 'sex': A} E ? ^ ^the values for
last_nameandsexchange every time the tests runI use the
sexinput parameter in the return statement8 return { 9 'first_name': first_name, 10 'last_name': 'doe', 11 'sex': sex, 12 'age': datetime.datetime.today().year - year_of_birth, 13 }the terminal shows AssertionError
E - {'age': X, 'first_name': Y, 'last_name': Z, 'sex': None} E ? ^^^^ E E + {'age': X, 'first_name': Y, 'last_name': Z, 'sex': 'M'} E ? ^^^the test expects
'M'as the value ofsexand the function returns None which is its default valueI change the default value of
sexto'M'4def factory( 5 first_name, year_of_birth, 6 last_name=None, sex='M', 7 ):the test passes when the
last_nameis randomly'doe'.When the
last_nameis not'doe', the terminal shows AssertionErrorE - {'age': X, 'first_name': Y, 'last_name': 'doe', 'sex': A} E ? ^^^^^ E E + {'age': X, 'first_name': Y, 'last_name': Z, 'sex': A} E ? ^the
last_namevalue is different between the two dictionariesI use the
last_nameinput parameter in the return statement8 return { 9 'first_name': first_name, 10 'last_name': last_name, 11 'sex': sex, 12 'age': datetime.datetime.today().year - year_of_birth, 13 }the terminal shows AssertionError
E - {'age': X, 'first_name': Y, 'last_name': None, 'sex': A} E ? ^ - E E + {'age': X, 'first_name': Y, 'last_name': 'doe', 'sex': A} E ? ^^ +the test expects
'doe'as the value oflast_nameand the function returns None which is its default valueI change the default value for
last_nameto match the expectation1import datetime 2 3 4def factory( 5 first_name, year_of_birth, 6 last_name='doe', sex='M', 7 ): 8 return { 9 'first_name': first_name, 10 'last_name': last_name, 11 'sex': sex, 12 'age': datetime.datetime.today().year - year_of_birth, 13 }the test passes with no more random failures. I am getting pretty good at this.
This solution only has 2 parameters with default values
def factory(
first_name, year_of_birth,
last_name='doe', sex='M',
):
which is a little bit different from the first solution where I had 3 parameters with default values
def factory(
first_name, last_name='doe',
sex='M', year_of_birth=None,
):
new things can be learned from repetition
close the project
I close
person.pyin the editorI click in the terminal and use ctrl+c on the keyboard to leave the tests, the terminal shows
.../pumping_pythonI am back in the
pumping_pythondirectory
Note
on Windows without Windows Subsystem for Linux
the terminal shows
(.venv) ...\pumping_python\personI deactivate the virtual environment
deactivatethe terminal goes back to the command line,
(.venv)is no longer on the left side...\pumping_python\personI change directory to the parent of
personcd ..the terminal shows
...\pumping_pythonI am back in the
pumping_pythondirectory
review
I ran tests to make a function that takes in keyword arguments as input, has default values for some of them, performs an action based on an input and returns a dictionary as output
I also saw the following Exceptions
code from the chapter
what is next?
you know
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