classes
I made functions that make dictionaries and strings in how to make a person. I can also do the same thing with a class since it is a group of attributes (variables) and methods (functions) that belong together.
preview
I have these tests by the end of the chapter
1import datetime
2import random
3import src.person
4import unittest
5
6
7def pick_one(*choices):
8 return random.choice(choices)
9
10
11def get_random_name():
12 return pick_one(
13 'jane', 'joe', 'john', 'person',
14 'doe', 'smith', 'blow', 'public',
15 )
16
17
18class TestPerson(unittest.TestCase):
19
20 def setUp(self):
21 self.random_first_name = get_random_name()
22 self.random_last_name = get_random_name()
23 this_year = datetime.datetime.now().year
24 self.random_year_of_birth = random.randint(
25 this_year-120, this_year
26 )
27 self.age = this_year - self.random_year_of_birth
28
29 def test_factory_w_keyword_arguments(self):
30 a_person = dict(
31 first_name=self.random_first_name,
32 last_name=self.random_last_name,
33 sex=pick_one('F', 'M'),
34 )
35
36 reality = src.person.factory(
37 **a_person,
38 year_of_birth=self.random_year_of_birth,
39 )
40 my_expectation = dict(
41 **a_person,
42 age=self.age,
43 )
44 self.assertEqual(reality, my_expectation)
45
46 def test_factory_w_optional_arguments(self):
47 reality = src.person.factory(
48 first_name=self.random_first_name,
49 year_of_birth=self.random_year_of_birth,
50 )
51 my_expectation = dict(
52 first_name=self.random_first_name,
53 last_name='doe',
54 sex='M',
55 age=self.age,
56 )
57 self.assertEqual(reality, my_expectation)
58
59 def test_factory_person_says_hello(self):
60 a_random_person = src.person.factory(
61 first_name=self.random_first_name,
62 last_name=self.random_last_name,
63 year_of_birth=self.random_year_of_birth,
64 )
65
66 reality = src.person.say_hello(a_random_person)
67 my_expectation = (
68 f'Hi, my name is {self.random_first_name}'
69 f' {self.random_last_name}'
70 f' and I am {self.age}'
71 )
72 self.assertEqual(reality, my_expectation)
73
74 def test_classy_person_says_hello(self):
75 a_random_person = src.person.Person(
76 first_name=self.random_first_name,
77 last_name=self.random_last_name,
78 year_of_birth=self.random_year_of_birth,
79 )
80
81 reality = a_random_person.say_hello()
82 my_expectation = (
83 f'Hi, my name is {self.random_first_name}'
84 f' {self.random_last_name}'
85 f' and I am {self.age}'
86 )
87 self.assertEqual(reality, my_expectation)
88
89 def test_attributes_and_methods_of_person_class(self):
90 reality = dir(src.person.Person)
91 my_expectation = [
92 '__class__',
93 '__delattr__',
94 '__dict__',
95 '__dir__',
96 '__doc__',
97 '__eq__',
98 '__firstlineno__',
99 '__format__',
100 '__ge__',
101 '__getattribute__',
102 '__getstate__',
103 '__gt__',
104 '__hash__',
105 '__init__',
106 '__init_subclass__',
107 '__le__',
108 '__lt__',
109 '__module__',
110 '__ne__',
111 '__new__',
112 '__reduce__',
113 '__reduce_ex__',
114 '__repr__',
115 '__setattr__',
116 '__sizeof__',
117 '__static_attributes__',
118 '__str__',
119 '__subclasshook__',
120 '__weakref__',
121 'say_hello',
122 ]
123 self.assertEqual(reality, my_expectation)
124
125 def test_attributes_and_methods_of_person_instance(self):
126 an_instance_of_person = src.person.Person(
127 first_name=self.random_first_name,
128 last_name=self.random_last_name,
129 year_of_birth=self.random_year_of_birth,
130 sex=pick_one('F', 'M')
131 )
132
133 reality = dir(an_instance_of_person)
134 my_expectation = [
135 '__class__',
136 '__delattr__',
137 '__dict__',
138 '__dir__',
139 '__doc__',
140 '__eq__',
141 '__firstlineno__',
142 '__format__',
143 '__ge__',
144 '__getattribute__',
145 '__getstate__',
146 '__gt__',
147 '__hash__',
148 '__init__',
149 '__init_subclass__',
150 '__le__',
151 '__lt__',
152 '__module__',
153 '__ne__',
154 '__new__',
155 '__reduce__',
156 '__reduce_ex__',
157 '__repr__',
158 '__setattr__',
159 '__sizeof__',
160 '__static_attributes__',
161 '__str__',
162 '__subclasshook__',
163 '__weakref__',
164 'first_name',
165 'last_name',
166 'say_hello',
167 'sex',
168 'year_of_birth',
169 ]
170 self.assertEqual(reality, my_expectation)
171
172
173# Exceptions seen
174# AssertionError
175# NameError
176# AttributeError
177# TypeError
178# SyntaxError
questions about classes
Questions to think about as I go through the chapter
requirements
open the project
I change directory to the
personfoldercd personthe terminal shows I am in the
personfolder.../pumping_python/personI use
pytest-watcherto run the testsuv run pytest-watcher . --nowthe terminal is my friend, and shows
rootdir: .../pumping_python/person configfile: pyproject.toml collected 3 items tests/test_person.py ... [100%] ==================== 3 passed in X.YZs =====================I hold ctrl on the keyboard, then click on
tests/test_person.pyto open it
test_classy_person_says_hello
I made a person say hello with a function, I can also do the same thing with a class because it is attributes and methods that belong together.
RED: make it fail
I add a new test to test_person.py
69 def test_factory_person_says_hello(self):
70 first_name = get_random_name()
71 last_name = get_random_name()
72 sex = pick_one('F', 'M')
73
74 year_of_birth = get_random_year_of_birth()
75 age = calculate_age(year_of_birth)
76
77 a_random_person = src.person.factory(
78 first_name=first_name,
79 last_name=last_name,
80 sex=sex,
81 year_of_birth=year_of_birth,
82 )
83
84 reality = src.person.say_hello(a_random_person)
85 my_expectation = (
86 f'Hi, my name is {first_name} {last_name}'
87 f' and I am {age}'
88 )
89 self.assertEqual(reality, my_expectation)
90
91 def test_classy_person_says_hello(self):
92 joe = src.person.Person(
93 first_name='joe',
94 last_name='blow',
95 year_of_birth=1996,
96 )
97
98 reality = src.person.say_hello(joe)
99 my_expectation = None
100 self.assertEqual(reality, my_expectation)
101
102
103# Exceptions seen
the terminal is my friend, and shows AttributeError
AttributeError: module 'src.person' has no attribute 'Person'
because there is no definition for Person in person.py in the src folder
GREEN: make it pass
I add a class to
person.py12def factory( 13 first_name, year_of_birth, 14 last_name='doe', sex='M', 15 ): 16 return { 17 'first_name': first_name, 18 'last_name': last_name, 19 'sex': sex, 20 'age': ( 21 datetime.datetime.today().year 22 - year_of_birth 23 ), 24 } 25 26 27class Person: 28 29 passI add a constructor method to the
Personclass so it can take arguments, it is used to define how copies of the class are made27class Person: 28 29 # pass 30 def __init__(): 31 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'first_name'because the definition for
__init__does not allow calling it with inputs (the parentheses are empty) and the test sends'first_name'as input.a constructor method is used to make copies of a class
I add the name in parentheses so that the
__init__constructor method can take input27class Person: 28 29 # pass 30 # def __init__(): 31 def __init__(first_name): 32 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.__init__() got multiple values for argument 'first_name'because the
__init__constructor method takes the instance it belongs to as the first argumentI add
selfas the first argument the way I do with all the test methods in the book27class Person: 28 29 # pass 30 # def __init__(): 31 # def __init__(first_name): 32 def __init__(self, first_name): 33 return Noneselfis Python convention, I can use any name I wantthe terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?I have seen this before, so far it is the same as making the factory function
I add
last_nameto the definition of__init__27class Person: 28 29 # pass 30 # def __init__(): 31 # def __init__(first_name): 32 # def __init__(self, first_name): 33 def __init__(self, first_name, last_name): 34 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'year_of_birth'still the same as making the factory function
I add
year_of_birthto the definition of the__init__method27class Person: 28 29 # pass 30 # def __init__(): 31 # def __init__(first_name): 32 # def __init__(self, first_name): 33 # def __init__(self, first_name, last_name): 34 def __init__( 35 self, first_name, last_name, 36 year_of_birth, 37 ): 38 return Nonethe terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'get'because
the test calls the
say_hellofunctionthe
say_hellofunction expects a dictionarythe
say_hellofunction calls the get method on what it receives andthe
Personobject it receives is not a dictionary and does not have a get method
I change
realityin test_classy_person_says_hello to use a method I can add toPerson, intest_person.py91 def test_classy_person_says_hello(self): 92 joe = src.person.Person( 93 first_name='joe', 94 last_name='blow', 95 year_of_birth=1996, 96 ) 97 98 # reality = src.person.say_hello(joe) 99 reality = src.person.Person.say_hello(joe) 100 my_expectation = None 101 self.assertEqual(reality, my_expectation) 102 103 104# Exceptions seenthe terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'say_hello'because the test calls the
say_hellofunction which does not yet exist in thePersonclassI add a method definition for it to the
Personclass inperson.py27class Person: 28 29 # pass 30 # def __init__(): 31 # def __init__(first_name): 32 # def __init__(self, first_name): 33 # def __init__(self, first_name, last_name): 34 def __init__( 35 self, first_name, last_name, 36 year_of_birth, 37 ): 38 return None 39 40 def say_hello(): 41 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.say_hello() takes 0 positional arguments but 2 were givenbecause the definition for
say_hellodoes not allow inputs and the test called the method with one positional argument (person). Why did the error say two were given when the test only sends one?I add
personto the method definition27class Person: 28 29 # pass 30 # def __init__(): 31 # def __init__(first_name): 32 # def __init__(self, first_name): 33 # def __init__(self, first_name, last_name): 34 def __init__( 35 self, first_name, last_name, 36 year_of_birth, 37 ): 38 return None 39 40 # def say_hello(): 41 def say_hello(person): 42 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.say_hello() takes 1 positional argument but 2 were givenbecause methods take the copy of the class (
self) they belong to as the first argument.
what is the staticmethod decorator?
I can use the staticmethod decorator if I do not want to add
selfto the method definition when it does not use anything in the class that way I am not sending more information than what the method needs. I add@staticmethodtosay_hello27class Person: 28 29 # pass 30 # def __init__(): 31 # def __init__(first_name): 32 # def __init__(self, first_name): 33 # def __init__(self, first_name, last_name): 34 def __init__( 35 self, first_name, last_name, 36 year_of_birth, 37 ): 38 return None 39 40 # def say_hello(): 41 @staticmethod 42 def say_hello(person): 43 return Nonethe test passes. I can call methods from outside the class they belong to.
I made a copy of the
Personclass namedjoeI called the say_hello method of the
Personclass withjoe(which is a copy of thePersonclass) as input. Confused? It is confusing and there is a better way.
REFACTOR: make it better
I want the say_hello method of the Person class to return a string for the person it receives, the same way the say_hello function returns a string for the person (dictionary) it receives as input
I change
my_expectationto an f-string in test_classy_person_says_hello intest_person.py91 def test_classy_person_says_hello(self): 92 joe = src.person.Person( 93 first_name='joe', 94 last_name='blow', 95 year_of_birth=1996, 96 ) 97 98 # reality = src.person.say_hello(joe) 99 reality = src.person.Person.say_hello(joe) 100 # my_expectation = None 101 my_expectation = ( 102 'Hi, my name is joe blow and I am' 103 f' {calculate_age(1996)}' 104 ) 105 self.assertEqual(reality, my_expectation) 106 107 108# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: None != 'Hi, my name is joe blow and I am 30'I copy the value from the terminal and paste it in the return statement for the say_hello method of the
Personclass inperson.py40 # def say_hello(): 41 @staticmethod 42 def say_hello(person): 43 # return None 44 return 'Hi, my name is joe blow and I am 30'the test passes.
I add an assertion for the next person to test_classy_person_says_hello in
test_person.py98 # reality = src.person.say_hello(joe) 99 reality = src.person.Person.say_hello(joe) 100 # my_expectation = None 101 my_expectation = ( 102 'Hi, my name is joe blow and I am' 103 f' {calculate_age(1996)}' 104 ) 105 self.assertEqual(reality, my_expectation) 106 107 jane = src.person.Person( 108 first_name='jane', 109 sex='F', 110 year_of_birth=1991, 111 ) 112 113 reality = src.person.Person.say_hello(jane) 114 my_expectation = ( 115 'Hi, my name is jane doe and I am' 116 f' {calculate_age(1991)}' 117 ) 118 self.assertEqual(reality, my_expectation) 119 120 121# Exceptions seenthe terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'sex'because the
__init__method definition does not have a parameter namedsexI add
sexto the definition of the__init__method27class Person: 28 29 # pass 30 # def __init__(): 31 # def __init__(first_name): 32 # def __init__(self, first_name): 33 # def __init__(self, first_name, last_name): 34 def __init__( 35 self, first_name, last_name, 36 # year_of_birth, 37 year_of_birth, sex, 38 ): 39 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.__init__() missing 1 required positional argument: 'sex'because when the test calls the
Personobject to makejoeit does not provide a value forsexwhich I just made a required argument when I added it to the__init__method definition, I have to make it a choiceI add a default value for
sexto make it optional34 def __init__( 35 self, first_name, last_name, 36 # year_of_birth, 37 # year_of_birth, sex, 38 year_of_birth, sex=None, 39 ): 40 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.__init__() missing 1 required positional argument: 'last_name'because when the test calls the
Personobject to makejaneit does not provide a value forlast_namewhich is a required argument, I have to make it a choice as wellI add a default value for
last_nameto make it optional34 def __init__( 35 # self, first_name, last_name, 36 self, first_name, last_name=None, 37 # year_of_birth, 38 # year_of_birth, sex, 39 year_of_birth, sex=None, 40 ): 41 return Nonethe terminal is my friend, and shows SyntaxError
SyntaxError: parameter without a default follows parameter with a defaultbecause parameters without default values must come before parameters with default values
I add a default value for
year_of_birthto make it optional34 def __init__( 35 # self, first_name, last_name, 36 self, first_name, last_name=None, 37 # year_of_birth, 38 # year_of_birth, sex, 39 # year_of_birth, sex=None, 40 year_of_birth=None, sex=None, 41 ): 42 return Nonethe terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is joe blow and I am 30' != 'Hi, my name is jane doe and I am 35'Progress. I can make the
say_hellofunction use attributes of the person it receives as input to make the message.I change the string in the return statement of the
say_hellomethod of thePersonclass to an f-string with thefirst_nameattribute of the person it receives, inperson.py44 # def say_hello(): 45 @staticmethod 46 def say_hello(person): 47 # return None 48 # return 'Hi, my name is joe blow and I am 30' 49 return ( 50 f'Hi, my name is {person.first_name} blow' 51 ' and I am 30' 52 )the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'first_name'because there is no definition for
first_namein thePersonclass definitionI add an attribute to the
Personclass forfirst_name27class Person: 28 29 first_name = 'jane' 30 31 # passthe terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is jane blow and I am 30' != 'Hi, my name is joe blow and I am 30'because I used a fixed value (
jane) and the first assertion of the test expectsjoe. I have to get the value from the object that is passed to thesay_hellomethod.I add a variable to the
__init__method to use it to allow changing thefirst_nameattribute anytime a copy of thePersonclass is made27class Person: 28 29 first_name = 'jane' 30 31 # pass 32 # def __init__(): 33 # def __init__(first_name): 34 # def __init__(self, first_name): 35 # def __init__(self, first_name, last_name): 36 def __init__( 37 # self, first_name, last_name, 38 self, first_name, last_name=None, 39 # year_of_birth, 40 # year_of_birth, sex, 41 # year_of_birth, sex=None, 42 year_of_birth=None, sex=None, 43 ): 44 first_name = first_name 45 return Nonethe terminal still shows AssertionError
AssertionError: 'Hi, my name is jane blow and I am 30' != 'Hi, my name is joe blow and I am 30'I change the variable to a class attribute by adding
self.before it36 def __init__( 37 # self, first_name, last_name, 38 self, first_name, last_name=None, 39 # year_of_birth, 40 # year_of_birth, sex, 41 # year_of_birth, sex=None, 42 year_of_birth=None, sex=None, 43 ): 44 # first_name = first_name 45 self.first_name = first_name 46 return Nonethe terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is jane blow and I am 30' != 'Hi, my name is jane doe and I am 35'the first names are the same, the last names and ages are different
I add the
last_nameattribute to the string in the return statement of thesay_hellomethod48 # def say_hello(): 49 @staticmethod 50 def say_hello(person): 51 # return None 52 # return 'Hi, my name is joe blow and I am 30' 53 return ( 54 # f'Hi, my name is {person.first_name} blow' 55 f'Hi, my name is {person.first_name}' 56 f' {person.last_name}' 57 ' and I am 30' 58 )the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'last_name'. Did you mean: 'first_name'?because there is no definition for
last_namein thePersonclass definitionI add an attribute to the
Personclass forlast_name27class Person: 28 29 first_name = 'jane' 30 last_name = 'doe' 31 32 # passthe terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is joe doe and I am 30' != 'Hi, my name is joe blow and I am 30'because I used a fixed value (
doe) and the first assertion of the test expectsblow. I have to get the value from the object that is passed to thesay_hellomethod.I add a variable to the
__init__method to use it to allow changing thelast_nameattribute anytime a copy of thePersonclass is made32 # pass 33 # def __init__(): 34 # def __init__(first_name): 35 # def __init__(self, first_name): 36 # def __init__(self, first_name, last_name): 37 def __init__( 38 # self, first_name, last_name, 39 self, first_name, last_name=None, 40 # year_of_birth, 41 # year_of_birth, sex, 42 # year_of_birth, sex=None, 43 year_of_birth=None, sex=None, 44 ): 45 # first_name = first_name 46 self.first_name = first_name 47 last_name = last_name 48 return Nonethe terminal still shows AssertionError
AssertionError: 'Hi, my name is joe doe and I am 30' != 'Hi, my name is joe blow and I am 30'I change
last_nameto a class attribute in the__init__method by addingself.before it37 def __init__( 38 # self, first_name, last_name, 39 self, first_name, last_name=None, 40 # year_of_birth, 41 # year_of_birth, sex, 42 # year_of_birth, sex=None, 43 year_of_birth=None, sex=None, 44 ): 45 # first_name = first_name 46 self.first_name = first_name 47 # last_name = last_name 48 self.last_name = last_name 49 return Nonethe terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is jane None and I am 30' != 'Hi, my name is jane doe and I am 35'the first names are the same and last names and ages are different
the
__init__method used None for the value ofself.last_namebecause the default value for thelast_nameparameter of the method is None. This means thatsrc.person.Person( first_name='jane', sex='F', year_of_birth=1991, )is the same as
src.person.Person.__init__( first_name='jane', sex='F', year_of_birth=1991, last_name=None, )because a method uses the default value for a parameter when it is called without the parameter.
I change the default value for
last_namein the__init__method to'doe'to give the test what it wants37 def __init__( 38 # self, first_name, last_name, 39 # self, first_name, last_name=None, 40 self, first_name, last_name='doe', 41 # year_of_birth, 42 # year_of_birth, sex, 43 # year_of_birth, sex=None, 44 year_of_birth=None, sex=None, 45 ):the terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is jane doe and I am 30' != 'Hi, my name is jane doe and I am 35'the age is the only thing that is different
I add a calculation for the age with the
year_of_birthattribute to the return statement of thesay_hellomethod52 # def say_hello(): 53 @staticmethod 54 def say_hello(person): 55 age = ( 56 datetime.datetime.today().year 57 - person.year_of_birth 58 ) 59 # return None 60 # return 'Hi, my name is joe blow and I am 30' 61 return ( 62 # f'Hi, my name is {person.first_name} blow' 63 f'Hi, my name is {person.first_name}' 64 f' {person.last_name}' 65 # f' and I am 30' 66 f' and I am {age}' 67 )the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'year_of_birth'because there is no definition for
year_of_birthin thePersonclass definitionI add an attribute to the
Personclass foryear_of_birth27class Person: 28 29 first_name = 'jane' 30 last_name = 'doe' 31 year_of_birth = 1991 32 33 # passthe terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is joe blow and I am 35' != 'Hi, my name is joe blow and I am 30'because I used a fixed value (
1991) and the first assertion of the test expectsdatetime.datetime.now().year-1996. I have to get the value from the object that is passed to thesay_hellomethod.I add a variable to the
__init__method to use it to allow changing theyear_of_birthattribute anytime a copy of thePersonclass is made38 def __init__( 39 # self, first_name, last_name, 40 # self, first_name, last_name=None, 41 self, first_name, last_name='doe', 42 # year_of_birth, 43 # year_of_birth, sex, 44 # year_of_birth, sex=None, 45 year_of_birth=None, sex=None, 46 ): 47 # first_name = first_name 48 self.first_name = first_name 49 # last_name = last_name 50 self.last_name = last_name 51 year_of_birth = year_of_birth 52 return Nonethe terminal still shows AssertionError
AssertionError: 'Hi, my name is joe blow and I am 35' != 'Hi, my name is joe blow and I am 30'I change
year_of_birthto a class attribute in the__init__method by addingself.before it38 def __init__( 39 # self, first_name, last_name, 40 # self, first_name, last_name=None, 41 self, first_name, last_name='doe', 42 # year_of_birth, 43 # year_of_birth, sex, 44 # year_of_birth, sex=None, 45 year_of_birth=None, sex=None, 46 ): 47 # first_name = first_name 48 self.first_name = first_name 49 # last_name = last_name 50 self.last_name = last_name 51 # year_of_birth = year_of_birth 52 self.year_of_birth = year_of_birth 53 return Nonethe test passes. What a beautiful life.
self.first_name,self.last_nameandself.year_of_birthare now defined twice in the class. I remove the first definition since the attributes are also made in the__init__method and that gets called when copies of thePersonclass are made, no need to have a default person bejane doeborn in199127class Person: 28 29 # first_name = 'jane' 30 # last_name = 'doe' 31 # year_of_birth = 1991 32 33 # passthe test is still green.
datetime.datetime.today().yeargets used to calculate the age in the say_hello method of thePersonclass and the return statement of the factory function. I make a helper function to calculate the age, the same way I do in the tests1import datetime 2 3 4def calculate_age(year_of_birth): 5 return ( 6 datetime.datetime.today().year 7 - year_of_birth 8 ) 9 10 11def say_hello(a_dictionary):I use the new function for the age calculation in the factory function
19def factory( 20 first_name, year_of_birth, 21 last_name='doe', sex='M', 22 ): 23 return { 24 'first_name': first_name, 25 'last_name': last_name, 26 'sex': sex, 27 # 'age': ( 28 # datetime.datetime.today().year 29 # - year_of_birth 30 # ), 31 'age': calculate_age(year_of_birth), 32 } 33 34 35class Person:still green.
I use the new function for the age calculation in the say_hello method of the
Personclass63 # def say_hello(): 64 @staticmethod 65 def say_hello(person): 66 # age = ( 67 # datetime.datetime.today().year 68 # - person.year_of_birth 69 # ) 70 age = calculate_age(person.year_of_birth) 71 # return None 72 # return 'Hi, my name is joe blow and I am 30' 73 return ( 74 # f'Hi, my name is {person.first_name} blow' 75 f'Hi, my name is {person.first_name}' 76 f' {person.last_name}' 77 # f' and I am 30' 78 f' and I am {age}' 79 )green.
The say_hello method is in the
Personclass, there is no need for it to take a copy of thePersonclass as input since it should be able to access the attributes of the class it belongs to. I changeperson.toself.to use class attributes instead63 # def say_hello(): 64 @staticmethod 65 def say_hello(person): 66 # age = ( 67 # datetime.datetime.today().year 68 # - person.year_of_birth 69 # ) 70 # age = calculate_age(person.year_of_birth) 71 age = calculate_age(self.year_of_birth) 72 # return None 73 # return 'Hi, my name is joe blow and I am 30' 74 return ( 75 # f'Hi, my name is {person.first_name} blow' 76 # f'Hi, my name is {person.first_name}' 77 # f' {person.last_name}' 78 # f' and I am 30' 79 f'Hi, my name is {self.first_name}' 80 f' {self.last_name}' 81 f' and I am {age}' 82 )the terminal is my friend, and shows NameError
NameError: name 'self' is not definedI change the name of the input parameter from
persontoself63 # def say_hello(): 64 @staticmethod 65 # def say_hello(person): 66 def say_hello(self):the test is green again.
I remove the staticmethod decorator because I no longer need it since the say_hello method is using class attributes
63 # def say_hello(): 64 # @staticmethod 65 # def say_hello(person): 66 def say_hello(self):the test is still green.
I change the call to
src.person.say_hello(joe)forjoebecause I can call methods directly from a copy of a class, in test_classy_person_says_hello intest_person.py98 # reality = src.person.say_hello(joe) 99 # reality = src.person.Person.say_hello(joe) 100 reality = joe.say_hello() 101 # my_expectation = None 102 my_expectation = ( 103 'Hi, my name is joe blow and I am' 104 f' {calculate_age(1996)}' 105 ) 106 self.assertEqual(reality, my_expectation) 107 108 jane = src.person.Person( 109 first_name='jane', 110 sex='F', 111 year_of_birth=1991, 112 )still green.
I change the call to
src.person.say_hello(joe)forjaneas well108 jane = src.person.Person( 109 first_name='jane', 110 sex='F', 111 year_of_birth=1991, 112 ) 113 114 # reality = src.person.Person.say_hello(jane) 115 reality = jane.say_hello() 116 my_expectation = ( 117 'Hi, my name is jane doe and I am' 118 f' {calculate_age(1991)}' 119 ) 120 self.assertEqual(reality, my_expectation) 121 122 123# Exceptions seengreen.
I add an assertion for the next person
114 # reality = src.person.Person.say_hello(jane) 115 reality = jane.say_hello() 116 my_expectation = ( 117 'Hi, my name is jane doe and I am' 118 f' {calculate_age(1991)}' 119 ) 120 self.assertEqual(reality, my_expectation) 121 122 john = src.person.Person( 123 first_name='john', 124 last_name='smith', 125 year_of_birth=1580, 126 ) 127 128 reality = john.say_hello() 129 my_expectation = ( 130 'Hi, my name is jane doe and I am' 131 f' {calculate_age(1991)}' 132 ) 133 self.assertEqual(reality, my_expectation) 134 135 136# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is john smith and I am 446' != 'Hi, my name is jane doe and I am 35'I change
my_expectationto matchrealityforjohn128 reality = john.say_hello() 129 my_expectation = ( 130 # 'Hi, my name is jane doe and I am' 131 # f' {calculate_age(1991)}' 132 'Hi, my name is john smith and I am' 133 f' {calculate_age(1580)}' 134 ) 135 self.assertEqual(reality, my_expectation)the test passes.
I add an assertion for
a_person128 reality = john.say_hello() 129 my_expectation = ( 130 # 'Hi, my name is jane doe and I am' 131 # f' {calculate_age(1991)}' 132 'Hi, my name is john smith and I am' 133 f' {calculate_age(1580)}' 134 ) 135 self.assertEqual(reality, my_expectation) 136 137 a_person = src.person.Person( 138 first_name='person', 139 last_name='public', 140 year_of_birth=2000, 141 sex='F', 142 ) 143 144 reality = a_person.say_hello() 145 my_expectation = ( 146 'Hi, my name is john smith and I am' 147 f' {calculate_age(1580)}' 148 ) 149 self.assertEqual(reality, my_expectation) 150 151 152# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: 'Hi, my name is person public and I am 26' != 'Hi, my name is john smith and I am 446'I change
my_expectationto matchrealityfora_person129 a_person = src.person.Person( 130 first_name='person', 131 last_name='public', 132 year_of_birth=2000, 133 sex='F', 134 ) 135 136 reality = a_person.say_hello() 137 my_expectation = ( 138 'Hi, my name is person public and I am' 139 f' {calculate_age(2000)}' 140 ) 141 self.assertEqual(reality, my_expectation) 142 143 144# Exceptions seenthe test passes.
I open a new terminal then change directories to
personcd personI add a git commit message in the other terminal
git commit -am 'add test_classy_person_says_hello'
test_classy_person_says_hello with random values
I want to use random values to test_classy_person_says_hello
I go back to the terminal that is running the tests
I add variables
91 def test_classy_person_says_hello(self): 92 first_name = 'joe' 93 last_name = 'blow' 94 95 year_of_birth = 1996 96 age = calculate_age(year_of_birth) 97 98 joe = src.person.Person( 99 first_name='joe', 100 last_name='blow', 101 year_of_birth=1996, 102 )I use the variables to remove repetition of
'joe','blow'and199696 joe = src.person.Person( 97 # first_name='joe', 98 # last_name='blow', 99 # year_of_birth=1996, 100 first_name=first_name, 101 last_name=last_name, 102 year_of_birth=year_of_birth, 103 ) 104 105 # reality = src.person.say_hello(joe) 106 # reality = src.person.Person.say_hello(joe) 107 reality = joe.say_hello() 108 # my_expectation = None 109 my_expectation = ( 110 # 'Hi, my name is joe blow and I am' 111 # f' {calculate_age(1996)}' 112 f'Hi, my name is {first_name} {last_name}' 113 f' and I am {age}' 114 ) 115 self.assertEqual(reality, my_expectation)the test is still green.
I add variables
108 # reality = src.person.say_hello(joe) 109 # reality = src.person.Person.say_hello(joe) 110 reality = joe.say_hello() 111 # my_expectation = None 112 my_expectation = ( 113 # 'Hi, my name is joe blow and I am' 114 # f' {calculate_age(1996)}' 115 f'Hi, my name is {first_name} {last_name}' 116 f' and I am {age}' 117 ) 118 self.assertEqual(reality, my_expectation) 119 120 first_name = 'jane' 121 last_name = 'doe' 122 123 year_of_birth = 1991 124 age = calculate_age(year_of_birth) 125 126 jane = src.person.Person( 127 first_name='jane', 128 sex='F', 129 year_of_birth=1991, 130 )I use the variables to remove repetition of
'jane'and1991126 jane = src.person.Person( 127 # first_name='jane', 128 sex='F', 129 # year_of_birth=1991, 130 first_name=first_name, 131 year_of_birth=year_of_birth, 132 ) 133 134 # reality = src.person.Person.say_hello(jane) 135 reality = jane.say_hello() 136 my_expectation = ( 137 # 'Hi, my name is jane doe and I am' 138 # f' {calculate_age(1991)}' 139 f'Hi, my name is {first_name} {last_name}' 140 f' and I am {age}' 141 ) 142 self.assertEqual(reality, my_expectation) 143 144 john = src.person.Person( 145 first_name='john', 146 last_name='smith', 147 year_of_birth=1580, 148 )the test is still green.
I do the same thing with
john134 # reality = src.person.Person.say_hello(jane) 135 reality = jane.say_hello() 136 my_expectation = ( 137 # 'Hi, my name is jane doe and I am' 138 # f' {calculate_age(1991)}' 139 f'Hi, my name is {first_name} {last_name}' 140 f' and I am {age}' 141 ) 142 self.assertEqual(reality, my_expectation) 143 144 first_name = 'john' 145 last_name = 'smith' 146 147 year_of_birth = 1580 148 age = calculate_age(year_of_birth) 149 150 john = src.person.Person( 151 # first_name='john', 152 # last_name='smith', 153 # year_of_birth=1580, 154 first_name=first_name, 155 last_name=last_name, 156 year_of_birth=year_of_birth, 157 ) 158 159 reality = john.say_hello() 160 my_expectation = ( 161 # 'Hi, my name is jane doe and I am' 162 # f' {calculate_age(1991)}' 163 # 'Hi, my name is john smith and I am' 164 # f' {calculate_age(1580)}' 165 f'Hi, my name is {first_name} {last_name}' 166 f' and I am {age}' 167 ) 168 self.assertEqual(reality, my_expectation)the test is still green.
I do the same thing with
a_person159 reality = john.say_hello() 160 my_expectation = ( 161 # 'Hi, my name is jane doe and I am' 162 # f' {calculate_age(1991)}' 163 # 'Hi, my name is john smith and I am' 164 # f' {calculate_age(1580)}' 165 f'Hi, my name is {first_name} {last_name}' 166 f' and I am {age}' 167 ) 168 self.assertEqual(reality, my_expectation) 169 170 first_name = 'person' 171 last_name = 'public' 172 173 year_of_birth = 2000 174 age = calculate_age(year_of_birth) 175 176 a_person = src.person.Person( 177 # first_name='person', 178 # last_name='public', 179 # year_of_birth=2000, 180 first_name=first_name, 181 last_name=last_name, 182 year_of_birth=year_of_birth, 183 sex='F', 184 ) 185 186 reality = a_person.say_hello() 187 my_expectation = ( 188 # 'Hi, my name is john smith and I am' 189 # f' {calculate_age(1580)}' 190 # 'Hi, my name is person public and I am' 191 # f' {calculate_age(2000)}' 192 f'Hi, my name is {first_name} {last_name}' 193 f' and I am {age}' 194 ) 195 self.assertEqual(reality, my_expectation) 196 197 198# Exceptions seenthe test is still green
I can add a random person with random values for the
first_name,last_nameandagevariables that are sent in the call tosrc.person.Personto replacejoe,jane,johnanda_personsince they are all made the same way91 def test_classy_person_says_hello(self): 92 first_name = get_random_name() 93 last_name = get_random_name() 94 95 year_of_birth = get_random_year_of_birth() 96 age = calculate_age(year_of_birth) 97 98 a_random_person = src.person.Person( 99 first_name=first_name, 100 last_name=last_name, 101 year_of_birth=year_of_birth, 102 ) 103 104 reality = a_random_person.say_hello() 105 my_expectation = '' 106 self.assertEqual(reality, my_expectation) 107 108 first_name = 'joe' 109 last_name = 'blow'the terminal is my friend and shows AssertionError
AssertionError: 'Hi, my name is Z Y and I am X' != ''I change
my_expectationto matchrealityfora_random_person104 reality = a_random_person.say_hello() 105 # my_expectation = '' 106 my_expectation = ( 107 f'Hi, my name is {first_name} {last_name}' 108 f' and I am {age}' 109 ) 110 self.assertEqual(reality, my_expectation)the test passes.
I remove the commented lines and the other people from test_classy_person_says_hello because
a_random_personcovers their cases91 def test_classy_person_says_hello(self): 92 first_name = get_random_name() 93 last_name = get_random_name() 94 95 year_of_birth = get_random_year_of_birth() 96 age = calculate_age(year_of_birth) 97 98 a_random_person = src.person.Person( 99 first_name=first_name, 100 last_name=last_name, 101 year_of_birth=year_of_birth, 102 ) 103 104 reality = a_random_person.say_hello() 105 my_expectation = ( 106 f'Hi, my name is {first_name} {last_name}' 107 f' and I am {age}' 108 ) 109 self.assertEqual(reality, my_expectation) 110 111 112# Exceptions seen 113# AssertionError 114# NameError 115# AttributeError 116# TypeError 117# SyntaxErrorI add a git commit message in the other terminal
git commit -am \ 'test_classy_person_says_hello with random values'
extract random_first_name class attribute
I make the values for first_name in the tests the same way each time, since TestPerson is a class, I can use a class attribute to remove repetition of how I make it, then have all the methods reference it
I go back to the terminal that is running the tests
I add a class attribute called
random_first_nameto theTestPersonobject32class TestPerson(unittest.TestCase): 33 34 random_first_name = get_random_name() 35 36 def test_factory_w_keyword_arguments(self):I use the new class attribute in test_factory_w_keyword_arguments
36 def test_factory_w_keyword_arguments(self): 37 year_of_birth = get_random_year_of_birth() 38 39 a_person = dict( 40 # first_name=get_random_name(), 41 first_name=self.random_first_name, 42 last_name=get_random_name(), 43 sex=pick_one('F', 'M'), 44 )the test is still green.
I use the new class attribute in test_factory_w_optional_arguments
56 def test_factory_w_optional_arguments(self): 57 # first_name = get_random_name() 58 first_name = self.random_first_name 59 year_of_birth = get_random_year_of_birth() 60 61 reality = src.person.factory( 62 # first_name=first_name, 63 first_name=self.random_first_name, 64 year_of_birth=year_of_birth, 65 ) 66 my_expectation = dict( 67 # first_name=first_name, 68 first_name=self.random_first_name, 69 last_name='doe', 70 sex='M', 71 age=calculate_age(year_of_birth), 72 ) 73 self.assertEqual(reality, my_expectation) 74 75 def test_factory_person_says_hello(self):the test is still green.
I use the new class attribute in test_factory_person_says_hello
75 def test_factory_person_says_hello(self): 76 # first_name = get_random_name() 77 first_name = self.random_first_name 78 last_name = get_random_name() 79 sex = pick_one('F', 'M') 80 81 year_of_birth = get_random_year_of_birth() 82 age = calculate_age(year_of_birth) 83 84 a_random_person = src.person.factory( 85 # first_name=first_name, 86 first_name=self.random_first_name, 87 last_name=last_name, 88 sex=sex, 89 year_of_birth=year_of_birth, 90 ) 91 92 reality = src.person.say_hello(a_random_person) 93 my_expectation = ( 94 # f'Hi, my name is {first_name} {last_name}' 95 f'Hi, my name is {self.random_first_name}' 96 f' {last_name}' 97 f' and I am {age}' 98 ) 99 self.assertEqual(reality, my_expectation) 100 101 def test_classy_person_says_hello(self):the test is still green.
I use the new class attribute in test_classy_person_says_hello
101 def test_classy_person_says_hello(self): 102 # first_name = get_random_name() 103 first_name = self.random_first_name 104 last_name = get_random_name() 105 106 year_of_birth = get_random_year_of_birth() 107 age = calculate_age(year_of_birth) 108 109 a_random_person = src.person.Person( 110 # first_name=first_name, 111 first_name=self.random_first_name, 112 last_name=last_name, 113 year_of_birth=year_of_birth, 114 ) 115 116 reality = a_random_person.say_hello() 117 my_expectation = ( 118 # f'Hi, my name is {first_name} {last_name}' 119 f'Hi, my name is {self.random_first_name}' 120 f' {last_name}' 121 f' and I am {age}' 122 ) 123 self.assertEqual(reality, my_expectation) 124 125 126# Exceptions seenthe test is still green.
I add a git commit message in the other terminal
git commit -am \ 'extract random_first_name class attribute'
extract random_year_of_birth class attribute
I call the get_random_year_of_birth function for year_of_birth in each test, since TestPerson is a class, I can use a class attribute to remove repetition of those calls, then have all the methods reference the value it returns
I go back to the terminal that is running the tests
I add a class attribute called
random_year_of_birthto theTestPersonobject32class TestPerson(unittest.TestCase): 33 34 random_first_name = get_random_name() 35 random_year_of_birth = get_random_year_of_birth() 36 37 def test_factory_w_keyword_arguments(self):I use
self.random_year_of_birthin test_factory_w_keyword_arguments37 def test_factory_w_keyword_arguments(self): 38 # year_of_birth = get_random_year_of_birth() 39 year_of_birth = self.random_year_of_birth 40 41 a_person = dict( 42 # first_name=get_random_name(), 43 first_name=self.random_first_name, 44 last_name=get_random_name(), 45 sex=pick_one('F', 'M'), 46 ) 47 48 reality = src.person.factory( 49 **a_person, 50 # year_of_birth=year_of_birth, 51 year_of_birth=self.random_year_of_birth, 52 ) 53 my_expectation = dict( 54 **a_person, 55 # age=calculate_age(year_of_birth), 56 age=calculate_age( 57 self.random_year_of_birth 58 ), 59 ) 60 self.assertEqual(reality, my_expectation) 61 62 def test_factory_w_optional_arguments(self):still green.
I use
self.random_year_of_birthin test_factory_w_optional_arguments62 def test_factory_w_optional_arguments(self): 63 # first_name = get_random_name() 64 first_name = self.random_first_name 65 # year_of_birth = get_random_year_of_birth() 66 year_of_birth = self.random_year_of_birth 67 68 reality = src.person.factory( 69 # first_name=first_name, 70 first_name=self.random_first_name, 71 # year_of_birth=year_of_birth, 72 year_of_birth=self.random_year_of_birth, 73 ) 74 my_expectation = dict( 75 # first_name=first_name, 76 first_name=self.random_first_name, 77 last_name='doe', 78 sex='M', 79 # age=calculate_age(year_of_birth), 80 age=calculate_age( 81 self.random_year_of_birth 82 ), 83 ) 84 self.assertEqual(reality, my_expectation) 85 86 def test_factory_person_says_hello(self):still green.
I use
self.random_year_of_birthin test_factory_person_says_hello86 def test_factory_person_says_hello(self): 87 # first_name = get_random_name() 88 first_name = self.random_first_name 89 last_name = get_random_name() 90 sex = pick_one('F', 'M') 91 92 # year_of_birth = get_random_year_of_birth() 93 # age = calculate_age(year_of_birth) 94 year_of_birth = self.random_year_of_birth 95 age = calculate_age(self.random_year_of_birth) 96 97 a_random_person = src.person.factory( 98 # first_name=first_name, 99 first_name=self.random_first_name, 100 last_name=last_name, 101 sex=sex, 102 # year_of_birth=year_of_birth, 103 year_of_birth=self.random_year_of_birth, 104 ) 105 106 reality = src.person.say_hello(a_random_person) 107 my_expectation = ( 108 # f'Hi, my name is {first_name} {last_name}' 109 f'Hi, my name is {self.random_first_name}' 110 f' {last_name}' 111 f' and I am {age}' 112 ) 113 self.assertEqual(reality, my_expectation) 114 115 def test_classy_person_says_hello(self):still green.
I use
self.random_year_of_birthin test_classy_person_says_hello115 def test_classy_person_says_hello(self): 116 # first_name = get_random_name() 117 first_name = self.random_first_name 118 last_name = get_random_name() 119 120 # year_of_birth = get_random_year_of_birth() 121 # age = calculate_age(year_of_birth) 122 year_of_birth = self.random_year_of_birth 123 age = calculate_age(self.random_year_of_birth) 124 125 a_random_person = src.person.Person( 126 # first_name=first_name, 127 first_name=self.random_first_name, 128 last_name=last_name, 129 # year_of_birth=year_of_birth, 130 year_of_birth=self.random_year_of_birth, 131 )still green.
I add a git commit message in the other terminal
git commit -am \ 'extract random_year_of_birth class attribute'
extract random_last_name class attribute
The last_name variable is made the same way in three of the four tests, I can use a class attribute to remove its repetition then have all the methods reference the value
I go back to the terminal that is running the tests
I add a class attribute called
random_last_nameto theTestPersonobject32class TestPerson(unittest.TestCase): 33 34 random_first_name = get_random_name() 35 random_last_name = get_random_name() 36 random_year_of_birth = get_random_year_of_birth() 37 38 def test_factory_w_keyword_arguments(self):I use
self.random_last_namein test_factory_w_keyword_arguments38 def test_factory_w_keyword_arguments(self): 39 # year_of_birth = get_random_year_of_birth() 40 year_of_birth = self.random_year_of_birth 41 42 a_person = dict( 43 # first_name=get_random_name(), 44 first_name=self.random_first_name, 45 # last_name=get_random_name(), 46 last_name=self.random_last_name, 47 sex=pick_one('F', 'M'), 48 )green.
I use
self.random_last_namein test_factory_person_says_hello88 def test_factory_person_says_hello(self): 89 # first_name = get_random_name() 90 first_name = self.random_first_name 91 # last_name = get_random_name() 92 last_name = self.random_last_name 93 sex = pick_one('F', 'M') 94 95 # year_of_birth = get_random_year_of_birth() 96 year_of_birth = self.random_year_of_birth 97 # age = calculate_age(year_of_birth) 98 age = calculate_age(self.random_year_of_birth) 99 100 a_random_person = src.person.factory( 101 # first_name=first_name, 102 first_name=self.random_first_name, 103 # last_name=last_name, 104 last_name=self.random_last_name, 105 sex=sex, 106 # year_of_birth=year_of_birth, 107 year_of_birth=self.random_year_of_birth, 108 ) 109 110 reality = src.person.say_hello(a_random_person) 111 my_expectation = ( 112 # f'Hi, my name is {first_name} {last_name}' 113 f'Hi, my name is {self.random_first_name}' 114 # f' {last_name}' 115 f' {self.random_last_name}' 116 f' and I am {age}' 117 ) 118 self.assertEqual(reality, my_expectation) 119 120 def test_classy_person_says_hello(self):green.
I use
self.random_last_namein test_classy_person_says_hello120 def test_classy_person_says_hello(self): 121 # first_name = get_random_name() 122 first_name = self.random_first_name 123 # last_name = get_random_name() 124 last_name = self.random_last_name 125 126 # year_of_birth = get_random_year_of_birth() 127 # age = calculate_age(year_of_birth) 128 year_of_birth = self.random_year_of_birth 129 age = calculate_age(self.random_year_of_birth) 130 131 a_random_person = src.person.Person( 132 # first_name=first_name, 133 first_name=self.random_first_name, 134 # last_name=last_name, 135 last_name=self.random_last_name, 136 # year_of_birth=year_of_birth, 137 year_of_birth=self.random_year_of_birth, 138 ) 139 140 reality = a_random_person.say_hello() 141 my_expectation = ( 142 # f'Hi, my name is {first_name} {last_name}' 143 f'Hi, my name is {self.random_first_name}' 144 # f' {last_name}' 145 f' {self.random_last_name}' 146 f' and I am {age}' 147 ) 148 self.assertEqual(reality, my_expectation) 149 150 151# Exceptions seengreen.
I add a git commit message in the other terminal
git commit -am \ 'extract random_last_name class attribute'
extract age class attribute
I call the calculate_age function with the self.random_year_of_birth attribute in each test, since TestPerson is a class, I can use a class attribute to remove repetition of those calls, then have all the methods reference the value it returns
I go back to the terminal that is running the tests
I add a class attribute called
ageto theTestPersonobject32class TestPerson(unittest.TestCase): 33 34 random_first_name = get_random_name() 35 random_last_name = get_random_name() 36 random_year_of_birth = get_random_year_of_birth() 37 age = calculate_age(random_year_of_birth) 38 39 def test_factory_w_keyword_arguments(self):I use
self.agein test_factory_w_keyword_arguments51 reality = src.person.factory( 52 **a_person, 53 # year_of_birth=year_of_birth, 54 year_of_birth=self.random_year_of_birth, 55 ) 56 my_expectation = dict( 57 **a_person, 58 # age=calculate_age(year_of_birth), 59 # age=calculate_age( 60 # self.random_year_of_birth 61 # ), 62 age=self.age, 63 ) 64 self.assertEqual(reality, my_expectation) 65 66 def test_factory_w_optional_arguments(self):still green.
I remove the commented lines and unused variables from test_factory_w_keyword_arguments
39 def test_factory_w_keyword_arguments(self): 40 a_person = dict( 41 first_name=self.random_first_name, 42 last_name=self.random_last_name, 43 sex=pick_one('F', 'M'), 44 ) 45 46 reality = src.person.factory( 47 **a_person, 48 year_of_birth=self.random_year_of_birth, 49 ) 50 my_expectation = dict( 51 **a_person, 52 age=self.age, 53 ) 54 self.assertEqual(reality, my_expectation) 55 56 def test_factory_w_optional_arguments(self):I use
self.agein test_factory_w_optional_arguments62 reality = src.person.factory( 63 # first_name=first_name, 64 first_name=self.random_first_name, 65 # year_of_birth=year_of_birth, 66 year_of_birth=self.random_year_of_birth, 67 ) 68 my_expectation = dict( 69 # first_name=first_name, 70 first_name=self.random_first_name, 71 last_name='doe', 72 sex='M', 73 # age=calculate_age(year_of_birth), 74 # age=calculate_age( 75 # self.random_year_of_birth 76 # ), 77 age=self.age, 78 ) 79 self.assertEqual(reality, my_expectation) 80 81 def test_factory_person_says_hello(self):still green.
I remove the commented lines and unused variables from test_factory_w_optional_arguments
56 def test_factory_w_optional_arguments(self): 57 reality = src.person.factory( 58 first_name=self.random_first_name, 59 year_of_birth=self.random_year_of_birth, 60 ) 61 my_expectation = dict( 62 first_name=self.random_first_name, 63 last_name='doe', 64 sex='M', 65 age=self.age, 66 ) 67 self.assertEqual(reality, my_expectation) 68 69 def test_factory_person_says_hello(self):I use
self.agein test_factory_person_says_hello69 def test_factory_person_says_hello(self): 70 # first_name = get_random_name() 71 first_name = self.random_first_name 72 # last_name = get_random_name() 73 last_name = self.random_last_name 74 sex = pick_one('F', 'M') 75 76 # year_of_birth = get_random_year_of_birth() 77 year_of_birth = self.random_year_of_birth 78 # age = calculate_age(year_of_birth) 79 # age = calculate_age(self.random_year_of_birth) 80 age = self.age 81 82 a_random_person = src.person.factory( 83 # first_name=first_name, 84 first_name=self.random_first_name, 85 # last_name=last_name, 86 last_name=self.random_last_name, 87 sex=sex, 88 # year_of_birth=year_of_birth, 89 year_of_birth=self.random_year_of_birth, 90 ) 91 92 reality = src.person.say_hello(a_random_person) 93 my_expectation = ( 94 # f'Hi, my name is {first_name} {last_name}' 95 f'Hi, my name is {self.random_first_name}' 96 # f' {last_name}' 97 f' {self.random_last_name}' 98 # f' and I am {age}' 99 f' and I am {self.age}' 100 ) 101 self.assertEqual(reality, my_expectation) 102 103 def test_classy_person_says_hello(self):still green.
I remove the commented lines and unused variables from test_factory_person_says_hello
69 def test_factory_person_says_hello(self): 70 a_random_person = src.person.factory( 71 first_name=self.random_first_name, 72 last_name=self.random_last_name, 73 year_of_birth=self.random_year_of_birth, 74 ) 75 76 reality = src.person.say_hello(a_random_person) 77 my_expectation = ( 78 f'Hi, my name is {self.random_first_name}' 79 f' {self.random_last_name}' 80 f' and I am {self.age}' 81 ) 82 self.assertEqual(reality, my_expectation) 83 84 def test_classy_person_says_hello(self):I remove the
sexvariable and parameter because it is not used in this test. I guessa_random_personis not all that random since it will always have'M'assexin this test.I use
self.agein test_classy_person_says_hello90 # year_of_birth = get_random_year_of_birth() 91 # age = calculate_age(year_of_birth) 92 year_of_birth = self.random_year_of_birth 93 # age = calculate_age(self.random_year_of_birth) 94 age = self.age 95 96 a_random_person = src.person.Person( 97 # first_name=first_name, 98 first_name=self.random_first_name, 99 # last_name=last_name, 100 last_name=self.random_last_name, 101 # year_of_birth=year_of_birth, 102 year_of_birth=self.random_year_of_birth, 103 ) 104 105 reality = a_random_person.say_hello() 106 my_expectation = ( 107 # f'Hi, my name is {first_name} {last_name}' 108 f'Hi, my name is {self.random_first_name}' 109 # f' {last_name}' 110 f' {self.random_last_name}' 111 # f' and I am {age}' 112 f' and I am {self.age}' 113 ) 114 self.assertEqual(reality, my_expectation) 115 116 117# Exceptions seenstill green.
I remove the commented lines and unused variables from test_classy_person_says_hello
84 def test_classy_person_says_hello(self): 85 a_random_person = src.person.Person( 86 first_name=self.random_first_name, 87 last_name=self.random_last_name, 88 year_of_birth=self.random_year_of_birth, 89 ) 90 91 reality = a_random_person.say_hello() 92 my_expectation = ( 93 f'Hi, my name is {self.random_first_name}' 94 f' {self.random_last_name}' 95 f' and I am {self.age}' 96 ) 97 self.assertEqual(reality, my_expectation) 98 99 100# Exceptions seenI also do not need the
sexvariable and parameter in this test.I add a git commit message in the other terminal
git commit -am \ 'extract age class attribute'
how to use the setUp method to reset class attributes for every test
A problem with the current setup with the class attributes is that they are made once when the class is initialized. This means that even though they all use random values, those values are created once and every test that references the values after that is using the exact same values for each test.
I want each test to get new random values every time they run and the unittest.TestCase class has a way to do that - the setUp method, it runs before every test is run.
RED: make it fail
I go back to the terminal that is running the tests
I add the unittest.TestCase.setUp method to
TestPersonthen move the class attributes into it32class TestPerson(unittest.TestCase): 33 34 # random_first_name = get_random_name() 35 # random_last_name = get_random_name() 36 # random_year_of_birth = get_random_year_of_birth() 37 # age = calculate_age(random_year_of_birth) 38 39 def setUp(self): 40 random_first_name = get_random_name() 41 random_last_name = get_random_name() 42 random_year_of_birth = get_random_year_of_birth() 43 age = calculate_age(random_year_of_birth) 44 45 def test_factory_w_keyword_arguments(self):the terminal is my friend, and shows AttributeError
FAILED ...test_classy_person_says_hello - AttributeError: 'TestPerson' object has no attribute 'random_first_name' FAILED ...test_factory_person_says_hello - AttributeError: 'TestPerson' object has no attribute 'random_first_name' FAILED ...test_factory_w_keyword_arguments - AttributeError: 'TestPerson' object has no attribute 'random_first_name' FAILED ...test_factory_w_optional_arguments - AttributeError: 'TestPerson' object has no attribute 'random_first_name'because the
first_namevariable now belongs to the setUp method, the other methods have no way to reach it. I have to make it a class attribute.
GREEN: make it pass
I change the
first_namevariable to a class attribute in the setUp method for the test methods to be able to use it39 def setUp(self): 40 # random_first_name = get_random_name() 41 self.random_first_name = get_random_name() 42 random_last_name = get_random_name() 43 random_year_of_birth = get_random_year_of_birth() 44 age = calculate_age(random_year_of_birth) 45 46 def test_factory_w_keyword_arguments(self):the terminal is my friend, and shows AttributeError
FAILED ...test_classy_person_says_hello - AttributeError: 'TestPerson' object has no attribute 'random_last_name'. Did you mean: 'random_first_name'? FAILED ...test_factory_person_says_hello - AttributeError: 'TestPerson' object has no attribute 'random_last_name'. Did you mean: 'random_first_name'? FAILED ...test_factory_w_keyword_arguments - AttributeError: 'TestPerson' object has no attribute 'random_last_name'. Did you mean: 'random_first_name'? FAILED ...test_factory_w_optional_arguments - AttributeError: 'TestPerson' object has no attribute 'random_year_of_birth'.because the
year_of_birthandlast_namevariables now belong to the setUp method, the other methods have no way to reach them. I have to make them class attributesI change the
year_of_birthandlast_namevariables to class attributes in the setUp method for the test methods to be able to use them as well39 def setUp(self): 40 # random_first_name = get_random_name() 41 self.random_first_name = get_random_name() 42 # random_last_name = get_random_name() 43 self.random_last_name = get_random_name() 44 # random_year_of_birth = get_random_year_of_birth() 45 # age = calculate_age(random_year_of_birth) 46 self.random_year_of_birth = ( 47 get_random_year_of_birth() 48 ) 49 age = calculate_age( 50 self.random_year_of_birth 51 ) 52 53 def test_factory_w_keyword_arguments(self):the terminal is my friend, and shows AttributeError
FAILED ...test_classy_person_says_hello - AttributeError: 'TestPerson' object has no attribute 'age' FAILED ...test_factory_person_says_hello - AttributeError: 'TestPerson' object has no attribute 'age' FAILED ...test_factory_w_keyword_arguments - AttributeError: 'TestPerson' object has no attribute 'age' FAILED ...test_factory_w_optional_arguments - AttributeError: 'TestPerson' object has no attribute 'age'because the
agebelongs to the setUp method, and the other methods have no way to reach it. I have to make it a class attributeI change the
agevariable to class attributes in the setUp method for the other methods to be able to use it39 def setUp(self): 40 # random_first_name = get_random_name() 41 self.random_first_name = get_random_name() 42 # random_last_name = get_random_name() 43 self.random_last_name = get_random_name() 44 # random_year_of_birth = get_random_year_of_birth() 45 # age = calculate_age(random_year_of_birth) 46 self.random_year_of_birth = ( 47 get_random_year_of_birth() 48 ) 49 # age = calculate_age( 50 self.age = calculate_age( 51 self.random_year_of_birth 52 ) 53 54 def test_factory_w_keyword_arguments(self):the test passes.
The unittest.TestCase.setUp method runs before every test, in this case it sets these class attributes (variables) to new values before every test
self.random_first_nameto the result of calling theget_random_namefunction, which returns a random nameself.random_last_nameto the result of calling theget_random_namefunction, which returns a random nameself.random_year_of_birthto the result of calling theget_random_year_of_birthfunction which returns a random year between 120 years ago and the current yearself.ageto the result of calling thecalculate_agefunction, which returns the current year minusself.random_year_of_birth
REFACTOR: make it better
I no longer need the calculate_age function because it is only called by the setUp method. I use `datetime.datetime.now`_ directly
39 def setUp(self): 40 # random_first_name = get_random_name() 41 self.random_first_name = get_random_name() 42 # random_last_name = get_random_name() 43 self.random_last_name = get_random_name() 44 # random_year_of_birth = get_random_year_of_birth() 45 # age = calculate_age(random_year_of_birth) 46 self.random_year_of_birth = ( 47 get_random_year_of_birth() 48 ) 49 # age = calculate_age( 50 # self.age = calculate_age( 51 # self.random_year_of_birth 52 # ) 53 self.age = ( 54 datetime.datetime.now().year 55 - self.random_year_of_birth 56 ) 57 58 def test_factory_w_keyword_arguments(self):the test is still green.
I no longer need the get_random_year_of_birth function because it is only called by the setUp method. I use random.randint directly
39 def setUp(self): 40 # random_first_name = get_random_name() 41 self.random_first_name = get_random_name() 42 # random_last_name = get_random_name() 43 self.random_last_name = get_random_name() 44 # random_year_of_birth = get_random_year_of_birth() 45 # age = calculate_age(random_year_of_birth) 46 # self.random_year_of_birth = ( 47 # get_random_year_of_birth() 48 # ) 49 this_year = datetime.datetime.now().year 50 self.random_year_of_birth = random.randint( 51 this_year-120, this_year 52 ) 53 # age = calculate_age( 54 # self.age = calculate_age( 55 # self.random_year_of_birth 56 # ) 57 self.age = ( 58 datetime.datetime.now().year 59 - self.random_year_of_birth 60 ) 61 62 def test_factory_w_keyword_arguments(self):still green.
I use the
this_yearvariable in the calculation forself.age39 def setUp(self): 40 # random_first_name = get_random_name() 41 self.random_first_name = get_random_name() 42 # random_last_name = get_random_name() 43 self.random_last_name = get_random_name() 44 # random_year_of_birth = get_random_year_of_birth() 45 # age = calculate_age(random_year_of_birth) 46 # self.random_year_of_birth = ( 47 # get_random_year_of_birth() 48 # ) 49 this_year = datetime.datetime.now().year 50 self.random_year_of_birth = random.randint( 51 this_year-120, this_year 52 ) 53 # age = calculate_age( 54 # self.age = calculate_age( 55 # self.random_year_of_birth 56 # ) 57 # self.age = ( 58 # datetime.datetime.now().year 59 # - self.random_year_of_birth 60 # ) 61 self.age = this_year - self.random_year_of_birth 62 63 def test_factory_w_keyword_arguments(self):green.
datetime.datetime.now().yearis now called once each time the setUp method runsI remove the commented lines
32class TestPerson(unittest.TestCase): 33 34 def setUp(self): 35 self.random_first_name = get_random_name() 36 self.random_last_name = get_random_name() 37 this_year = datetime.datetime.now().year 38 self.random_year_of_birth = random.randint( 39 this_year-120, this_year 40 ) 41 self.age = this_year - self.random_year_of_birth 42 43 def test_factory_w_keyword_arguments(self):I remove the calculate_age and get_random_year_of_birth functions
1import datetime 2import random 3import src.person 4import unittest 5 6 7def pick_one(*choices): 8 return random.choice(choices) 9 10 11def get_random_name(): 12 return pick_one( 13 'jane', 'joe', 'john', 'person', 14 'doe', 'smith', 'blow', 'public', 15 ) 16 17 18class TestPerson(unittest.TestCase):I add a git commit message in the other terminal
git commit -am 'move class attributes to setUp method'
test_attributes_and_methods_of_person_instance
Python has the dir built-in function which shows the attributes and methods of the object it is given in parentheses. It allows me to see what makes up an object without looking at the code or reading the documentation. I can then run tests to see what each thing does.
RED: make it fail
I go back to the terminal that is running the tests
I add a new test with the dir built-in function in
test_person.py81 reality = a_random_person.say_hello() 82 my_expectation = ( 83 f'Hi, my name is {self.random_first_name}' 84 f' {self.random_last_name}' 85 f' and I am {self.age}' 86 ) 87 self.assertEqual(reality, my_expectation) 88 89 def test_attributes_and_methods_of_person_class(self): 90 reality = dir(src.person.Person) 91 my_expectation = None 92 self.assertEqual(reality, my_expectation) 93 94 95# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: Lists differ: ['__class__', '__delattr__', '__dict__', '[377 chars]llo'] != Nonebecause dir returned a list (anything in square brackets
[ ]) andmy_expectationis None
GREEN: make it pass
I copy (ctrl/command+c) the values from the terminal and paste (ctrl/command+v) them as
my_expectation89 def test_attributes_and_methods_of_person_class(self): 90 reality = dir(src.person.Person) 91 # my_expectation = None 92 my_expectation = [ 93 '__class__', '__delattr__', '__dict__', 94 [371 chars]llo' 95 ] 96 self.assertEqual(reality, my_expectation) 97 98 99# Exceptions seenthe terminal is my friend, and shows SyntaxError
E [371 chars]llo' E ^ E SyntaxError: unterminated string literal (detected at line 94)because I have a closing quote (
') without a matching opening one and enclosures must be closed once openI add the opening quote
89 def test_attributes_and_methods_of_person_class(self): 90 reality = dir(src.person.Person) 91 # my_expectation = None 92 my_expectation = [ 93 '__class__', '__delattr__', '__dict__', 94 '[371 chars]llo' 95 ] 96 self.assertEqual(reality, my_expectation) 97 98 99# Exceptions seenthe terminal is my friend and shows AssertionError
AssertionError: Lists differ: ['__c[32 chars]_', '__dir__', '__doc__', '__eq__', '__firstli[329 chars]llo'] != ['__c[32 chars]_', '[371 chars]llo']it shows me the entire list below the message
I copy (ctrl/command+c) the values from the terminal and paste (ctrl/command+v) them as
my_expectation89 def test_attributes_and_methods_of_person_class(self): 90 reality = dir(src.person.Person) 91 # my_expectation = None 92 # my_expectation = [ 93 # '__class__', '__delattr__', '__dict__', 94 # '[371 chars]llo' 95 # ] 96 my_expectation = E - ['__class__', 97E - '__delattr__', 98E - '__dict__', 99E - '__dir__', 100E - '__doc__', 101E - '__eq__', 102E - '__firstlineno__', 103E - '__format__', 104E - '__ge__', 105E - '__getattribute__', 106E - '__getstate__', 107E - '__gt__', 108E - '__hash__', 109E - '__init__', 110E - '__init_subclass__', 111E - '__le__', 112E - '__lt__', 113E - '__module__', 114E - '__ne__', 115E - '__new__', 116E - '__reduce__', 117E - '__reduce_ex__', 118E - '__repr__', 119E - '__setattr__', 120E - '__sizeof__', 121E - '__static_attributes__', 122E - '__str__', 123E - '__subclasshook__', 124E - '__weakref__', 125E - 'say_hello'] 126 self.assertEqual(reality, my_expectation) 127 128 129# Exceptions seenNameError: name 'E' is not definedI use the
find and replacefeature of the Integrated Development Environment (IDE) to remove the extra characters, then remove the commented lines89 def test_attributes_and_methods_of_person_class(self): 90 reality = dir(src.person.Person) 91 my_expectation = [ 92 '__class__', 93 '__delattr__', 94 '__dict__', 95 '__dir__', 96 '__doc__', 97 '__eq__', 98 '__firstlineno__', 99 '__format__', 100 '__ge__', 101 '__getattribute__', 102 '__getstate__', 103 '__gt__', 104 '__hash__', 105 '__init__', 106 '__init_subclass__', 107 '__le__', 108 '__lt__', 109 '__module__', 110 '__ne__', 111 '__new__', 112 '__reduce__', 113 '__reduce_ex__', 114 '__repr__', 115 '__setattr__', 116 '__sizeof__', 117 '__static_attributes__', 118 '__str__', 119 '__subclasshook__', 120 '__weakref__', 121 'say_hello', 122 ] 123 self.assertEqual(reality, my_expectation) 124 125 126# Exceptions seenthe test passes.
the
__init__andsay_hellomethods I defined are in the listthere are names in the list that I did not define, which leads to the question of where did they come from?
The attributes I defined in the
__init__method are not in the list, because the test called dir onsrc.person.Personwhich is the class, not an instance (copy) of the class
I add a git commit message in the other terminal
git commit -am \ 'add test_attributes_and_methods_of_person_class'
test_attributes_and_methods_of_person_class
RED: make it fail
I add a test for the attributes and methods of an instance/copy of the Person class to see the difference between it and the original
118 '__str__',
119 '__subclasshook__',
120 '__weakref__',
121 'say_hello',
122 ]
123 self.assertEqual(reality, my_expectation)
124
125 def test_attributes_and_methods_of_person_instance(self):
126 an_instance_of_person = src.person.Person(
127 first_name=self.random_first_name,
128 last_name=self.random_last_name,
129 year_of_birth=self.random_year_of_birth,
130 sex=pick_one('F', 'M')
131 )
132
133 reality = dir(an_instance_of_person)
134 my_expectation = [
135 '__class__',
136 '__delattr__',
137 '__dict__',
138 '__dir__',
139 '__doc__',
140 '__eq__',
141 '__firstlineno__',
142 '__format__',
143 '__ge__',
144 '__getattribute__',
145 '__getstate__',
146 '__gt__',
147 '__hash__',
148 '__init__',
149 '__init_subclass__',
150 '__le__',
151 '__lt__',
152 '__module__',
153 '__ne__',
154 '__new__',
155 '__reduce__',
156 '__reduce_ex__',
157 '__repr__',
158 '__setattr__',
159 '__sizeof__',
160 '__static_attributes__',
161 '__str__',
162 '__subclasshook__',
163 '__weakref__',
164 'say_hello',
165 ]
166 self.assertEqual(reality, my_expectation)
167
168
169# Exceptions
the terminal is my friend, and shows AssertionError
AssertionError: Lists differ:
['__c[393 chars]ef__', 'first_name', 'last_name',
'say_hello', 'year_of_birth']
!= ['__c[393 chars]ef__', 'say_hello']
because first_name, last_name and year_of_birth are missing. Why is there no sex?
GREEN: make it pass
I add the missing attributes to my_expectation
133 reality = dir(an_instance_of_person)
134 my_expectation = [
135 '__class__',
136 '__delattr__',
137 '__dict__',
138 '__dir__',
139 '__doc__',
140 '__eq__',
141 '__firstlineno__',
142 '__format__',
143 '__ge__',
144 '__getattribute__',
145 '__getstate__',
146 '__gt__',
147 '__hash__',
148 '__init__',
149 '__init_subclass__',
150 '__le__',
151 '__lt__',
152 '__module__',
153 '__ne__',
154 '__new__',
155 '__reduce__',
156 '__reduce_ex__',
157 '__repr__',
158 '__setattr__',
159 '__sizeof__',
160 '__static_attributes__',
161 '__str__',
162 '__subclasshook__',
163 '__weakref__',
164 'first_name',
165 'last_name',
166 'say_hello',
167 'year_of_birth',
168 ]
169 self.assertEqual(reality, my_expectation)
170
171
172# Exceptions seen
the test passes.
REFACTOR: make it better
I add
sexto the list133 reality = dir(an_instance_of_person) 134 my_expectation = [ 135 '__class__', 136 '__delattr__', 137 '__dict__', 138 '__dir__', 139 '__doc__', 140 '__eq__', 141 '__firstlineno__', 142 '__format__', 143 '__ge__', 144 '__getattribute__', 145 '__getstate__', 146 '__gt__', 147 '__hash__', 148 '__init__', 149 '__init_subclass__', 150 '__le__', 151 '__lt__', 152 '__module__', 153 '__ne__', 154 '__new__', 155 '__reduce__', 156 '__reduce_ex__', 157 '__repr__', 158 '__setattr__', 159 '__sizeof__', 160 '__static_attributes__', 161 '__str__', 162 '__subclasshook__', 163 '__weakref__', 164 'first_name', 165 'last_name', 166 'say_hello', 167 'sex', 168 'year_of_birth', 169 ] 170 self.assertEqual(reality, my_expectation)the terminal is my friend, and shows AssertionError
AssertionError: Lists differ: ['__c[400 chars]'first_name', 'last_name', 'say_hello', 'year_of_birth'] != ['__c[400 chars]'first_name', 'last_name', 'say_hello', 'sex', 'year_of_birth']the
sexattribute is not defined anywhere in thePersonclassI add
self.sexto the__init__method of thePersonclass inperson.py41 # pass 42 # def __init__(): 43 # def __init__(first_name): 44 # def __init__(self, first_name): 45 # def __init__(self, first_name, last_name): 46 def __init__( 47 # self, first_name, last_name, 48 # self, first_name, last_name=None, 49 self, first_name, last_name='doe', 50 # year_of_birth, 51 # year_of_birth, sex, 52 # year_of_birth, sex=None, 53 year_of_birth=None, sex=None, 54 ): 55 # first_name = first_name 56 self.first_name = first_name 57 # last_name = last_name 58 self.last_name = last_name 59 # year_of_birth = year_of_birth 60 self.year_of_birth = year_of_birth 61 self.sex = sex 62 return Nonethe test passes
I remove the commented lines
1import datetime 2 3 4def calculate_age(year_of_birth): 5 return ( 6 datetime.datetime.today().year 7 - year_of_birth 8 ) 9 10 11def say_hello(a_dictionary): 12 return ( 13 f'Hi, my name is {a_dictionary.get("first_name")}' 14 f' {a_dictionary.get("last_name")}' 15 f' and I am {a_dictionary.get("age")}' 16 ) 17 18 19def factory( 20 first_name, year_of_birth, 21 last_name='doe', sex='M', 22 ): 23 return { 24 'first_name': first_name, 25 'last_name': last_name, 26 'sex': sex, 27 'age': calculate_age(year_of_birth), 28 } 29 30 31class Person: 32 33 def __init__( 34 self, first_name, last_name='doe', 35 year_of_birth=None, sex=None, 36 ): 37 self.first_name = first_name 38 self.last_name = last_name 39 self.year_of_birth = year_of_birth 40 self.sex = sex 41 return None 42 43 def say_hello(self): 44 age = calculate_age(self.year_of_birth) 45 return ( 46 f'Hi, my name is {self.first_name}' 47 f' {self.last_name}' 48 f' and I am {age}' 49 )I add a git commit message in the other terminal
git commit -am \ 'add test_attributes_and_methods_of_person_instance'
close the project
I close
test_person.pyandperson.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
code from the chapter
review
There are few problems with what I have now
anyone seeing the tests for the first time has to read the class attributes
class TestPerson(unittest.TestCase): def setUp(self): self.random_first_name = get_random_name() self.random_last_name = get_random_name() this_year = datetime.datetime.now().year self.random_year_of_birth = random.randint( this_year-120, this_year ) self.age = this_year - self.random_year_of_birth def test_factory_w_keyword_arguments(self):to know how they are created then referenced in each test
test_factory_w_keyword_arguments needs the person reading the test to know about double starred expressions
src.person.factory( **a_person, year_of_birth=self.random_year_of_birth, )dict( **a_person, age=self.age, )to know why using the dictionary works in the call to
src.person.factoryand inside another dictionarytest_factory_person_says_hello and test_classy_person_says_hello need the person reading the test to know about f-strings
f'Hi, my name is {self.random_first_name}' f' {self.random_last_name} ' f'and I am {self.age}'
To review
A class is attributes and methods that belong together
A class can be used to represent something
A class attributes is a variable that belongs to a class
classes make it easier to write tests for something
Tip
what is next?
You have gone through a lot of things and know:
Would you like to test what causes AttributeError or Would you like to know where the extra attributes and methods of the Person class came from?
You know enough to go into the world and use Python. If you stopped going through the book at this point, you would be fine because you know how to make classes, functions and can make dictionaries which is what is behind a lot of the things you will encounter.
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.