how to make a person with an object
The factory and say_hello functions use three of the same inputs
first_namelast_nameyear_of_birth
I want to give those values once, and get a representation for a person. I can do that with an object.
I think of objects as attributes (variables) and methods (functions) that belong together (a classification).
preview
I have these tests by the end of the chapter
1import src.person
2
3
4def assert_equal(left, right):
5 assert left == right
8def assert_person_can_say_hello(
9 first_name, last_name,
10 sex, year_of_birth,
11 ):
12 assert_equal(
13 src.person.Person(
14 first_name=first_name,
15 last_name=last_name,
16 sex=sex,
17 year_of_birth=year_of_birth,
18 ).say_hello(),
19 (
20 f'Hello, my name is {first_name}'
21 f' {last_name} and I am'
22 f' {2026-year_of_birth}.'
23 )
24 )
27def assert_say_hello_works(
28 first_name, last_name,
29 year_of_birth,
30 ):
31 assert_equal(
32 src.person.say_hello(
33 first_name=first_name,
34 last_name=last_name,
35 year_of_birth=year_of_birth
36 ),
37 (
38 f'Hello, my name is {first_name}'
39 f' {last_name} and I am'
40 f' {2026-year_of_birth}.'
41 )
42 )
45def assert_person_factory_works(
46 first_name, last_name,
47 sex, year_of_birth
48 ):
49 assert_equal(
50 src.person.person(
51 first_name=first_name,
52 last_name=last_name,
53 sex=sex,
54 year_of_birth=year_of_birth,
55 ),
56 (
57 f'{first_name}, {last_name},'
58 f' {sex}, {year_of_birth}'
59 )
60 )
63def test_joe():
64 first_name = 'joe'
65 last_name = 'blow'
66 sex = 'M'
67 year_of_birth = 1996
68
69 assert_person_factory_works(
70 first_name=first_name,
71 last_name=last_name,
72 sex=sex,
73 year_of_birth=year_of_birth
74 )
75
76 assert_say_hello_works(
77 first_name=first_name,
78 last_name=last_name,
79 year_of_birth=year_of_birth,
80 )
81
82 assert_person_can_say_hello(
83 first_name=first_name,
84 last_name=last_name,
85 sex=sex,
86 year_of_birth=year_of_birth
87 )
90def test_jane():
91 first_name = 'jane'
92 last_name = 'doe'
93 sex = 'F'
94 year_of_birth = 1991
95
96 assert_person_factory_works(
97 first_name=first_name,
98 last_name=last_name,
99 sex=sex,
100 year_of_birth=year_of_birth,
101 )
102
103 assert_say_hello_works(
104 first_name=first_name,
105 last_name=last_name,
106 year_of_birth=year_of_birth,
107 )
108
109 assert_person_can_say_hello(
110 first_name=first_name,
111 last_name=last_name,
112 sex=sex,
113 year_of_birth=year_of_birth,
114 )
117def test_john():
118 first_name = 'john'
119 last_name = 'smith'
120 sex = 'M'
121 year_of_birth = 1580
122
123 assert_person_factory_works(
124 first_name=first_name,
125 last_name=last_name,
126 sex=sex,
127 year_of_birth=year_of_birth,
128 )
129
130 assert_say_hello_works(
131 first_name=first_name,
132 last_name=last_name,
133 year_of_birth=year_of_birth,
134 )
135
136 assert_person_can_say_hello(
137 first_name=first_name,
138 last_name=last_name,
139 sex=sex,
140 year_of_birth=year_of_birth,
141 )
144def test_mary():
145 first_name = 'mary'
146 last_name = 'public'
147 sex = 'F'
148 year_of_birth = 2000
149
150 assert_person_factory_works(
151 first_name=first_name,
152 last_name=last_name,
153 sex=sex,
154 year_of_birth=year_of_birth,
155 )
156
157 assert_say_hello_works(
158 first_name=first_name,
159 last_name=last_name,
160 year_of_birth=year_of_birth,
161 )
162
163 assert_person_can_say_hello(
164 first_name=first_name,
165 last_name=last_name,
166 sex=sex,
167 year_of_birth=year_of_birth,
168 )
171def test_dir_person_class():
172 assert_equal(
173 dir(src.person.Person),
174 [
175 '__class__', '__delattr__', '__dict__',
176 '__dir__', '__doc__', '__eq__',
177 '__firstlineno__', '__format__', '__ge__',
178 '__getattribute__', '__getstate__', '__gt__',
179 '__hash__', '__init__', '__init_subclass__',
180 '__le__', '__lt__', '__module__', '__ne__',
181 '__new__', '__reduce__', '__reduce_ex__',
182 '__repr__', '__setattr__', '__sizeof__',
183 '__static_attributes__', '__str__',
184 '__subclasshook__', '__weakref__', 'say_hello'
185 ]
186 )
188def test_dir_person_instance():
189 assert_equal(
190 dir(
191 src.person.Person(
192 first_name='first_name',
193 last_name='last_name',
194 sex='M',
195 year_of_birth=2026,
196 )
197 ),
198 [
199 '__class__', '__delattr__', '__dict__',
200 '__dir__', '__doc__', '__eq__',
201 '__firstlineno__', '__format__', '__ge__',
202 '__getattribute__', '__getstate__', '__gt__',
203 '__hash__', '__init__', '__init_subclass__',
204 '__le__', '__lt__', '__module__', '__ne__',
205 '__new__', '__reduce__', '__reduce_ex__',
206 '__repr__', '__setattr__', '__sizeof__',
207 '__static_attributes__', '__str__',
208 '__subclasshook__', '__weakref__', 'first_name',
209 'last_name', 'say_hello', 'sex', 'year_of_birth',
210 ]
211 )
212
213
214# Exceptions seen
215# AssertionError
216# NameError
217# TypeError
218# AttributeError
219# SyntaxError
open the project
I open a terminal
I change directory to the project
cd personthe terminal shows I am in the
personfolder.../pumping_python/personI open
test_person.pyfrom thetestsfolderI use pytest-watcher to run the tests automatically
uv run pytest-watcher . --nowthe terminal shows
tests/test_person.py .... [100%] =================== 4 passed in A.BCs ====================
add Person class
I made a function that makes a string to represent a person when I give it first_name, last_name, sex and year_of_birth. I can also represent a person with an object because it is attributes and methods that belong together.
RED: make it fail
I make an instance of an object to represent joe in test_joe in tests/test_person.py
38def test_joe():
39 first_name = 'joe'
40 last_name = 'blow'
41 sex = 'M'
42 year_of_birth = 1996
43
44 assert_person_factory_works(
45 first_name=first_name,
46 last_name=last_name,
47 sex=sex,
48 year_of_birth=year_of_birth
49 )
50
51 assert_say_hello_works(
52 first_name=first_name,
53 last_name=last_name,
54 year_of_birth=year_of_birth,
55 )
56
57 joe = Person(
58 first_name=first_name,
59 last_name=last_name,
60 sex=sex,
61 year_of_birth=year_of_birth,
62 )
63
64
65def test_jane():
the terminal is my friend, and shows NameError
NameError: name 'Person' is not defined
because there is no definition for Person in tests/test_person.py.
GREEN: make it pass
I add a object definition for
Person1import src.person 2 3 4class Person: 5 6 pass 7 8 9def assert_say_hello_works( 10 first_name, last_name, 11 year_of_birth, 12 ):
the constructor method
A constructor method is used to define what happens when an instance (a copy) of a class is made.
I add the constructor method to the Person class so it can take arguments
4class Person: 5 6 # pass 7 def __init__(): 8 return None 9 10 11def assert_say_hello_works( 12 first_name, last_name, 13 year_of_birth, 14 ):the terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'first_name'I am violating the method signature when I call it in a way that is different from its definition.
Person( first_name='joe', last_name='blow', sex='M', year_of_birth=1996, ) └── Person.__init__( first_name='joe', # not in definition last_name='blow', sex='M', year_of_birth=1996, ) └── def __init__(): return Nonewhich raises TypeError since the __init__ method gets called with a name (
first_name) that is not in the parentheses of its definition.I add
first_namein parentheses so that the __init__ method can take input4class Person: 5 6 # pass 7 # def __init__(): 8 def __init__(first_name): 9 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.__init__() got multiple values for argument 'first_name'because a method of an instance takes the instance of the class (
self) it belongs to as the first argument.The test calls the function with four keyword arguments
(first_name, last_name, sex and year_of_birth).Python does not know which value to use for the first argument if I use its name and position.
I add
selfas the first argument4class Person: 5 6 # pass 7 # def __init__(): 8 # def __init__(first_name): 9 def __init__(self, first_name): 10 return Noneselfis Python convention, I can use any name I want.selfis the instance of the class.The terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?selfis for the instance of the class.I am violating the method signature when I call it in a way that is different from its definition
Person( first_name='joe', last_name='blow', sex='M', year_of_birth=1996, ) └── Person.__init__( self, first_name='joe', last_name='blow', # not in definition sex='M', year_of_birth=1996, ) └── def __init__(first_name): return Nonewhich raises TypeError since the
__init__method got called with a name (last_name) that is not in the parentheses of its definition.this is the same as making the person function.
I add
last_nameto the definition of __init__4class Person: 5 6 # pass 7 # def __init__(): 8 # def __init__(first_name): 9 # def __init__(self, first_name): 10 def __init__(self, first_name, last_name): 11 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'sex'selfis the instance of the class.I am violating the method signature when I call it in a way that is different from its definition.
Person( first_name='joe', last_name='blow', sex='M', year_of_birth=1996, ) └── Person.__init__( self, first_name='joe', last_name='blow', sex='M', # not in definition year_of_birth=1996, ) └── def __init__(self, first_name, last_name): return Nonewhich raises TypeError since the
__init__method got called with a name (sex) that is not in the parentheses of its definition.Still the same as making the person function.
I add
sexto the definition of the __init__ method4class Person: 5 6 # pass 7 # def __init__(): 8 # def __init__(first_name): 9 # def __init__(self, first_name): 10 # def __init__(self, first_name, last_name): 11 def __init__( 12 self, first_name, last_name, 13 sex, 14 ): 15 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.__init__() got an unexpected keyword argument 'year_of_birth'selfis the instance of the class.I am violating the method signature when I call it in a way that is different from its definition.
Person( first_name='joe', last_name='blow', sex='M', year_of_birth=1996, ) └── Person.__init__( self, first_name='joe', last_name='blow', sex='M', year_of_birth=1996, # not in definition ) └── def __init__( self, first_name, last_name, sex, ): return Nonewhich raises TypeError because the
__init__method got called with a name (year_of_birth) that is not in the parentheses of its definition.Same as with the person function.
I add
year_of_birthto the definition of the __init__ method4class Person: 5 6 # pass 7 # def __init__(): 8 # def __init__(first_name): 9 # def __init__(self, first_name): 10 # def __init__(self, first_name, last_name): 11 def __init__( 12 self, first_name, last_name, 13 # sex, 14 sex, year_of_birth, 15 ): 16 return Nonethe test passes.
REFACTOR: make it better
I remove the commented lines from the Person class
4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 return None 11 12 13def assert_say_hello_works( 14 first_name, last_name, 15 year_of_birth, 16 ):I open a new terminal then change directories to
personcd personI add a git commit message in the new terminal
git commit -am 'add Person class'
add say_hello method
I made a person say hi with a function, I can also do the same thing with an object because it is attributes and methods that belong together.
RED: make it fail
I add an assertion with a call to the say_hello function with the attributes of
joein test_joe47def test_joe(): 48 first_name = 'joe' 49 last_name = 'blow' 50 sex = 'M' 51 year_of_birth = 1996 52 53 assert_person_factory_works( 54 first_name=first_name, 55 last_name=last_name, 56 sex=sex, 57 year_of_birth=year_of_birth 58 ) 59 60 assert_say_hello_works( 61 first_name=first_name, 62 last_name=last_name, 63 year_of_birth=year_of_birth, 64 ) 65 66 joe = Person( 67 first_name=first_name, 68 last_name=last_name, 69 sex=sex, 70 year_of_birth=year_of_birth, 71 ) 72 73 reality = src.person.say_hello( 74 first_name=joe.first_name, 75 last_name=joe.last_name, 76 year_of_birth=joe.year_of_birth, 77 ) 78 my_expectation = ( 79 f'Hello, my name is {first_name}' 80 f' {last_name} and I am' 81 f' {2026-year_of_birth}.' 82 ) 83 assert reality == my_expectation 84 85 86def test_jane():the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'first_name'because there is nothing named
first_namein the Person class, so Python cannot reach thefirst_nameattribute ofjoe.
GREEN: make it pass
I add
self.first_nameto the __init__ method of the Person class4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 self.first_name 11 return Nonethe terminal still shows AttributeError because all I have done is add a reference to the
first_name, not defined it.I point
self.first_nameto the value forfirst_namewhen the __init__ method is called4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 # self.first_name 11 self.first_name = first_name 12 return Nonethe terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'last_name'. Did you mean: 'first_name'?because there is nothing named
last_namein the Person class, so Python cannot reach thelast_nameattribute ofjoe.I add
self.last_nameand point it to the value forlast_namewhen the __init__ method is called4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 # self.first_name 11 self.first_name = first_name 12 self.last_name = last_name 13 return Nonethe terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'year_of_birth'because there is nothing named
year_of_birthin the Person class, so Python cannot reach theyear_of_birthattribute ofjoe.I add
self.year_of_birthand point it to the value foryear_of_birthwhen the __init__ constructor method is called4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 # self.first_name 11 self.first_name = first_name 12 self.last_name = last_name 13 self.year_of_birth = year_of_birth 14 return Nonethe test passes.
├── first_name = 'joe' ├── last_name = 'blow' ├── sex = 'M' ├── year_of_birth = 1996 └── joe = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( self, first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── def __init__( self, first_name, last_name, sex, year_of_birth, ): ├── self.first_name = 'joe' ├── self.last_name = 'blow' ├── self.year_of_birth = 1996 └── return Noneselfis the instance of the class,joein this case.src.person.say_hello( first_name=joe.first_name, last_name=joe.last_name, year_of_birth=joe.year_of_birth, ) └── src/ └── person/ └── __init__.py └── def say_hello( first_name, last_name, year_of_birth, ): ├── first_name = 'joe' ├── last_name = 'blow' ├── year_of_birth = 1996 └── return ( f'Hello, my name is {first_name}' f' {last_name} and I am' f' {2026-year_of_birth}.' ) return 'Hello, my name is joe blow and I am 30.'
REFACTOR: make it better
I remove the commented line from the Person class
4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 self.first_name = first_name 11 self.last_name = last_name 12 self.year_of_birth = year_of_birth 13 return None 14 15 16def assert_say_hello_works( 17 first_name, last_name, 18 year_of_birth, 19 ):I change the call to
src.person.say_helloin test_joe to a call to the say_hello method of the Person class69 joe = Person( 70 first_name=first_name, 71 last_name=last_name, 72 sex=sex, 73 year_of_birth=year_of_birth, 74 ) 75 76 # reality = src.person.say_hello( 77 reality = Person.say_hello( 78 first_name=joe.first_name, 79 last_name=joe.last_name, 80 year_of_birth=joe.year_of_birth, 81 ) 82 my_expectation = ( 83 f'Hello, my name is {first_name}' 84 f' {last_name} and I am' 85 f' {2026-year_of_birth}.' 86 ) 87 assert reality == my_expectation 88 89 90def test_jane():the terminal is my friend, and shows AttributeError
AttributeError: type object 'Person' has no attribute 'say_hello'because the test calls the say_hello method which does not yet exist in the Person class.
I add a method definition for
say_helloto the Person class4class Person: 5 6 def __init__( 7 self, first_name, last_name, 8 sex, year_of_birth, 9 ): 10 self.first_name = first_name 11 self.last_name = last_name 12 self.year_of_birth = year_of_birth 13 return None 14 15 def say_hello(): 16 return None 17 18 19def assert_say_hello_works( 20 first_name, last_name, 21 year_of_birth, 22 ):the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got an unexpected keyword argument 'first_name'because the say_hello method got called with a name (
first_name) that is not in the parentheses of its definition.I add
first_nameto the method definition15 # def say_hello(): 16 def say_hello(first_name): 17 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?because the say_hello method got called with a name (
last_name) that is not in the parentheses of its definition.I add
last_nameto the method definition15 # def say_hello(): 16 # def say_hello(first_name): 17 def say_hello(first_name, last_name): 18 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got an unexpected keyword argument 'year_of_birth'because the say_hello method got called with a name (
year_of_birth) that is not in the parentheses of its definition.I add
year_of_birthto the method definition15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 def say_hello(first_name, last_name, year_of_birth): 19 return Nonethe terminal is my friend, and shows AssertionError
AssertionError: assert None == 'Hello, my name is joe blow and I am 30.'I change the return statement to match
15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 def say_hello(first_name, last_name, year_of_birth): 19 # return None 20 return 'Hello, my name is joe blow and I am 30.' 21 22 23def assert_say_hello_works( 24 first_name, last_name, 25 year_of_birth, 26 ):the test passes.
Person.say_hello( first_name=joe.first_name, last_name=joe.last_name, year_of_birth=joe.year_of_birth, ) └── class Person: └── def say_hello(first_name, last_name, year_of_birth): ├── first_name = 'joe' ├── last_name = 'blow' ├── year_of_birth = 1996 └── return 'Hello, my name is joe blow and I am 30.'I add a call to the say_hello method of the Person class in test_jane
97def test_jane(): 98 first_name = 'jane' 99 last_name = 'doe' 100 sex = 'F' 101 year_of_birth = 1991 102 103 assert_person_factory_works( 104 first_name=first_name, 105 last_name=last_name, 106 sex=sex, 107 year_of_birth=year_of_birth, 108 ) 109 110 assert_say_hello_works( 111 first_name=first_name, 112 last_name=last_name, 113 year_of_birth=year_of_birth, 114 ) 115 116 jane = Person( 117 first_name=first_name, 118 last_name=last_name, 119 sex=sex, 120 year_of_birth=year_of_birth, 121 ) 122 123 reality = Person.say_hello( 124 first_name=jane.first_name, 125 last_name=jane.last_name, 126 year_of_birth=jane.year_of_birth, 127 ) 128 my_expectation = ( 129 f'Hello, my name is {first_name}' 130 f' {last_name} and I am' 131 f' {2026-year_of_birth}.' 132 ) 133 assert reality == my_expectation 134 135 136def test_john():the terminal is my friend, and shows AssertionError
AssertionError: assert 'Hello, my name ... and I am 30.' == 'Hello, my name ... and I am 35.'I change the return statement to an f-string like I did with the say_hello function
15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 def say_hello(first_name, last_name, year_of_birth): 19 # return None 20 # return 'Hello, my name is joe blow and I am 30.' 21 return ( 22 f'Hello, my name is {first_name}' 23 f' {last_name} and I am' 24 f' {2026-year_of_birth}.' 25 ) 26 27 28def assert_say_hello_works( 29 first_name, last_name, 30 year_of_birth, 31 ):the test passes.
├── first_name = 'jane' ├── last_name = 'doe' ├── sex = 'F' ├── year_of_birth = 1991 └── jane = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( self, first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── def __init__( self, first_name, last_name, sex, year_of_birth, ): ├── self.first_name = 'jane' ├── self.last_name = 'doe' ├── self.year_of_birth = 1991 └── return NonePerson.say_hello( first_name=jane.first_name, last_name=jane.last_name, year_of_birth=jane.year_of_birth, ) └── def say_hello(first_name, last_name, year_of_birth): ├── first_name = 'jane' ├── last_name = 'doe' ├── year_of_birth = 1991 └── return ( f'Hello, my name is {first_name}' f' {last_name} and I am' f' {2026-year_of_birth}.' ) return 'Hello, my name is jane doe and I am 35.'I add an instance (copy) of the Person class to the call to the say_hello method from test_joe because the instance has the attributes I use in the method
88 # reality = src.person.say_hello( 89 reality = Person.say_hello( 90 person=joe, 91 first_name=joe.first_name, 92 last_name=joe.last_name, 93 year_of_birth=joe.year_of_birth, 94 ) 95 my_expectation = ( 96 f'Hello, my name is {first_name}' 97 f' {last_name} and I am' 98 f' {2026-year_of_birth}.' 99 ) 100 assert reality == my_expectation 101 102 103def test_jane():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got an unexpected keyword argument 'person'because the say_hello method got called with a name (
person) that is not in the parentheses of its definition.I add
personto the method definition for say_hello15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 def say_hello( 20 person, first_name, last_name, year_of_birth, 21 ): 22 # return None 23 # return 'Hello, my name is joe blow and I am 30.' 24 return ( 25 f'Hello, my name is {first_name}' 26 f' {last_name} and I am' 27 f' {2026-year_of_birth}.' 28 )the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() missing 1 required positional argument: 'person'I have to make the same change to test_jane
I add the
personkeyword argument to the call to the say_hello method from test_jane132 reality = Person.say_hello( 133 person=jane, 134 first_name=jane.first_name, 135 last_name=jane.last_name, 136 year_of_birth=jane.year_of_birth, 137 ) 138 my_expectation = ( 139 f'Hello, my name is {first_name}' 140 f' {last_name} and I am' 141 f' {2026-year_of_birth}.' 142 ) 143 assert reality == my_expectation 144 145 146def test_john():the test passes.
I change the return statement of the say_hello method to use the attributes of the class instance it receives as input
15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 def say_hello( 20 person, first_name, last_name, year_of_birth, 21 ): 22 # return None 23 # return 'Hello, my name is joe blow and I am 30.' 24 return ( 25 # f'Hello, my name is {first_name}' 26 # f' {last_name} and I am' 27 # f' {2026-year_of_birth}.' 28 f'Hello, my name is {person.first_name}' 29 f' {person.last_name} and I am' 30 f' {2026-person.year_of_birth}.' 31 ) 32 33 34def assert_say_hello_works( 35 first_name, last_name, 36 year_of_birth, 37 ):the tests are still green
instance = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( self, first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── def __init__( self, first_name, last_name, sex, year_of_birth, ): ├── self.first_name = first_name ├── self.last_name = last_name ├── self.year_of_birth = year_of_birth └── return NonePerson.say_hello( person=instance, first_name=instance.first_name, last_name=instance.last_name, year_of_birth=instance.year_of_birth, ) └── Person.say_hello( person=instance, first_name=instance.first_name, last_name=instance.last_name, year_of_birth=instance.year_of_birth, ) └── def say_hello( person, first_name, last_name, year_of_birth, ): ├── person = instance ├── first_name = first_name ├── last_name = last_name ├── year_of_birth = year_of_birth └── return ( f'Hello, my name is {person.first_name}' f' {person.last_name} and I am' f' {2026-person.year_of_birth}.' ) return ( f'Hello, my name is {instance.first_name}' f' {instance.last_name} and I am' f' {2026-instance.year_of_birth}.' )I remove the
first_name,last_nameandyear_of_birtharguments from the call to the say_hello method from test_joe since they are repetitions of the object attributes94 # reality = src.person.say_hello( 95 reality = Person.say_hello( 96 person=joe, 97 # first_name=joe.first_name, 98 # last_name=joe.last_name, 99 # year_of_birth=joe.year_of_birth, 100 ) 101 my_expectation = ( 102 f'Hello, my name is {first_name}' 103 f' {last_name} and I am' 104 f' {2026-year_of_birth}.' 105 ) 106 assert reality == my_expectation 107 108 109def test_jane():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() missing 3 required positional arguments: 'first_name', 'last_name', and 'year_of_birth'I remove
first_name,last_nameandyear_of_birthfrom the definition of the say_hello method15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 def say_hello( 20 # person, first_name, last_name, year_of_birth, 21 person, 22 ): 23 # return None 24 # return 'Hello, my name is joe blow and I am 30.' 25 return ( 26 # f'Hello, my name is {first_name}' 27 # f' {last_name} and I am' 28 # f' {2026-year_of_birth}.' 29 f'Hello, my name is {person.first_name}' 30 f' {person.last_name} and I am' 31 f' {2026-person.year_of_birth}.' 32 )the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got an unexpected keyword argument 'first_name'because the say_hello method got called from test_jane with a name (
first_name) that is not in the parentheses of its definition.I remove the
first_name,last_nameandyear_of_birtharguments from the call from test_jane136 reality = Person.say_hello( 137 person=jane, 138 # first_name=jane.first_name, 139 # last_name=jane.last_name, 140 # year_of_birth=jane.year_of_birth, 141 ) 142 my_expectation = ( 143 f'Hello, my name is {first_name}' 144 f' {last_name} and I am' 145 f' {2026-year_of_birth}.' 146 ) 147 assert reality == my_expectation 148 149 150def test_john():the test passes. This is still a repetition, I give an instance (copy) of the Person class as input to the say_hello method of the same object.
I change the call to the say_hello method from test_jane because the say_hello method is in the Person class so its copies also have the say_hello method
136 # reality = Person.say_hello( 137 reality = jane.say_hello( 138 person=jane, 139 # first_name=jane.first_name, 140 # last_name=jane.last_name, 141 # year_of_birth=jane.year_of_birth, 142 ) 143 my_expectation = ( 144 f'Hello, my name is {first_name}' 145 f' {last_name} and I am' 146 f' {2026-year_of_birth}.' 147 ) 148 assert reality == my_expectation 149 150 151def test_john():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() got multiple values for argument 'person'because a method of an instance takes the instance of the class (
self) it belongs to as the first argument and I gave a value when I called the method.
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 that belongs to the object, so I do not send more than what the method needs. I add @staticmethod<what is the staticmethod decorator?>` to the say_hello method15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 @staticmethod 20 def say_hello( 21 # person, first_name, last_name, year_of_birth, 22 person, 23 ):the test passes.
I change the call to
Person.say_hellofrom test_joe because the say_hello method is in the Person class, there is no need for it to take a copy of the Person class as input since it should be able to use its own attributes96 # reality = src.person.say_hello( 97 # reality = Person.say_hello( 98 reality = joe.say_hello( 99 # person=joe, 100 # first_name=joe.first_name, 101 # last_name=joe.last_name, 102 # year_of_birth=joe.year_of_birth, 103 ) 104 my_expectation = ( 105 f'Hello, my name is {first_name}' 106 f' {last_name} and I am' 107 f' {2026-year_of_birth}.' 108 ) 109 assert reality == my_expectation 110 111 112def test_jane():the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() missing 1 required positional argument: 'person'I make
personoptional in the say_hello method15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 @staticmethod 20 def say_hello( 21 # person, first_name, last_name, year_of_birth, 22 # person, 23 person=None, 24 ):the terminal is my friend, and shows AttributeError
AttributeError: 'NoneType' object has no attribute 'first_name'I change
person.toself.in the return statement15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 @staticmethod 20 def say_hello( 21 # person, first_name, last_name, year_of_birth, 22 # person, 23 person=None, 24 ): 25 # return None 26 # return 'Hello, my name is joe blow and I am 30.' 27 return ( 28 # f'Hello, my name is {first_name}' 29 # f' {last_name} and I am' 30 # f' {2026-year_of_birth}.' 31 # f'Hello, my name is {person.first_name}' 32 # f' {person.last_name} and I am' 33 # f' {2026-person.year_of_birth}.' 34 f'Hello, my name is {self.first_name}' 35 f' {self.last_name} and I am' 36 f' {2026-self.year_of_birth}.' 37 )the terminal is my friend, and shows NameError
NameError: name 'self' is not definedI add
selfto the parentheses15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 @staticmethod 20 def say_hello( 21 # person, first_name, last_name, year_of_birth, 22 # person, 23 # person=None, 24 self, person=None, 25 ):the terminal is my friend, and shows TypeError
TypeError: Person.say_hello() missing 1 required positional argument: 'self'I remove the staticmethod decorator because I no longer need it since the say_hello method is using object attributes
15 # def say_hello(): 16 # def say_hello(first_name): 17 # def say_hello(first_name, last_name): 18 # def say_hello(first_name, last_name, year_of_birth): 19 # @staticmethod 20 def say_hello( 21 # person, first_name, last_name, year_of_birth, 22 # person 23 # person=None 24 self, person=None 25 ):the test passes because
instance = Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── Person.__init__( self, first_name='joe', last_name='blow', sex='M', year_of_birth=1996, ) └── def __init__( self, first_name, last_name, sex, year_of_birth, ): ├── self.first_name = first_name ├── self.last_name = last_name ├── self.year_of_birth = year_of_birth └── return Noneselfis the instance of the class,instancein this caseinstance.say_hello() └── class Person: └── def say_hello(self, person=None): ├── self = instance └── return ( f'Hello, my name is {self.first_name}' f' {self.last_name} and I am' f' {2026-self.year_of_birth}.' ) return ( f'Hello, my name is {instance.first_name}' f' {instance.last_name} and I am' f' {2026-instance.year_of_birth}.' )a simple way to think of
instance.say_hello()isinstance = Person() instance.say_hello() == Person().say_hello() instance.say_hello() == Person().say_hello(Person()) instance.say_hello() == instance.say_hello(Person()) instance.say_hello() == instance.say_hello(instance)I do not need to pass
joeas input to the say_hello method since it isself.I remove
person=janefrom the call to the say_hello method from test_jane because the say_hello method is in the Person class143 # reality = Person.say_hello( 144 reality = jane.say_hello( 145 # person=jane, 146 # first_name=jane.first_name, 147 # last_name=jane.last_name, 148 # year_of_birth=jane.year_of_birth, 149 ) 150 my_expectation = ( 151 f'Hello, my name is {first_name}' 152 f' {last_name} and I am' 153 f' {2026-year_of_birth}.' 154 ) 155 assert reality == my_expectation 156 157 158def test_john():the test is still green.
I add an assertion for the say_hello method to test_john
158def test_john(): 159 first_name = 'john' 160 last_name = 'smith' 161 sex = 'M' 162 year_of_birth = 1580 163 164 assert_person_factory_works( 165 first_name=first_name, 166 last_name=last_name, 167 sex=sex, 168 year_of_birth=year_of_birth, 169 ) 170 171 assert_say_hello_works( 172 first_name=first_name, 173 last_name=last_name, 174 year_of_birth=year_of_birth, 175 ) 176 177 john = Person( 178 first_name=first_name, 179 last_name=last_name, 180 sex=sex, 181 year_of_birth=year_of_birth, 182 ) 183 184 reality = john.say_hello() 185 my_expectation = ( 186 f'Hello, my name is {first_name}' 187 f' {last_name} and I am' 188 f' {2026-year_of_birth}.' 189 ) 190 assert reality == None 191 192 193def test_mary():the terminal is my friend, and shows AssertionError
AssertionError: assert 'Hello, my name is john smith and I am 446.' == NoneI change my expectation to match
realityin test_john184 reality = john.say_hello() 185 my_expectation = ( 186 f'Hello, my name is {first_name}' 187 f' {last_name} and I am' 188 f' {2026-year_of_birth}.' 189 ) 190 # assert reality == None 191 assert reality == my_expectation 192 193 194def test_mary():the test passes.
I add an assertion for the say_hello method to test_mary
194def test_mary(): 195 first_name = 'mary' 196 last_name = 'public' 197 sex = 'F' 198 year_of_birth = 2000 199 200 assert_person_factory_works( 201 first_name=first_name, 202 last_name=last_name, 203 sex=sex, 204 year_of_birth=year_of_birth, 205 ) 206 207 assert_say_hello_works( 208 first_name=first_name, 209 last_name=last_name, 210 year_of_birth=year_of_birth, 211 ) 212 213 mary = Person( 214 first_name=first_name, 215 last_name=last_name, 216 sex=sex, 217 year_of_birth=year_of_birth, 218 ) 219 220 reality = mary.say_hello() 221 my_expectation = ( 222 f'Hello, my name is {first_name}' 223 f' {last_name} and I am' 224 f' {2026-year_of_birth}.' 225 ) 226 assert reality == None 227 228 229# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: assert 'Hello, my name is mary public and I am 26.' == NoneI change my expectation to match
realityin test_mary220 reality = mary.say_hello() 221 my_expectation = ( 222 f'Hello, my name is {first_name}' 223 f' {last_name} and I am' 224 f' {2026-year_of_birth}.' 225 ) 226 # assert reality == None 227 assert reality == my_expectation 228 229 230# Exceptions seenthe test passes.
I add a git commit message in the other terminal
git commit -am 'add say_hello method'
Since the say_hello method is the same as the say_hello function I could have used the say_hello method to call the say_hello function to get the same result
def say_hello(self):
return say_hello(
first_name=self.first_name,
last_name=self.last_name,
year_of_birth=self.year_of_birth,
)
separate and equal Person class
RED: make it fail
I go back to the terminal where the tests are running
I change
maryin test_mary to be an instance of the Person class of thepersonmodule in thesrcfolder instead of the Person class intests/test_person.py213 # mary = Person( 214 mary = src.person.Person( 215 first_name=first_name, 216 last_name=last_name, 217 sex=sex, 218 year_of_birth=year_of_birth, 219 ) 220 221 reality = mary.say_hello() 222 my_expectation = ( 223 f'Hello, my name is {first_name}' 224 f' {last_name} and I am' 225 f' {2026-year_of_birth}.' 226 ) 227 # assert reality == None 228 assert reality == my_expectation 229 230 231# Exceptions seenthe terminal is my friend, and shows AttributeError
AttributeError: module 'src.person' has no attribute 'Person'because there is nothing with that name in
src/person/__init__.py.
GREEN: make it pass
I add the name to
src/person/__init__.py1Person 2 3 4def say_hello( 5 first_name, last_name, year_of_birth 6 ):the terminal is my friend, and shows NameError
NameError: name 'Person' is not definedI point
Personto None to define it1# Person 2Person = None 3 4 5def say_hello( 6 first_name, last_name, year_of_birth 7 ):the terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause I cannot call None like a function.
I change
Personto a function1# Person 2# Person = None 3def Person(): 4 return None 5 6 7def say_hello( 8 first_name, last_name, year_of_birth 9 ):the terminal is my friend, and shows TypeError
TypeError: Person() got an unexpected keyword argument 'first_name'because the
Personfunction got called with a name (first_name) that is not in the parentheses of its definition.I add
first_nameto the parentheses1# Person 2# Person = None 3# def Person(): 4def Person(first_name): 5 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person() got an unexpected keyword argument 'last_name'. Did you mean 'first_name'?because the
Personfunction got called with a name (last_name) that is not in the parentheses of its definition.I add
last_nameto the parentheses1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5def Person(first_name, last_name): 6 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person() got an unexpected keyword argument 'sex'because the
Personfunction got called with a name (sex) that is not in the parentheses of its definition.I add
sexto the parentheses1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5# def Person(first_name, last_name): 6def Person(first_name, last_name, sex): 7 return Nonethe terminal is my friend, and shows TypeError
TypeError: Person() got an unexpected keyword argument 'year_of_birth'because the
Personfunction got called with a name (year_of_birth) that is not in the parentheses of its definition.I add
year_of_birthto the parentheses1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5# def Person(first_name, last_name): 6# def Person(first_name, last_name, sex): 7def Person( 8 first_name, last_name, 9 sex, year_of_birth, 10 ): 11 return Nonethe terminal is my friend, and shows AttributeError
AttributeError: 'NoneType' object has no attribute 'say_hello'because the function I just made returns None
├── first_name = 'mary' ├── last_name = 'public' ├── sex = 'F' ├── year_of_birth = 2000 └── mary = src.person.Person( first_name=first_name, last_name=last_name, sex=sex, year_of_birth=year_of_birth, ) └── src/ └── person/ └── __init__.py └── def Person( first_name, last_name, sex, year_of_birth, ): └── return Nonereality = mary.say_hello() reality = None.say_hello()which raises AttributeError since None does not have anything named
say_helloin it.I change
Personto an object1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5# def Person(first_name, last_name): 6# def Person(first_name, last_name, sex): 7# def Person( 8class Person( 9 first_name, last_name, 10 sex, year_of_birth, 11 ): 12 return Nonethe terminal is my friend, and shows SyntaxError
SyntaxError: 'return' outside functionI add SyntaxError to the list of Exceptions seen, in
tests/test_person.py231# Exceptions seen 232# AssertionError 233# NameError 234# TypeError 235# AttributeError 236# SyntaxErrorI change the return statement in the
Personobject to the pass keyword, insrc/person/__init__.py1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5# def Person(first_name, last_name): 6# def Person(first_name, last_name, sex): 7# def Person( 8class Person( 9 first_name, last_name, 10 sex, year_of_birth, 11 ): 12 # return None 13 pass 14 15 16def say_hello( 17 first_name, last_name, year_of_birth 18 ):the terminal is my friend, and shows NameError
NameError: name 'first_name' is not definedbecause the only definitions for
first_nameare in the say_hello and person functions insrc/person/__init__.py.I add the constructor method to handle the inputs
1# Person 2# Person = None 3# def Person(): 4# def Person(first_name): 5# def Person(first_name, last_name): 6# def Person(first_name, last_name, sex): 7# def Person( 8# class Person( 9# first_name, last_name, 10# sex, year_of_birth, 11# ): 12class Person: 13 14 def __init__( 15 first_name, last_name, 16 sex, year_of_birth, 17 ): 18 # return None 19 pass 20 21 22def say_hello( 23 first_name, last_name, year_of_birth 24 ):the terminal is my friend, and shows TypeError
TypeError: Person.__init__() got multiple values for argument 'first_name'because a method of an instance takes the instance of the class (
self) it belongs to as the first argument.I add
selfto the constructor method12class Person: 13 14 def __init__( 15 # first_name, last_name, 16 self, first_name, last_name, 17 sex, year_of_birth, 18 ): 19 # return None 20 passthe terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'say_hello'I add the name to the Person class
12class Person: 13 14 say_hello 15 16 def __init__( 17 # first_name, last_name, 18 self, first_name, last_name, 19 sex, year_of_birth, 20 ): 21 # return None 22 passthe terminal is my friend, and shows NameError
NameError: name 'say_hello' is not definedI point
say_helloto None to define it12class Person: 13 14 # say_hello 15 say_hello = None 16 17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ): 22 # return None 23 passthe terminal is my friend, and shows TypeError
TypeError: 'NoneType' object is not callablebecause I cannot call None like a function.
I change
say_helloto a method12class Person: 13 14 # say_hello 15 # say_hello = None 16 17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ): 22 # return None 23 pass 24 25 def say_hello(): 26 return None 27 28 29def say_hello( 30 first_name, last_name, year_of_birth 31 ):TypeError: Person.say_hello() takes 0 positional arguments but 1 was givenI add a name to the parentheses
25 # def say_hello(): 26 def say_hello(argument): 27 return Nonethe terminal is my friend, and shows AssertionError
AssertionError: assert None == 'Hello, my name is mary public and I am 26.'I copy and paste the string from the terminal to use as the return statement
25 # def say_hello(): 26 def say_hello(argument): 27 # return None 28 return 'Hello, my name is mary public and I am 26.' 29 30 31def say_hello( 32 first_name, last_name, year_of_birth 33 ):the test passes.
REFACTOR: make it better
I remove the commented lines from test_mary in
tests/test_person.py194def test_mary(): 195 first_name = 'mary' 196 last_name = 'public' 197 sex = 'F' 198 year_of_birth = 2000 199 200 assert_person_factory_works( 201 first_name=first_name, 202 last_name=last_name, 203 sex=sex, 204 year_of_birth=year_of_birth, 205 ) 206 207 assert_say_hello_works( 208 first_name=first_name, 209 last_name=last_name, 210 year_of_birth=year_of_birth, 211 ) 212 213 mary = src.person.Person( 214 first_name=first_name, 215 last_name=last_name, 216 sex=sex, 217 year_of_birth=year_of_birth, 218 ) 219 220 reality = mary.say_hello() 221 my_expectation = ( 222 f'Hello, my name is {first_name}' 223 f' {last_name} and I am' 224 f' {2026-year_of_birth}.' 225 ) 226 assert reality == my_expectation 227 228 229# Exceptions seenI change
johnin test_john to be an instance of the Person class of thepersonmodule in thesrcfolder171 assert_say_hello_works( 172 first_name=first_name, 173 last_name=last_name, 174 year_of_birth=year_of_birth, 175 ) 176 177 # john = Person( 178 john = src.person.Person( 179 first_name=first_name, 180 last_name=last_name, 181 sex=sex, 182 year_of_birth=year_of_birth, 183 ) 184 185 reality = john.say_hello() 186 my_expectation = ( 187 f'Hello, my name is {first_name}' 188 f' {last_name} and I am' 189 f' {2026-year_of_birth}.' 190 ) 191 # assert reality == None 192 assert reality == my_expectation 193 194 195def test_mary():the terminal is my friend, and shows AssertionError
AssertionError: assert 'Hello, my name ... and I am 26.' == 'Hello, my name ...and I am 446.'I change the return statement of the say_hello method to return the input, in
src/person/__init__.py25 # def say_hello(): 26 def say_hello(argument): 27 # return None 28 # return 'Hello, my name is mary public and I am 26.' 29 return argument 30 31 32def say_hello( 33 first_name, last_name, year_of_birth 34 ):the terminal is my friend, and shows AssertionError
AssertionError: assert <src.person.Person object at 0xffffb012cd34> == 'Hello, my name is mary public and I am 26.'because
argumentis an instance (a copy) of the Person class.I change the return statement to use object attributes in an f-string
25 # def say_hello(): 26 def say_hello(argument): 27 # return None 28 # return 'Hello, my name is mary public and I am 26.' 29 # return argument 30 return ( 31 f'Hello, my name is {argument.first_name}' 32 f' {argument.last_name} and I am' 33 f' {2026-argument.year_of_birth}.' 34 ) 35 36 37def say_hello( 38 first_name, last_name, year_of_birth 39 ):the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'first_name'because I have not defined a object attribute named
first_name.I add
self.first_nameto the __init__ constructor method12class Person: 13 14 # say_hello 15 # say_hello = None 16 17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ): 22 # return None 23 # pass 24 self.first_name = first_name 25 26 # def say_hello():the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'last_name'. Did you mean: 'first_name'?because I have not defined a object attribute named
last_name.I add
self.last_nameto the __init__ method17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ): 22 # return None 23 # pass 24 self.first_name = first_name 25 self.last_name = last_name 26 27 # def say_hello():the terminal is my friend, and shows AttributeError
AttributeError: 'Person' object has no attribute 'year_of_birth'because I have not defined a object attribute named
year_of_birth.I add
self.year_of_birthto the __init__ constructor method17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ): 22 # return None 23 # pass 24 self.first_name = first_name 25 self.last_name = last_name 26 self.year_of_birth = year_of_birth 27 28 # def say_hello():the test passes.
I change
argumenttoselfin the say_hello method to follow Python convention28 # def say_hello(): 29 # def say_hello(argument): 30 def say_hello(self): 31 # return None 32 # return 'Hello, my name is mary public and I am 26.' 33 # return argument 34 return ( 35 # f'Hello, my name is {argument.first_name}' 36 # f' {argument.last_name} and I am' 37 # f' {2026-argument.year_of_birth}.' 38 f'Hello, my name is {self.first_name}' 39 f' {self.last_name} and I am' 40 f' {2026-self.year_of_birth}.' 41 ) 42 43 44def say_hello( 45 first_name, last_name, year_of_birth 46 ):the test is still green because a method of an instance takes the instance of the class (
self) it belongs to as the first argument which meansinstance = Person() instance.say_hello() == Person().say_hello() instance.say_hello() == Person().say_hello(Person()) instance.say_hello() == instance.say_hello(Person()) instance.say_hello() == instance.say_hello(instance)I do not need to pass the instance as input to the say_hello method since it is
self.I remove the commented lines from test_john in
tests/test_person.py158def test_john(): 159 first_name = 'john' 160 last_name = 'smith' 161 sex = 'M' 162 year_of_birth = 1580 163 164 assert_person_factory_works( 165 first_name=first_name, 166 last_name=last_name, 167 sex=sex, 168 year_of_birth=year_of_birth, 169 ) 170 171 assert_say_hello_works( 172 first_name=first_name, 173 last_name=last_name, 174 year_of_birth=year_of_birth, 175 ) 176 177 john = src.person.Person( 178 first_name=first_name, 179 last_name=last_name, 180 sex=sex, 181 year_of_birth=year_of_birth, 182 ) 183 184 reality = john.say_hello() 185 my_expectation = ( 186 f'Hello, my name is {first_name}' 187 f' {last_name} and I am' 188 f' {2026-year_of_birth}.' 189 ) 190 assert reality == my_expectation 191 192 193def test_mary():I change
janein test_jane to be an instance of the Person class of thepersonmodule in thesrcfolder130 assert_say_hello_works( 131 first_name=first_name, 132 last_name=last_name, 133 year_of_birth=year_of_birth, 134 ) 135 136 # jane = Person( 137 jane = src.person.Person( 138 first_name=first_name, 139 last_name=last_name, 140 sex=sex, 141 year_of_birth=year_of_birth, 142 ) 143 144 # reality = Person.say_hello( 145 reality = jane.say_hello( 146 # person=jane, 147 # first_name=jane.first_name, 148 # last_name=jane.last_name, 149 # year_of_birth=jane.year_of_birth, 150 ) 151 my_expectation = ( 152 f'Hello, my name is {first_name}' 153 f' {last_name} and I am' 154 f' {2026-year_of_birth}.' 155 ) 156 assert reality == my_expectation 157 158 159def test_john():the test is still green.
I remove the commented lines from test_jane
117def test_jane(): 118 first_name = 'jane' 119 last_name = 'doe' 120 sex = 'F' 121 year_of_birth = 1991 122 123 assert_person_factory_works( 124 first_name=first_name, 125 last_name=last_name, 126 sex=sex, 127 year_of_birth=year_of_birth, 128 ) 129 130 assert_say_hello_works( 131 first_name=first_name, 132 last_name=last_name, 133 year_of_birth=year_of_birth, 134 ) 135 136 jane = src.person.Person( 137 first_name=first_name, 138 last_name=last_name, 139 sex=sex, 140 year_of_birth=year_of_birth, 141 ) 142 143 reality = jane.say_hello() 144 my_expectation = ( 145 f'Hello, my name is {first_name}' 146 f' {last_name} and I am' 147 f' {2026-year_of_birth}.' 148 ) 149 assert reality == my_expectation 150 151 152def test_john():I change
joein test_joe to be an instance of the Person class of thepersonmodule in thesrcfolder88 assert_say_hello_works( 89 first_name=first_name, 90 last_name=last_name, 91 year_of_birth=year_of_birth, 92 ) 93 94 # joe = Person( 95 joe = src.person.Person( 96 first_name=first_name, 97 last_name=last_name, 98 sex=sex, 99 year_of_birth=year_of_birth, 100 ) 101 102 # reality = src.person.say_hello( 103 # reality = Person.say_hello( 104 reality = joe.say_hello( 105 # person=joe, 106 # first_name=joe.first_name, 107 # last_name=joe.last_name, 108 # year_of_birth=joe.year_of_birth, 109 ) 110 my_expectation = ( 111 f'Hello, my name is {first_name}' 112 f' {last_name} and I am' 113 f' {2026-year_of_birth}.' 114 ) 115 assert reality == my_expectation 116 117 118def test_jane():the test is still green.
I remove the commented lines from test_joe
75def test_joe(): 76 first_name = 'joe' 77 last_name = 'blow' 78 sex = 'M' 79 year_of_birth = 1996 80 81 assert_person_factory_works( 82 first_name=first_name, 83 last_name=last_name, 84 sex=sex, 85 year_of_birth=year_of_birth 86 ) 87 88 assert_say_hello_works( 89 first_name=first_name, 90 last_name=last_name, 91 year_of_birth=year_of_birth, 92 ) 93 94 joe = src.person.Person( 95 first_name=first_name, 96 last_name=last_name, 97 sex=sex, 98 year_of_birth=year_of_birth, 99 ) 100 101 reality = joe.say_hello() 102 my_expectation = ( 103 f'Hello, my name is {first_name}' 104 f' {last_name} and I am' 105 f' {2026-year_of_birth}.' 106 ) 107 assert reality == my_expectation 108 109 110def test_jane():I remove the Person class from
tests/test_person.pyimport src.person def assert_say_hello_works( first_name, last_name, year_of_birth, ):all the tests are still green because the instances of the Person class that were in
tests/test_person.pyare now of the Person class insrc/person/__init__.py.src.person.Person src/ └── person/ └── __init__.py └── class Person: └── def __init__( self, first_name, last_name, sex, year_of_birth, ): ├── self.first_name = first_name ├── self.last_name = last_name └── self.year_of_birth = year_of_birthI add a git commit message in the other terminal
git commit -am \ 'move Person class to src'the terminal shows a summary of the changes then goes back to the command line.
extract assert_person_can_say_hello function
test_joe, test_jane, test_john and test_mary use the same process to test the say_hello method, they
make an instance of the Person class with values for
first_name,last_name,sexandyear_of_birthcall the say_hello method of the Person class with the values of
first_name,last_nameandyear_of_birthmake a string with the values of
first_name,last_nameandyear_of_birthassert that the result of the call to the say_hello method is equal to the string
instance = src.person.Person(
first_name=first_name,
last_name=last_name,
sex=sex,
year_of_birth=year_of_birth,
)
reality = instance.say_hello()
my_expectation = (
f'Hello, my name is {first_name}'
f' {last_name} and I am'
f' {2026-year_of_birth}.'
)
assert reality == my_expectation
I can make a function that takes in first_name, last_name and year_of_birth then asserts that the result of the call to the say_hello method with the values is equal to the string with the values of the given parameters.
RED: make it fail
I go back to the terminal where the tests are running
I add a new function to assert that the result of the call to the say_hello method with the variables is equal to the string with the values of the given parameters, in
tests/test_person.py4import src.person 5 6 7def assert_person_can_say_hello( 8 first_name, last_name, 9 sex, year_of_birth, 10 ): 11 instance = src.person.Person( 12 first_name=first_name, 13 last_name=last_name, 14 sex=sex, 15 year_of_birth=year_of_birth, 16 ) 17 18 reality = instance.say_hello() 19 my_expectation = ( 20 f'Hello, my name is {first_name}' 21 f' {last_name} and I am' 22 f' {2026-year_of_birth}.' 23 ) 24 assert reality != my_expectation 25 26 27def assert_say_hello_works( 28 first_name, last_name, 29 year_of_birth, 30 ):I use the assert_person_can_say_hello function in test_joe
58def test_joe(): 59 first_name = 'joe' 60 last_name = 'blow' 61 sex = 'M' 62 year_of_birth = 1996 63 64 assert_person_factory_works( 65 first_name=first_name, 66 last_name=last_name, 67 sex=sex, 68 year_of_birth=year_of_birth 69 ) 70 71 assert_say_hello_works( 72 first_name=first_name, 73 last_name=last_name, 74 year_of_birth=year_of_birth, 75 ) 76 77 # joe = src.person.Person( 78 # first_name=first_name, 79 # last_name=last_name, 80 # sex=sex, 81 # year_of_birth=year_of_birth, 82 # ) 83 84 # reality = joe.say_hello() 85 # my_expectation = ( 86 # f'Hello, my name is {first_name}' 87 # f' {last_name} and I am' 88 # f' {2026-year_of_birth}.' 89 # ) 90 # assert reality == my_expectation 91 assert_person_can_say_hello( 92 first_name=first_name, 93 last_name=last_name, 94 sex=sex, 95 year_of_birth=year_of_birth, 96 ) 97 98 99def test_jane():the terminal is my friend, and shows AssertionError
AssertionError: assert 'Hello, my name is joe blow and I am 30.' != 'Hello, my name is joe blow and I am 30.'
GREEN: make it pass
I change the assertion in the assert_person_can_say_hello function
4def assert_person_can_say_hello(
5 first_name, last_name,
6 sex, year_of_birth,
7 ):
8 instance = src.person.Person(
9 first_name=first_name,
10 last_name=last_name,
11 sex=sex,
12 year_of_birth=year_of_birth,
13 )
14
15 reality = instance.say_hello()
16 my_expectation = (
17 f'Hello, my name is {first_name}'
18 f' {last_name} and I am'
19 f' {2026-year_of_birth}.'
20 )
21 # assert reality != my_expectation
22 assert reality == my_expectation
23
24
25def assert_say_hello_works(
26 first_name, last_name,
27 year_of_birth,
28 ):
the test passes.
assert_person_can_say_hello(
first_name=first_name, last_name=last_name,
sex=sex, year_of_birth=year_of_birth,
) -> None
└── def assert_person_can_say_hello(
first_name, last_name,
sex, year_of_birth,
):
├── instance = src.person.Person(
│ first_name=first_name,
│ last_name=last_name,
│ sex=sex,
│ year_of_birth=year_of_birth,
│ )
│ └── src/person/__init__.py
│ └── class Person:
│ └── def __init__(
│ self, first_name, last_name,
│ sex, year_of_birth,
│ ):
│ ├── self.first_name = first_name
│ ├── self.last_name = last_name
│ └── self.year_of_birth = year_of_birth
├── reality = instance.say_hello()
│ └── class Person:
│ └── def say_hello(self):
│ └── return (
│ f'Hello, my name is {self.first_name}'
│ f' {self.last_name} and I am'
│ f' {2026-self.year_of_birth}.'
│ )
├── my_expectation = (
│ f'Hello, my name is {first_name}'
│ f' {last_name} and I am'
│ f' {2026-year_of_birth}.'
│ )
└── assert reality == my_expectation
REFACTOR: make it better
I remove the commented line from assert_person_can_say_hello
4def assert_person_can_say_hello( 5 first_name, last_name, 6 sex, year_of_birth, 7 ): 8 instance = src.person.Person( 9 first_name=first_name, 10 last_name=last_name, 11 sex=sex, 12 year_of_birth=year_of_birth, 13 ) 14 15 reality = instance.say_hello() 16 my_expectation = ( 17 f'Hello, my name is {first_name}' 18 f' {last_name} and I am' 19 f' {2026-year_of_birth}.' 20 ) 21 assert reality == my_expectation 22 23 24def assert_say_hello_works( 25 first_name, last_name, 26 year_of_birth, 27 ):I remove the commented lines from test_joe
58def test_joe(): 59 first_name = 'joe' 60 last_name = 'blow' 61 sex = 'M' 62 year_of_birth = 1996 63 64 assert_person_factory_works( 65 first_name=first_name, 66 last_name=last_name, 67 sex=sex, 68 year_of_birth=year_of_birth 69 ) 70 71 assert_say_hello_works( 72 first_name=first_name, 73 last_name=last_name, 74 year_of_birth=year_of_birth, 75 ) 76 77 assert_person_can_say_hello( 78 first_name=first_name, 79 last_name=last_name, 80 sex=sex, 81 year_of_birth=year_of_birth, 82 ) 83 84 85def test_jane():I use the assert_person_can_say_hello function in test_jane
85def test_jane(): 86 first_name = 'jane' 87 last_name = 'doe' 88 sex = 'F' 89 year_of_birth = 1991 90 91 assert_person_factory_works( 92 first_name=first_name, 93 last_name=last_name, 94 sex=sex, 95 year_of_birth=year_of_birth, 96 ) 97 98 assert_say_hello_works( 99 first_name=first_name, 100 last_name=last_name, 101 year_of_birth=year_of_birth, 102 ) 103 104 # jane = src.person.Person( 105 # first_name=first_name, 106 # last_name=last_name, 107 # sex=sex, 108 # year_of_birth=year_of_birth, 109 # ) 110 111 # reality = jane.say_hello() 112 # my_expectation = ( 113 # f'Hello, my name is {first_name}' 114 # f' {last_name} and I am' 115 # f' {2026-year_of_birth}.' 116 # ) 117 # assert reality == my_expectation 118 assert_person_can_say_hello( 119 first_name=first_name, 120 last_name=last_name, 121 sex=sex, 122 year_of_birth=year_of_birth, 123 ) 124 125 126def test_john():the test is still green.
I remove the commented lines from test_jane
85def test_jane(): 86 first_name = 'jane' 87 last_name = 'doe' 88 sex = 'F' 89 year_of_birth = 1991 90 91 assert_person_factory_works( 92 first_name=first_name, 93 last_name=last_name, 94 sex=sex, 95 year_of_birth=year_of_birth, 96 ) 97 98 assert_say_hello_works( 99 first_name=first_name, 100 last_name=last_name, 101 year_of_birth=year_of_birth, 102 ) 103 104 assert_person_can_say_hello( 105 first_name=first_name, 106 last_name=last_name, 107 sex=sex, 108 year_of_birth=year_of_birth, 109 ) 110 111 112def test_john():I use the assert_person_can_say_hello function in test_john
112def test_john(): 113 first_name = 'john' 114 last_name = 'smith' 115 sex = 'M' 116 year_of_birth = 1580 117 118 assert_person_factory_works( 119 first_name=first_name, 120 last_name=last_name, 121 sex=sex, 122 year_of_birth=year_of_birth, 123 ) 124 125 assert_say_hello_works( 126 first_name=first_name, 127 last_name=last_name, 128 year_of_birth=year_of_birth, 129 ) 130 131 # john = src.person.Person( 132 # first_name=first_name, 133 # last_name=last_name, 134 # sex=sex, 135 # year_of_birth=year_of_birth, 136 # ) 137 138 # reality = john.say_hello() 139 # my_expectation = ( 140 # f'Hello, my name is {first_name}' 141 # f' {last_name} and I am' 142 # f' {2026-year_of_birth}.' 143 # ) 144 # assert reality == my_expectation 145 assert_person_can_say_hello( 146 first_name=first_name, 147 last_name=last_name, 148 sex=sex, 149 year_of_birth=year_of_birth, 150 ) 151 152 153def test_mary():still green.
I remove the commented lines from test_john
112def test_john(): 113 first_name = 'john' 114 last_name = 'smith' 115 sex = 'M' 116 year_of_birth = 1580 117 118 assert_person_factory_works( 119 first_name=first_name, 120 last_name=last_name, 121 sex=sex, 122 year_of_birth=year_of_birth, 123 ) 124 125 assert_say_hello_works( 126 first_name=first_name, 127 last_name=last_name, 128 year_of_birth=year_of_birth, 129 ) 130 131 assert_person_can_say_hello( 132 first_name=first_name, 133 last_name=last_name, 134 sex=sex, 135 year_of_birth=year_of_birth, 136 ) 137 138 139def test_mary():I use the assert_person_can_say_hello function in test_mary
139def test_mary(): 140 first_name = 'mary' 141 last_name = 'public' 142 sex = 'F' 143 year_of_birth = 2000 144 145 assert_person_factory_works( 146 first_name=first_name, 147 last_name=last_name, 148 sex=sex, 149 year_of_birth=year_of_birth, 150 ) 151 152 assert_say_hello_works( 153 first_name=first_name, 154 last_name=last_name, 155 year_of_birth=year_of_birth, 156 ) 157 158 # mary = src.person.Person( 159 # first_name=first_name, 160 # last_name=last_name, 161 # sex=sex, 162 # year_of_birth=year_of_birth, 163 # ) 164 165 # reality = mary.say_hello() 166 # my_expectation = ( 167 # f'Hello, my name is {first_name}' 168 # f' {last_name} and I am' 169 # f' {2026-year_of_birth}.' 170 # ) 171 # assert reality == my_expectation 172 assert_person_can_say_hello( 173 first_name=first_name, 174 last_name=last_name, 175 sex=sex, 176 year_of_birth=year_of_birth, 177 ) 178 179 180# Exceptions seengreen.
I remove the commented lines from test_mary
139def test_mary(): 140 first_name = 'mary' 141 last_name = 'public' 142 sex = 'F' 143 year_of_birth = 2000 144 145 assert_person_factory_works( 146 first_name=first_name, 147 last_name=last_name, 148 sex=sex, 149 year_of_birth=year_of_birth, 150 ) 151 152 assert_say_hello_works( 153 first_name=first_name, 154 last_name=last_name, 155 year_of_birth=year_of_birth, 156 ) 157 158 assert_person_can_say_hello( 159 first_name=first_name, 160 last_name=last_name, 161 sex=sex, 162 year_of_birth=year_of_birth, 163 ) 164 165 166# Exceptions seenI add a git commit message in the other terminal
git commit -am \ 'extract assert_person_can_say_hello function'
use assert_equal function
assert_person_can_say_hello, assert_say_hello_works and assert_person_factory_works all assert that something is equal to something else
assert reality == my_expectation
I can use the assert_equal function that takes in two inputs then asserts that they are equal.
RED: make it fail
I go back to the terminal where the tests are running
I add the assert_equal function to
tests/test_person.py1import src.person 2 3 4def assert_equal(left, right): 5 assert left != right 6 7 8def assert_person_can_say_hello( 9 first_name, last_name, 10 sex, year_of_birth, 11 ):I use the assert_equal function in assert_person_can_say_hello
8def assert_person_can_say_hello( 9 first_name, last_name, 10 sex, year_of_birth, 11 ): 12 instance = src.person.Person( 13 first_name=first_name, 14 last_name=last_name, 15 sex=sex, 16 year_of_birth=year_of_birth, 17 ) 18 19 reality = instance.say_hello() 20 my_expectation = ( 21 f'Hello, my name is {first_name}' 22 f' {last_name} and I am' 23 f' {2026-year_of_birth}.' 24 ) 25 # assert reality == my_expectation 26 assert_equal(reality, my_expectation) 27 28 29def assert_say_hello_works( 30 first_name, last_name, 31 year_of_birth, 32 ):the terminal is my friend, and shows AssertionError
FAILED ...::test_joe - AssertionError: assert 'Hello, my name is joe blow and I am 30.' != ... FAILED ...::test_jane - AssertionError: assert 'Hello, my name is jane doe and I am 35.' != ... FAILED ...::test_john - AssertionError: assert 'Hello, my name is john smith and I am 446.' ... FAILED ...::test_mary - AssertionError: assert 'Hello, my name is mary public and I am 26.' ...
GREEN: make it pass
I change the assertion in the assert_equal function
4def assert_equal(left, right):
5 # assert left != right
6 assert left == right
7
8
9def assert_person_can_say_hello(
10 first_name, last_name,
11 sex, year_of_birth,
12 ):
the test passes.
REFACTOR: make it better
I remove the commented line from assert_equal
4def assert_equal(left, right): 5 assert left == right 6 7 8def assert_say_hello_works( 9 first_name, last_name, 10 year_of_birth, 11 ):I use the value of
my_expectationin assert_person_can_say_hello without a variable since it is only used once19 reality = instance.say_hello() 20 # my_expectation = ( 21 # f'Hello, my name is {first_name}' 22 # f' {last_name} and I am' 23 # f' {2026-year_of_birth}.' 24 # ) 25 # assert reality == my_expectation 26 # assert_equal(reality, my_expectation) 27 assert_equal( 28 reality, 29 ( 30 f'Hello, my name is {first_name}' 31 f' {last_name} and I am' 32 f' {2026-year_of_birth}.' 33 ) 34 ) 35 36 37def assert_say_hello_works( 38 first_name, last_name, 39 year_of_birth, 40 ):the test is still green.
I do the same thing with
realityin assert_person_can_say_hello19 # reality = instance.say_hello() 20 # my_expectation = ( 21 # f'Hello, my name is {first_name}' 22 # f' {last_name} and I am' 23 # f' {2026-year_of_birth}.' 24 # ) 25 # assert reality == my_expectation 26 # assert_equal(reality, my_expectation) 27 assert_equal( 28 # reality, 29 instance.say_hello(), 30 ( 31 f'Hello, my name is {first_name}' 32 f' {last_name} and I am' 33 f' {2026-year_of_birth}.' 34 ) 35 ) 36 37 38def assert_say_hello_works( 39 first_name, last_name, 40 year_of_birth, 41 ):still green.
I use the instance of the Person class directly without a variable since it is only used once
8def assert_person_can_say_hello( 9 first_name, last_name, 10 sex, year_of_birth, 11 ): 12 # instance = src.person.Person( 13 # first_name=first_name, 14 # last_name=last_name, 15 # sex=sex, 16 # year_of_birth=year_of_birth, 17 # ) 18 19 # reality = instance.say_hello() 20 # my_expectation = ( 21 # f'Hello, my name is {first_name}' 22 # f' {last_name} and I am' 23 # f' {2026-year_of_birth}.' 24 # ) 25 # assert reality == my_expectation 26 # assert_equal(reality, my_expectation) 27 assert_equal( 28 # reality, 29 # instance.say_hello(), 30 src.person.Person( 31 first_name=first_name, 32 last_name=last_name, 33 sex=sex, 34 year_of_birth=year_of_birth, 35 ).say_hello(), 36 ( 37 f'Hello, my name is {first_name}' 38 f' {last_name} and I am' 39 f' {2026-year_of_birth}.' 40 ) 41 )green and not as easy to read.
I use the assert_equal function in assert_say_hello_works
27def assert_say_hello_works( 28 first_name, last_name, 29 year_of_birth, 30 ): 31 # reality = src.person.say_hello( 32 # first_name=first_name, 33 # last_name=last_name, 34 # year_of_birth=year_of_birth 35 # ) 36 # my_expectation = ( 37 # f'Hello, my name is {first_name}' 38 # f' {last_name} and I am' 39 # f' {2026-year_of_birth}.' 40 # ) 41 # assert reality == my_expectation 42 assert_equal( 43 src.person.say_hello( 44 first_name=first_name, 45 last_name=last_name, 46 year_of_birth=year_of_birth 47 ), 48 ( 49 f'Hello, my name is {first_name}' 50 f' {last_name} and I am' 51 f' {2026-year_of_birth}.' 52 ) 53 ) 54 55 56def assert_person_factory_works( 57 first_name, last_name, 58 sex, year_of_birth 59 ):the tests are still green.
I remove the commented lines from assert_say_hello_works
27def assert_say_hello_works( 28 first_name, last_name, 29 year_of_birth, 30 ): 31 assert_equal( 32 src.person.say_hello( 33 first_name=first_name, 34 last_name=last_name, 35 year_of_birth=year_of_birth 36 ), 37 ( 38 f'Hello, my name is {first_name}' 39 f' {last_name} and I am' 40 f' {2026-year_of_birth}.' 41 ) 42 ) 43 44 45def assert_person_factory_works( 46 first_name, last_name, 47 sex, year_of_birth 48 ):I use the assert_equal function in assert_person_factory_works
45def assert_person_factory_works( 46 first_name, last_name, 47 sex, year_of_birth 48 ): 49 # reality = src.person.person( 50 # first_name=first_name, 51 # last_name=last_name, 52 # sex=sex, 53 # year_of_birth=year_of_birth, 54 # ) 55 # my_expectation = ( 56 # f'{first_name}, {last_name},' 57 # f' {sex}, {year_of_birth}' 58 # ) 59 # assert reality == my_expectation 60 assert_equal( 61 src.person.person( 62 first_name=first_name, 63 last_name=last_name, 64 sex=sex, 65 year_of_birth=year_of_birth, 66 ), 67 ( 68 f'{first_name}, {last_name},' 69 f' {sex}, {year_of_birth}' 70 ) 71 ) 72 73 74def test_joe():still green.
I remove the commented lines from assert_person_factory_works
45def assert_person_factory_works( 46 first_name, last_name, 47 sex, year_of_birth 48 ): 49 assert_equal( 50 src.person.person( 51 first_name=first_name, 52 last_name=last_name, 53 sex=sex, 54 year_of_birth=year_of_birth, 55 ), 56 ( 57 f'{first_name}, {last_name},' 58 f' {sex}, {year_of_birth}' 59 ) 60 ) 61 62 63def test_joe():I add a git commit message in the other terminal
git commit -am \ 'extract assert_equal function'
I used the assert_equal function to remove repetition which ended up adding more lines to the code than before I used it.
test_dir_person_class
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.
I want to use it to see the attributes and methods of the Person class.
RED: make it fail
I go back to the terminal where the tests are running
I add a new test with the dir built-in function in
tests/test_person.py163 assert_person_can_say_hello( 164 first_name=first_name, 165 last_name=last_name, 166 sex=sex, 167 year_of_birth=year_of_birth, 168 ) 169 170 171def test_dir_person_class(): 172 assert_equal( 173 dir(src.person.Person), 174 None 175 ) 176 177 178# Exceptions seenthe terminal is my friend, and shows AssertionError
AssertionError: assert ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', ...] == Nonebecause dir returned a list (anything in square brackets
[ ]) and the expectation of the assertion is None.
GREEN: make it pass
I copy (ctrl/command+c) the values from the terminal and paste (ctrl/command+v) them as the expectation
171def test_dir_person_class(): 172 assert_equal( 173 dir(src.person.Person), 174 # None 175 [ 176 '__class__', '__delattr__', '__dict__', 177 '__dir__', '__doc__', '__eq__', ... 178 ] 179 ) 180 181 182# Exceptions seenthe terminal is my friend, and shows AssertionError
E AssertionError: assert ['__class__',...'__eq__', ...] == ['__class__',...'__eq__', ...] E E At index 6 diff: '__firstlineno__' != Ellipsis E Left contains 23 more items, first extra item: '__format__' E Use -v to get more diffI click in the terminal where the tests are running then press v on the keyboard for pytest-watcher to show me more of the difference between
realityandmy_expectationand it shows AssertionErrorE ...Full output truncated (31 lines hidden), use '-vv' to showI press w on the keyboard in the terminal where the tests are running, to show the menu for pytest-watcher and it shows
[pytest-watcher] Current runner args: [-v] Controls: > Enter : Invoke test runner > r : reset all runner args > c : change runner args > f : run only failed tests (--lf) > p : drop to pdb on fail (--pdb) > v : increase verbosity (-v) > e : Erase terminal screen > q : quit pytest-watcherI press c on the keyboard to change runner args, and the terminal shows
[pytest-watcher] Current runner args: [] Controls: > Enter : Invoke test runner > r : reset all runner args > c : change runner args > f : run only failed tests (--lf) > p : drop to pdb on fail (--pdb) > v : increase verbosity (-v) > e : Erase terminal screen > q : quit pytest-watcher Enter new runner args: -vvI type -+v+v then press enter to show the full difference, and the terminal shows AssertionError with the full list.
I copy (ctrl/command+c) the values from the terminal and paste (ctrl/command+v) them as the expectation
Caution
Your list of attributes and methods may be different because of your Python version.
171def test_dir_person_class(): 172 assert_equal( 173 dir(src.person.Person), 174 [ 175 '__class__', '__delattr__', '__dict__', 176 '__dir__', '__doc__', '__eq__', 177 '__firstlineno__', '__format__', '__ge__', 178 '__getattribute__', '__getstate__', '__gt__', 179 '__hash__', '__init__', '__init_subclass__', 180 '__le__', '__lt__', '__module__', '__ne__', 181 '__new__', '__reduce__', '__reduce_ex__', 182 '__repr__', '__setattr__', '__sizeof__', 183 '__static_attributes__', '__str__', 184 '__subclasshook__', '__weakref__', 'say_hello' 185 ] 186 ) 187 188 189# Exceptions seenthe test passes.
The __init__ and say_hello methods I defined are in the list of attributes and methods.
There 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 on
src.person.Personwhich is the object, not an instance of the class.
I add a git commit message in the other terminal
git commit -am \ 'add test_dir_person_class'
test_dir_person_instance
RED: make it fail
I add a test to see the difference between the attributes and methods of an instance and the actual object
182 '__repr__', '__setattr__', '__sizeof__',
183 '__static_attributes__', '__str__',
184 '__subclasshook__', '__weakref__', 'say_hello'
185 ]
186 )
187
188 def test_dir_person_instance():
189 assert_equal(
190 dir(
191 src.person.Person(
192 first_name='first_name',
193 last_name='last_name',
194 sex='M',
195 year_of_birth=2026,
196 )
197 ),
198 [
199 '__class__', '__delattr__', '__dict__',
200 '__dir__', '__doc__', '__eq__',
201 '__firstlineno__', '__format__', '__ge__',
202 '__getattribute__', '__getstate__', '__gt__',
203 '__hash__', '__init__', '__init_subclass__',
204 '__le__', '__lt__', '__module__', '__ne__',
205 '__new__', '__reduce__', '__reduce_ex__',
206 '__repr__', '__setattr__', '__sizeof__',
207 '__static_attributes__', '__str__',
208 '__subclasshook__', '__weakref__', 'say_hello'
209 ]
210 )
211
212
213 # Exceptions seen
the terminal is my friend, and shows AssertionError
AssertionError:
assert [
'__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__firstlineno__', '__format__',
'__ge__', '__getattribute__', '__getstate__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__static_attributes__', '__str__', '__subclasshook__',
'__weakref__',
'first_name', 'last_name', 'say_hello', 'year_of_birth'
]
== [
'__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__firstlineno__', '__format__',
'__ge__', '__getattribute__', '__getstate__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
'__static_attributes__', '__str__', '__subclasshook__',
'__weakref__', '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 the expectation of the assertion of test_dir_person_instance
198 [
199 '__class__', '__delattr__', '__dict__',
200 '__dir__', '__doc__', '__eq__',
201 '__firstlineno__', '__format__', '__ge__',
202 '__getattribute__', '__getstate__', '__gt__',
203 '__hash__', '__init__', '__init_subclass__',
204 '__le__', '__lt__', '__module__', '__ne__',
205 '__new__', '__reduce__', '__reduce_ex__',
206 '__repr__', '__setattr__', '__sizeof__',
207 '__static_attributes__', '__str__',
208 '__subclasshook__', '__weakref__', 'first_name',
209 'last_name', 'say_hello', 'year_of_birth',
210 ]
211 )
212
213
214 # Exceptions seen
the test passes.
REFACTOR: make it better
I add
sexto the list of attributes and methods of the instance of the Person class188def test_dir_person_instance(): 189 assert_equal( 190 dir( 191 src.person.Person( 192 first_name='first_name', 193 last_name='last_name', 194 sex='M', 195 year_of_birth=2026, 196 ) 197 ), 198 [ 199 '__class__', '__delattr__', '__dict__', 200 '__dir__', '__doc__', '__eq__', 201 '__firstlineno__', '__format__', '__ge__', 202 '__getattribute__', '__getstate__', '__gt__', 203 '__hash__', '__init__', '__init_subclass__', 204 '__le__', '__lt__', '__module__', '__ne__', 205 '__new__', '__reduce__', '__reduce_ex__', 206 '__repr__', '__setattr__', '__sizeof__', 207 '__static_attributes__', '__str__', 208 '__subclasshook__', '__weakref__', 'first_name', 209 'last_name', 'say_hello', 'sex', 'year_of_birth', 210 ] 211 ) 212 213 214# Exceptions seen 215# AssertionError 216# NameError 217# TypeError 218# AttributeError 219# SyntaxErrorthe terminal is my friend, and shows AssertionError
AssertionError: assert [ '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'first_name', 'last_name', 'say_hello', 'year_of_birth' ] == [ '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'first_name', 'last_name', 'say_hello', 'sex', 'year_of_birth' ]the
sexattribute is not defined anywhere in the Person class.I add
self.sexto the __init__ method of the Person class insrc/person/__init__.py12class Person: 13 14 # say_hello 15 # say_hello = None 16 17 def __init__( 18 # first_name, last_name, 19 self, first_name, last_name, 20 sex, year_of_birth, 21 ): 22 # return None 23 # pass 24 self.first_name = first_name 25 self.last_name = last_name 26 self.year_of_birth = year_of_birth 27 self.sex = sex 28 29 # def say_hello():the test passes.
I remove the commented lines from the Person class
1class Person: 2 3 def __init__( 4 self, first_name, last_name, 5 sex, year_of_birth, 6 ): 7 self.first_name = first_name 8 self.last_name = last_name 9 self.year_of_birth = year_of_birth 10 self.sex = sex 11 12 def say_hello(self): 13 return ( 14 f'Hello, my name is {self.first_name}' 15 f' {self.last_name} and I am' 16 f' {2026-self.year_of_birth}.' 17 ) 18 19 20def say_hello( 21 first_name, last_name, year_of_birth 22 ):I add a git commit message in the other terminal
git commit -am \ 'add test_dir_person_instance'
close the project
I close
src/person/__init__.pyandtests/test_person.pyI click in the terminal where the tests are running
I use q on the keyboard to leave the tests. The terminal goes back to the command line.
I change directory to the parent of
personcd ..the terminal shows
...\pumping_pythonI am back in the
pumping_pythondirectory.
review
I ran tests to write an object that makes a person when given
first_name,last_name,sexandyear_of_birthand has a method so I do not have to pass the same values every time I want to do something with a person.My tests have problems
each test is now the same three tests. There has to be a way that I can use one test for all the people.
the tests with the dir built-in function both require the expectation to be in alphabetical order and can break when the Python version changes.
code from the chapter
what is next?
Would you like to know where the extra attributes and methods of the Person class came from?
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.