how to make a person¶
This is an exercise in making dictionaries with functions. I think these are the 2 most important concepts in Python
requirements¶
I open a terminal to run makePythonTdd.sh with
personas the name of the project./makePythonTdd.sh person
on Windows without Windows Subsystem Linux use makePythonTdd.ps1
./makePythonTdd.ps1 person
it 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: AssertionError
I hold
ctrl(windows/linux) oroption(mac) on the keyboard and use the mouse to click ontests/test_person.py:7to open it in the editorthen I change
TruetoFalseto make the test pass7self.assertFalse(False)
I change the name of the class to match the CapWords format
4class TestPerson(unittest.TestCase):
test_takes_keyword_arguments¶
red: make it fail¶
I change
test_failuretotest_takes_keyword_arguments1import unittest 2 3 4class TestPerson(unittest.TestCase): 5 6 def test_takes_keyword_arguments(self): 7 self.assertEqual( 8 src.person.factory(), 9 None 10 )
the terminal shows NameError
NameError: name 'src' is not defined
green: make it pass¶
I add it to the list of Exceptions encountered in
test_person.py13# Exceptions Encountered 14# AssertionError 15# NameError
then I add an import statement for the
personmodule at the top of the file1import src.person 2import unittest
the terminal shows AttributeError
AttributeError: module 'src.person' has no attribute 'factory'
I add it to the list of Exceptions encountered
14# Exceptions Encountered 15# AssertionError 16# NameError 17# AttributeError
I click on
person.pyin thesrcfolder to open it in the editor then I add a function1def factory(): 2 return None
the test passes
I want the function to take in a keyword argument named
first_name. I add it to the test intest_person.py7 def test_takes_keyword_arguments(self): 8 self.assertEqual( 9 src.person.factory( 10 first_name='first_name', 11 ), 12 None 13 )
the terminal shows TypeError
TypeError: factory() got an unexpected keyword argument 'first_name'
I add the error to the list of Exceptions encountered in
test_person.py16# Exceptions Encountered 17# AssertionError 18# NameError 19# AttributeError 20# TypeError
then I add
first_nameas an input parameter to the function inperson.py1def factory(first_name): 2 return None
the test passes
I want the function to take in a keyword argument named
last_name. I add it to the test intest_person.py7 def test_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 )
the terminal shows TypeError
TypeError: factory() got an unexpected keyword argument 'last_name'
I add
last_nameto the function definition inperson.py1def factory(first_name, last_name): 2 return None
the test passes
I want the function to take in a keyword argument named
sex. I add it to the test intest_person.py7 def test_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 )
the terminal shows TypeError
TypeError: factory() got an unexpected keyword argument 'sex'
I add
sexas input to the function inperson.py1def factory( 2 first_name, last_name, 3 sex, 4 ): 5 return None
the 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_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 )
the terminal shows NameError
NameError: name 'this_year' is not defined
I add a definition for the
this_yearfunction above the class definition intest_person.pyNote
the …(ellipsis) represents code that does not need to change in this part
1import unittest 2import src.person 3 4 5def this_year(): 6 return None 7 8 9class TestPerson(unittest.TestCase): 10 11 def test_takes_keyword_arguments(self): 12 ...
the terminal shows TypeError
TypeError: factory() got an unexpected keyword argument 'year_of_birth'
when I add the name to the function definition in
person.py1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return None
the test passes
I want the
factoryfunction to return a dictionary as output, I change the expectation in the assertion intest_person.py11 def test_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 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 write an empty dictionaryI want the expected dictionary in the test to have a key named
first_namewith the same value as what is given when thefactoryfunction is called. I change it intest_person.py11 def test_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
'first_name'appears twice in the test, which means I have to make a change in 2 places if I want a different value for it. I add a variable to remove the repetition intest_person.py11 def test_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 )
the test is still green. I now only need to change the value of
first_namein one place11 def test_takes_keyword_arguments(self): 12 first_name = 'jane' 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 )
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 dictionary to have a key named
last_namewith the same value as what is given in the call to thefactoryfunction. I add it to the expectation intest_person.py11 def test_takes_keyword_arguments(self): 12 first_name = 'jane' 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 last_name='last_name', 24 ) 25 )
the terminal shows AssertionError
AssertionError: {'first_name': 'jane'} != {'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 twice in the test, I add a variable to remove the duplication like I did withfirst_nameintest_person.py11 def test_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 )
I change the value
Note
the …(ellipsis) represents code that does not need to change in this part
11 def test_takes_keyword_arguments(self): 12 first_name = 'jane' 13 last_name = 'doe' 14 15 ...
the terminal shows AssertionError
AssertionError: {'first_name': 'jane', 'last_name': 'last_name'} != {'first_name': 'jane', 'last_name': 'doe'}
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 }
the test passes
I add a key named
sexto the dictionary with the same value as what is given in the call to thefactoryfunction intest_person.py11 def test_takes_keyword_arguments(self): 12 first_name = 'jane' 13 last_name = 'doe' 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 sex='M', 26 ) 27 )
the terminal shows AssertionError
AssertionError: {'first_name': 'jane', 'last_name': 'doe'} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'}
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': 'M', 9 }
the terminal shows green again
I add a variable to remove the repetition in
test_person.py11 def test_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 )
the test is still green
when I change the value of the
sexvariableNote
the …(ellipsis) represents code that does not need to change in this part
1 def test_takes_keyword_arguments(self): 2 first_name = 'jane' 3 last_name = 'doe' 4 sex = 'F' 5 6 ...
the terminal shows AssertionError
AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M'} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'}
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 add a calculation and a key for
ageto the expectation intest_person.py11 def test_takes_keyword_arguments(self): 12 first_name = 'jane' 13 last_name = 'doe' 14 sex = 'F' 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 age=this_year()-this_year(), 28 ) 29 )
the terminal shows TypeError
TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
I cannot do subtraction with None and I want the value for the current year
I add an import statement at the top of
test_person.py1import datetime 2import src.person 3import unittest 4 5 6def this_year(): 7 return None
datetime 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().year
datetime.datetime.now().yearreturns theyearattribute of thedatetimeobject returned by the now method of thedatetimeclass, from the datetime module. If you are confused, do not worry, that was a lot of terminology, it is explained in more detail when I test AttributeError and classesI can also use the today method to get the same value
6def this_year(): 7 return datetime.datetime.today().year
the terminal shows AssertionError
AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F'} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 0}
the new dictionary has a value for
ageI change the return statement in
person.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 to remove duplication from
test_person.py12 def test_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 )
and the test is still green
refactor: make it better¶
I add an import statement at the top of
test_person.pyto use random values in the testsTip
I like to arrange import statements alphabetically
4import datetime 5import random 6import src.person 7import unittest
random is a module from the python standard library that is used to make fake random numbers. I use it for the
year_of_birthvariable13 def test_takes_keyword_arguments(self): 14 first_name = 'jane' 15 last_name = 'doe' 16 sex = 'F' 17 year_of_birth = random.randint( 18 this_year()-120, this_year() 19 ) 20 21 ...
random.randint(this_year()-120, this_year())gives me a random number from 120 years ago, up to and including the current year which is returned bythis_year(). I hit savectrl+s(windows/linux) orcommand+s(mac) a few times to run the tests and and when the age is not0, the terminal shows AssertionErrorAssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': 0} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': X}
I add the age calculation from
test_person.pyto 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': this_year() - year_of_birth, 13 }
the terminal shows NameError
NameError: name 'this_year' is not defined
because I called a function that is NOT in
person.py.I use the return statement of the
this_year()function fromtest_person.pyto changethis_year()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': datetime.datetime.now().year - year_of_birth, 13 }
the terminal shows NameError
NameError: name 'datetime' is not defined. Did you forget to import 'datetime'
I add an import statement at the top of
person.pyNote
the …(ellipsis) represents code that does not need to change in this part
1import datetime 2 3 4def factory( 5 ...
the test passes
I add randomness to the
sexvariable intest_person.py13 def test_takes_keyword_arguments(self): 14 first_name = 'jane' 15 last_name = 'doe' 16 sex = random.choice(('F', 'M')) 17 year_of_birth = random.randint( 18 this_year()-120, this_year() 19 ) 20 21 ...
random.choice(('F', 'M'))randomly gives meForMevery time the test runs. I hit savectrl+s(windows/linux) orcommand+s(mac) a few times to run the tests and the terminal shows success whensexis randomly'F', and when it is randomly'M', the terminal shows AssertionErrorAssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': 'F', 'age': X} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M', 'age': X}
I add the
sexinput parameter instead of a value that does not change to 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': sex, 12 'age': datetime.datetime.now().year - year_of_birth, 13 }
the test passes with no more random failures
I use random.choice with the
last_namevariable intest_person.py13 def test_takes_keyword_arguments(self): 14 first_name = 'jane' 15 last_name = random.choice(( 16 'doe', 'smith', 'blow', 'public', 17 )) 18 sex = random.choice(('F', 'M')) 19 year_of_birth = random.randint( 20 this_year()-120, this_year() 21 ) 22 23 ...
I hit save
ctrl+s(windows/linux) orcommand+s(mac) a few times to run the tests and the terminal shows success whenlast_nameis'doe', and when it is not, the terminal shows AssertionErrorAssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': X, 'age': Y} != {'first_name': 'jane', 'last_name': 'public', 'sex': X, 'age': Y} AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': X, 'age': Y} != {'first_name': 'jane', 'last_name': 'smith', 'sex': X, 'age': Y} AssertionError: {'first_name': 'jane', 'last_name': 'doe', 'sex': X, 'age': Y} != {'first_name': 'jane', 'last_name': 'blow', 'sex': X, 'age': Y}
I add the
last_nameinput parameter in 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': last_name, 11 'sex': sex, 12 'age': datetime.datetime.today().year - year_of_birth, 13 }
and the test is green again
I do the same thing for the
first_namevariable intest_person.py13 def test_takes_keyword_arguments(self): 14 first_name = random.choice(( 15 'jane', 'joe', 'john', 'person', 16 )) 17 last_name = random.choice(( 18 'doe', 'smith', 'blow', 'public', 19 )) 20 sex = random.choice(('F', 'M')) 21 year_of_birth = random.randint( 22 this_year()-120, this_year() 23 ) 24 25 ...
I hit save
ctrl+s(windows/linux) orcommand+s(mac) a few times to run the tests and the terminal shows green whenfirst_nameis'jane', and when it is not, the terminal shows AssertionErrorAssertionError: {'first_name': 'jane', 'last_name': X, 'sex': Y, 'age': Z} != {'first_name': 'joe', 'last_name': X, 'sex': Y, 'age': Z} AssertionError: {'first_name': 'jane', 'last_name': X, 'sex': Y, 'age': Z} != {'first_name': 'john', 'last_name': X, 'sex': Y, 'age': Z} AssertionError: {'first_name': 'jane', 'last_name': X, 'sex': Y, 'age': Z} != {'first_name': 'person', 'last_name': X, 'sex': Y, 'age': Z}
I add the
first_nameinput parameter in 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!
test_function_w_default_keyword_arguments¶
I want to see what would happen when I try to make a person without a value for the last_name argument
red: make it fail¶
I select
test_takes_keyword_arguments, then copy (ctrl+c(windows/linux) orcommand+c(mac)) and paste (ctrl+v(windows/linux) orcommand+v(mac)) it below intest_person.pyI change the name of the new test to
test_function_w_default_keyword_argumentsand comment out thelast_namevariable1import datetime 2import random 3import src.person 4import unittest 5 6 7def this_year(): 8 return datetime.datetime.now().year 9 10 11class TestPerson(unittest.TestCase): 12 13 def test_takes_keyword_arguments(self): 14 first_name = random.choice(( 15 'jane', 'joe', 'john', 'person', 16 )) 17 last_name = random.choice(( 18 'doe', 'smith', 'blow', 'public', 19 )) 20 sex = random.choice(('F', 'M')) 21 year_of_birth = random.randint( 22 this_year()-120, this_year() 23 ) 24 25 self.assertEqual( 26 src.person.factory( 27 first_name=first_name, 28 last_name=last_name, 29 sex=sex, 30 year_of_birth=year_of_birth, 31 ), 32 dict( 33 first_name=first_name, 34 last_name=last_name, 35 sex=sex, 36 age=this_year()-year_of_birth, 37 ) 38 ) 39 40 def test_function_w_default_keyword_arguments(self): 41 first_name = random.choice(( 42 'jane', 'joe', 'john', 'person', 43 )) 44 # last_name = random.choice(( 45 # 'doe', 'smith', 'blow', 'public', 46 # )) 47 sex = random.choice(('F', 'M')) 48 year_of_birth = random.randint( 49 this_year()-120, this_year() 50 ) 51 52 self.assertEqual( 53 src.person.factory( 54 first_name=first_name, 55 last_name=last_name, 56 sex=sex, 57 year_of_birth=year_of_birth, 58 ), 59 dict( 60 first_name=first_name, 61 last_name=last_name, 62 sex=sex, 63 age=this_year()-year_of_birth, 64 ) 65 )
the terminal shows NameError
NameError: name 'last_name' is not defined
green: make it pass¶
I comment out
last_namein the call to thefactoryfunction intest_function_w_default_keyword_arguments52 self.assertEqual( 53 src.person.factory( 54 first_name=first_name, 55 # last_name=last_name, 56 year_of_birth=year_of_birth, 57 sex=sex, 58 ), 59 dict( 60 first_name=first_name, 61 last_name=last_name, 62 sex=sex, 63 age=this_year() - year_of_birth, 64 ) 65 )
the terminal shows TypeError
TypeError: factory() missing 1 required positional argument: 'last_name'
the
factoryfunction is called with 3 arguments intest_function_w_default_keyword_argumentsbut the definition expects 4 inperson.pyI add a default value for
last_nameinperson.py4def factory( 5 first_name, last_name=None, 6 sex, year_of_birth, 7 ): 8 ...
the terminal shows SyntaxError
SyntaxError: parameter without a default follows parameter with a default
I add it to the list of Exceptions encountered in
test_person.py68# Exceptions Encountered 69# AssertionError 70# NameError 71# AttributeError 72# TypeError 73# SyntaxError
I add a default value for the
sexparameter in thefactoryfunction inperson.py4def factory( 5 first_name, last_name=None, 6 sex=None, year_of_birth, 7 ): 8 ...
the terminal shows SyntaxError
SyntaxError: parameter without a default follows parameter with a default
I give the
year_of_birthparameter a default value as well4def factory( 5 first_name, last_name=None, 6 sex=None, year_of_birth=None, 7 ): 8 ...
the terminal shows NameError
NameError: name 'last_name' is not defined
the value for the
last_namekey in the expected dictionary intest_function_w_default_keyword_argumentspoints to thelast_namevariable which I just commented outI change the expectation of
test_function_w_default_keyword_argumentsintest_person.py52 self.assertEqual( 53 src.person.factory( 54 first_name=first_name, 55 # last_name=last_name, 56 sex=sex, 57 year_of_birth=year_of_birth, 58 ), 59 dict( 60 first_name=first_name, 61 last_name='doe', 62 sex=sex, 63 age=this_year()-year_of_birth, 64 ) 65 )
the terminal shows AssertionError
AssertionError: {'first_name': X, 'last_name': None, 'sex': Y, 'age': Z} != {'first_name': X, 'last_name': 'doe', 'sex': Y, 'age': Z}
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 ): 8 ...
the test passes
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', )
I remove the commented lines from
test_function_w_default_keyword_argumentsintest_person.py40 def test_function_w_default_keyword_arguments(self): 41 first_name = random.choice(( 42 'jane', 'joe', 'john', 'person', 43 )) 44 sex = random.choice(('F', 'M')) 45 year_of_birth = random.randint( 46 this_year()-120, this_year() 47 ) 48 49 self.assertEqual( 50 src.person.factory( 51 first_name=first_name, 52 sex=sex, 53 year_of_birth=year_of_birth, 54 ), 55 dict( 56 first_name=first_name, 57 last_name='doe', 58 sex=sex, 59 age=this_year()-year_of_birth, 60 ) 61 )
I comment out the
sexvariable in the test to see what would happen if I do not know its value40 def test_function_w_default_keyword_arguments(self): 41 first_name = random.choice(( 42 'jane', 'joe', 'john', 'person', 43 )) 44 # sex = random.choice(('F', 'M')) 45 year_of_birth = random.randint( 46 this_year()-120, this_year() 47 )
the terminal shows NameError
NameError: name 'sex' is not defined
I comment out
sexin the call to thefactoryfunction in the assertion intest_person.py49 self.assertEqual( 50 src.person.factory( 51 first_name=first_name, 52 # sex=sex, 53 year_of_birth=year_of_birth, 54 ), 55 dict( 56 first_name=first_name, 57 last_name='doe', 58 sex=sex, 59 age=this_year()-year_of_birth, 60 ) 61 )
the terminal shows NameError
NameError: name 'sex' is not defined
the value in the expected dictionary still uses the
sexvariable I commented out. I change the expectation49 self.assertEqual( 50 src.person.factory( 51 first_name=first_name, 52 # sex=sex, 53 year_of_birth=year_of_birth, 54 ), 55 dict( 56 first_name=first_name, 57 last_name='doe', 58 sex='M', 59 age=this_year()-year_of_birth, 60 ) 61 )
the terminal shows AssertionError
AssertionError: {'first_name': X, 'last_name': 'doe', 'sex': None, 'age': Y} != {'first_name': X, 'last_name': 'doe', 'sex': 'M', 'age': Y}
the
factoryfunction returns a dictionary with None as the value forsexand the test expects'M'I add a default value for
sexinperson.py4def factory( 5 first_name, last_name='doe', 6 sex='M', year_of_birth=None 7 ): 8 ...
the test passes
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, )
I remove the commented lines from
test_person.py40 def test_function_w_default_keyword_arguments(self): 41 first_name = random.choice(( 42 'jane', 'joe', 'john', 'person', 43 )) 44 year_of_birth = random.randint( 45 this_year()-120, this_year() 46 ) 47 48 self.assertEqual( 49 src.person.factory( 50 first_name=first_name, 51 year_of_birth=year_of_birth, 52 ), 53 dict( 54 first_name=first_name, 55 last_name='doe', 56 sex='M', 57 age=this_year()-year_of_birth, 58 ) 59 )
the terminal still shows green
refactor: make it better¶
first_nameandyear_of_birthare made the same way in both tests, I can remove this repetition by adding attributes to theTestPersonclassNote
the …(ellipsis) represents code that does not need to change in this part
1import datetime 2import random 3import src.person 4import unittest 5 6 7def this_year(): 8 return datetime.datetime.now().year 9 10 11class TestPerson(unittest.TestCase): 12 13 first_name = random.choice(( 14 'jane', 'joe', 'john', 'person', 15 )) 16 year_of_birth = random.randint( 17 this_year()-120, this_year() 18 ) 19 20 def test_takes_keyword_arguments(self): 21 ...
I can use them in
test_takes_keyword_argumentswithself.the same way I use theassertmethods since they now belong to theTestPersonclass20 def test_takes_keyword_arguments(self): 21 # first_name = random.choice(( 22 # 'jane', 'joe', 'john', 'person', 23 # )) 24 first_name = self.first_name 25 last_name = random.choice(( 26 'doe', 'smith', 'blow', 'public', 27 )) 28 sex = random.choice(('F', 'M')) 29 # year_of_birth = random.randint( 30 # this_year()-120, this_year() 31 # ) 32 year_of_birth = self.year_of_birth 33 34 self.assertEqual( 35 ...
the test is still green. I do the same thing in
test_function_w_default_keyword_arguments49 def test_function_w_default_keyword_arguments(self): 50 # first_name = random.choice(( 51 # 'jane', 'joe', 'john', 'person', 52 # )) 53 first_name = self.first_name 54 # year_of_birth = random.randint( 55 # this_year()-120, this_year() 56 # ) 57 year_of_birth = self.year_of_birth 58 59 self.assertEqual( 60 ...
the terminal still shows green
I remove the commented lines from
test_takes_keyword_arguments20 def test_takes_keyword_arguments(self): 21 first_name = self.first_name 22 last_name = random.choice(( 23 'doe', 'smith', 'blow', 'public', 24 )) 25 sex = random.choice(('F', 'M')) 26 year_of_birth = self.year_of_birth 27 28 self.assertEqual( 29 ...
and remove the commented lines from
test_function_w_default_keyword_arguments43 def test_function_w_default_keyword_arguments(self): 44 first_name = self.first_name 45 year_of_birth = self.year_of_birth 46 47 self.assertEqual( 48 ...
still green
since the variables point to class attributes, I can use them directly and comment out
first_nameandyear_of_birthintest_takes_keyword_arguments20 def test_takes_keyword_arguments(self): 21 # first_name = self.first_name 22 last_name = random.choice(( 23 'doe', 'smith', 'blow', 'public', 24 )) 25 sex = random.choice(('F', 'M')) 26 # year_of_birth = self.year_of_birth 27 28 self.assertEqual( 29 src.person.factory( 30 first_name=self.first_name, 31 last_name=last_name, 32 sex=sex, 33 year_of_birth=self.year_of_birth, 34 ), 35 dict( 36 first_name=self.first_name, 37 last_name=last_name, 38 sex=sex, 39 age=this_year()-self.year_of_birth, 40 ) 41 )
the test is still green. I make the same change in
test_function_w_default_keyword_arguments43 def test_function_w_default_keyword_arguments(self): 44 # first_name = self.first_name 45 # year_of_birth = self.year_of_birth 46 47 self.assertEqual( 48 src.person.factory( 49 first_name=self.first_name, 50 year_of_birth=self.year_of_birth, 51 ), 52 dict( 53 first_name=self.first_name, 54 last_name='doe', 55 sex='M', 56 age=this_year()-self.year_of_birth, 57 ) 58 )
all tests are still passing
I remove the commented lines from
test_takes_keyword_arguments20 def test_takes_keyword_arguments(self): 21 last_name = random.choice(( 22 'doe', 'smith', 'blow', 'public', 23 )) 24 sex = random.choice(('F', 'M')) 25 26 self.assertEqual( 27 ...
and in
test_function_w_default_keyword_arguments41 def test_function_w_default_keyword_arguments(self): 42 self.assertEqual( 43 src.person.factory( 44 first_name=self.first_name, 45 year_of_birth=self.year_of_birth 46 ), 47 dict( 48 first_name=self.first_name, 49 last_name='doe', 50 sex='M', 51 age=this_year()-self.year_of_birth, 52 ) 53 )
the tests are still passing
both tests have the same random values for
first_nameandyear_of_birth, they were not always the same before the change. I add the unittest.TestCase.setUp method to make sure they both get new random values before each test runs13class TestPerson(unittest.TestCase): 14 15 def setUp(self): 16 first_name = random.choice(( 17 'jane', 'joe', 'john', 'person', 18 )) 19 year_of_birth = random.randint( 20 this_year()-120, this_year() 21 ) 22 23 def test_takes_keyword_arguments(self): 24 ...
the terminal shows AttributeError
AttributeError: 'TestPerson' object has no attribute 'first_name'
because there is no longer a class attribute named
first_name, it is now a variable in the unittest.TestCase.setUp method and the other methods cannot reach itI add
self.to make it a class attribute13 def setUp(self): 14 self.first_name = random.choice(( 15 'jane', 'joe', 'john', 'person', 16 )) 17 year_of_birth = random.randint( 18 this_year()-120, this_year() 19 ) 20 21 def test_takes_keyword_arguments(self): 22 ...
the terminal shows AttributeError
AttributeError: 'TestPerson' object has no attribute 'year_of_birth'
same problem, same solution
13 def setUp(self): 14 self.first_name = random.choice(( 15 'jane', 'joe', 'john', 'person', 16 )) 17 self.year_of_birth = random.randint( 18 this_year()-120, this_year() 19 ) 20 21 def test_takes_keyword_arguments(self): 22 ...
and both tests are green again!
self.first_nameandself.year_of_birthare given random values before the first test, then given random values again before the second test. That was a lot, but we got through it.
test_person_tests¶
red: make it fail¶
I close
test_person.pyI want to write the solution without looking at the tests and delete all the text in
person.py. The terminal shows AttributeErrorAttributeError: module 'src.person' has no attribute 'factory'
green: make it pass¶
I add the name
1factory
the terminal shows NameError
NameError: name 'factory' is not defined
I point it to None
1factory = None
the terminal shows TypeError
TypeError: 'NoneType' object is not callable
when I make it a function
1def factory(): 2 return None
the terminal shows TypeError
TypeError: factory() got an unexpected keyword argument 'first_name'
I add the keyword argument to the function definition
1def factory(first_name): 2 return None
the terminal shows TypeError
TypeError: factory() got an unexpected keyword argument 'last_name'
I add the keyword argument
1def factory(first_name, last_name): 2 return None
the terminal shows TypeError
TypeError: factory() got an unexpected keyword argument 'sex'
when I add the keyword argument
1def factory( 2 first_name, last_name, 3 sex 4 ): 5 return None
the terminal shows TypeError
TypeError: factory() got an unexpected keyword argument 'year_of_birth'
I add the missing keyword argument
1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return None
the terminal shows AssertionError
AssertionError: None != {'first_name': X, 'last_name': Y, 'sex': Z, 'age': A}
I change the return statement
1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': 'john', 7 'last_name': 'blow', 8 'sex': 'F', 9 'age': 20, 10 }
the terminal shows AssertionError
AssertionError: {'first_name': 'john', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'jane', 'last_name': 'smith', 'sex': 'M', 'age': 50} AssertionError: {'first_name': 'john', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'jane', 'last_name': 'doe', 'sex': 'M', 'age': 73} AssertionError: {'first_name': 'john', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'john', 'last_name': 'smith', 'sex': 'M', 'age': 77} AssertionError: {'first_name': 'john', 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': 'jane', 'last_name': 'public', 'sex': 'M', 'age': 98}
the values of
first_name,last_name,sexandagechange every time the test runsI make the dictionary in the return statement use the
first_nameinput parameter instead of a value that does not change1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': first_name, 7 'last_name': 'blow', 8 'sex': 'F', 9 'age': 20, 10 }
the terminal shows AssertionError
AssertionError: {'first_name': X, 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': X, 'last_name': 'public', 'sex': 'M', 'age': 69} AssertionError: {'first_name': X, 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': X, 'last_name': 'blow', 'sex': 'M', 'age': 97} AssertionError: {'first_name': X, 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': X, 'last_name': 'smith', 'sex': 'M', 'age': 74} AssertionError: {'first_name': X, 'last_name': 'blow', 'sex': 'F', 'age': 20} != {'first_name': X, 'last_name': 'public', 'sex': 'M', 'age': 19}
the
first_namenow matches, and the values oflast_name,sexandagechange every time the test runsI use the
last_nameinput parameter in the return statement1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': first_name, 7 'last_name': last_name, 8 'sex': 'F', 9 'age': 20, 10 }
the terminal shows AssertionError
AssertionError: {'first_name': X, 'last_name': Y, 'sex': 'F', 'age': 20} != {'first_name': X, 'last_name': Y, 'sex': 'M', 'age': 3} AssertionError: {'first_name': X, 'last_name': Y, 'sex': 'F', 'age': 20} != {'first_name': X, 'last_name': Y, 'sex': 'M', 'age': 118} AssertionError: {'first_name': X, 'last_name': Y, 'sex': 'F', 'age': 20} != {'first_name': X, 'last_name': Y, 'sex': 'M', 'age': 19} AssertionError: {'first_name': X, 'last_name': Y, 'sex': 'F', 'age': 20} != {'first_name': X, 'last_name': Y, 'sex': 'M', 'age': 95}
the values for
first_name, andlast_namenow match, the values ofsexandagechange every time the test runsWhen I add the
sexinput parameter to the return statement1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': first_name, 7 'last_name': last_name, 8 'sex': sex, 9 'age': 20, 10 }
the terminal shows AssertionError
AssertionError: {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 20} != {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 1} AssertionError: {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 20} != {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 90} AssertionError: {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 20} != {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 113} AssertionError: {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 20} != {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 58}
the
first_name,last_nameandsexmatch, the value ofagechanges every time the test runsI add the
year_of_birthinput parameter to the return statement1def factory( 2 first_name, last_name, 3 sex, year_of_birth, 4 ): 5 return { 6 'first_name': first_name, 7 'last_name': last_name, 8 'sex': sex, 9 'age': year_of_birth, 10 }
the terminal shows AssertionError
AssertionError: {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 2022} != {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 2} AssertionError: {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 2024} != {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 0} AssertionError: {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 1981} != {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 43} AssertionError: {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 1969} != {'first_name': X, 'last_name': Y, 'sex': Z, 'age': 55}
the test expects the difference between the current year and the
year_of_birthto get theageI add an import statement at the top of the file
1import datetime 2 3 4def factory( 5 ...
then I use it to get the current year for the age calculation
4def 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 terminal shows TypeError
TypeError: factory() missing 2 required positional arguments: 'last_name' and 'sex'
I add a default value for
last_name4def factory( 5 first_name, last_name=None, 6 sex, year_of_birth, 7 ): 8 ...
the terminal shows SyntaxError
SyntaxError: parameter without a default follows parameter with a default
I add a default value for
sex4def factory( 5 first_name, last_name=None, 6 sex=None, year_of_birth, 7 ): 8 ...
the terminal shows SyntaxError
SyntaxError: parameter without a default follows parameter with a default
I 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
AssertionError: {'first_name': X, 'last_name': None, 'sex': None, 'age': Y} != {'first_name': X, 'last_name': 'doe', 'sex': 'M', 'age': Y}
the values for
last_nameandsexdo not match the expectationI change the default value for
last_nameto match4def factory( 5 first_name, last_name='doe', 6 sex=None, year_of_birth=None, 7 ): 8 ...
the terminal shows AssertionError
AssertionError: {'first_name': X, 'last_name': Y, 'sex': None, 'age': Z} != {'first_name': Z, 'last_name': Y, 'sex': 'M', 'age': Z}
when I make the default value of the
sexargument match the expectation1import datetime 2 3 4def factory( 5 first_name, last_name='doe', 6 sex='M', year_of_birth=None 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 }
both tests pass! I think I am pretty good at this
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 ran into the following Exceptions
Would you like to know how to test that an Exception is raised?