-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcode_testing.py
More file actions
289 lines (222 loc) · 11.5 KB
/
Copy pathcode_testing.py
File metadata and controls
289 lines (222 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
"""
This is where the code I write while following along with Python Crash Course 2nd Edition
will go.
"""
import unittest
def get_formatted_name(first, last):
"""Generate a neatly formatted name."""
full_name = f"{first} {last}"
return full_name.title()
while True:
print("Give me a first and last name (q to quit): ")
first = input("What's the first name? ")
if first == 'q':
break
last = input("What's the last name? ")
if last == 'q':
break
formatted_name = get_formatted_name(first, last)
print(f"Name: {formatted_name}")
# Say that we wanted to modify the program by adding an optional middle name, and we want to make
# sure that the program doesn't break. We could just run the program and type in a name every time,
# but that would get tedious. Instead, Python has an efficient way that we could use to automate the
# testing of a function's output.
# The unittest module from the Python standard library provides tools for testing code. A unit test
# checks if one specific aspect of a function's behaviour is correct. A text case is a colleciton of
# unit tests that together prove that a function behaves as it's supposed to, within the full range
# situations that the code is expected to handle. A good test case considers all the possible kinds
# of input that a function could receive and includes tests to represent all of those situations.
# A test case with full coverage includes a full range of unit tests covering all the possible ways
# a function could be used.
# Since full coverage on a project can be very difficult, it's generally good enough to test for
# code's critical behaviours only, and aim for full coverage only if the project sees widespread
# use.
# It's easy to add more unit tests to a function once the test case has been set up.
# To write a test case for a function, create a class that inherits from unittest.TestCase.
class NamesTestCase(unittest.TestCase):
"""Tests for get_formatted_name()."""
def test_first_last_name(self):
"""Check if certain names work."""
formatted_name = get_formatted_name('janis', 'joplin')
self.assertEqual(formatted_name, 'Janis Joplin')
# The class NamesTestCase will contain a series of unit tests for get_formatted_name(), but doesn't
# have to be called that. It should relate to the function that's being tested, and should have the
# word test in it. It also must inherit from unittest.TestCase so that Python knows how to run
# tests we write.
# Any method that starts with test_ will be run automatically when this file is run.
# Assert methods check that the result we receive matches the expected result.
# We're going to run this file directly, but it's important to note that many testing frameworks
# import our test files before running them. When a file is imported, the interpreter executes the
# file as if it's being imported.
# __name__ is a special variable that is set when the program is executed. If this file is being run
# as the main program, the value of __name__ is set to '__main__'. unittest.main runs the test case.
# When a testing framework imports this file, the value of __name__ won't be '__main__' and the file
# won't be executed.
# If we modify the function so that it can accept a middle name, but breaks if none is provided:
# def get_formatted_name(first, middle, last):
# """Generate a neatly formatted name."""
# full_name = f"{first} {middle} {last}"
# return full_name.title()
# Then we get this traceback:
# E
# ======================================================================
# ERROR: test_first_last_name (__main__.NamesTestCase)
# Check if certain names work.
# ----------------------------------------------------------------------
# Traceback (most recent call last):
# File "/workspaces/functions/code_testing.py", line 53, in test_first_last_name
# formatted_name = get_formatted_name('janis', 'joplin')
# TypeError: get_formatted_name() missing 1 required positional argument: 'last'
# ----------------------------------------------------------------------
# Ran 1 test in 0.002s
# FAILED (errors=1)
# The 'E' means that one unit test in the case resulted in an error. Lines 86-87 tell us what test
# failed, and what it's docline was. Lines 89-92 tell us what the specific error was. We also see
# how many tests were ran, how long it took, and how many errors there were.
# This would pass the test.
# def get_formatted_name(first, last, middle=''):
# """Generate a neatly formatted name."""
# if middle:
# full_name = f"{first} {middle} {last}"
# else:
# full_name = f"{first} {last}"
# return full_name.title()
# We can add a new test to the NamesTestCase class by defining a new method and doing the same
# thing:
# class NamesTestCase(unittest.TestCase):
# """Tests for get_formatted_name()."""
# def test_first_last_name(self):
# --snip--
# def test_first_last_middle_name(self):
# """Check if middle names work with a first/last name."""
# formatted_name = get_formatted_name('milo', 'lee', 'sirion')
# self.assertEqual(formatted_name, 'Milo Sirion Lee')
# CITY, COUNTRY PRACTICE
# def format_city_country_name(city, country):
# """Return a neatly formatted city and country name."""
# return f"{city}, {country}"
# class CityCountryTestCase(unittest.TestCase):
# """A class to test the format_city_country_name() function."""
# def test_formatting_function(self):
# """A test to check that format_city_country_name() works."""
# formatted_location = format_city_country_name('San Francisco', 'California')
# self.assertEqual(formatted_location, 'San Francisco, California')
# if __name__ == '__main__':
# unittest.main()
def format_city_country_name(city, country, population=''):
"""Return a neatly formatted city and country name, and an optional population."""
if population:
return f"{city}, {country} - {population}"
else:
return f"{city}, {country}"
class CityCountryTestCase(unittest.TestCase):
"""A class to test the format_city_country_name() function."""
def test_without_population(self):
"""Check that the function works without a population."""
formatted_location = format_city_country_name('San Francisco', 'California')
self.assertEqual(formatted_location, 'San Francisco, California')
def test_with_population(self):
"""Test that the function works with a population."""
formatted_location = format_city_country_name('San Francisco', 'California', 12345)
self.assertEqual(formatted_location, 'San Francisco, California - 12345')
# Common assert methods:
# assertEqual(a, b) # Verify that a == b
# assertNotEqual(a, b) # Verify that a != b
# assertTrue(x) # Verify that x is True
# assertFalse(x) # Verify that x is False
# assertIn(item, list) # Verify that item is in list
# assertNotIN(item, list) # Verify that item is not in list
class AnonymousSurvey:
"""Collect anonymous answers to a survey."""
def __init__(self, question):
"""Store a question and prepare to store responses."""
self.question = question
self.responses = []
def show_question(self):
"""Show the survey question."""
print(self.question)
def store_response(self, new_response):
"""Store a single response to the survey."""
self.responses.append(new_response)
def show_results(self):
"""Show all given responses."""
print("Survey results:")
for response in self.responses:
print(f" - {response}")
my_survey = AnonymousSurvey(question='Test that a single response is stored properly.')
my_survey.show_question()
while True:
print("Enter 'q' to quit: ")
response = input("Answer: ")
if response == 'q':
break
my_survey.store_response(response)
my_survey.show_results()
# class TestAnonymousSurvey(unittest.TestCase):
# """Tests for the AnonymousSurvey class."""
# def test_store_single_response(self):
# """Test that a single response is stored properly."""
# question = "Test that a single response is stored properly."
# my_survey = AnonymousSurvey(question)
# my_survey.store_response('English')
# self.assertIn('English', my_survey.responses)
# def test_store_three_responses(self):
# """Test that three responses are stored properly."""
# question = "What language did you first learn to speak?"
# responses = ["English", "Spanish", "German"]
# for response in responses:
# my_survey.store_response(response)
# for response in responses:
# self.assertIn(response, my_survey.responses)
# The unittest.TestCase class has a built-in setUp() method that allows us to create objects once
# and then use them in each of our test methods. When a setUp() method is included in a TestCase
# class, Python runs the SetUp() method before running each method starting with 'test_'. Any
# objects created in the setUp() method are then available in each test method that we write.
class TestAnonymousSurvey(unittest.TestCase):
"""Tests for the AnonymousSurvey class."""
def setUp(self):
"""Create a survey and a set of responses for use in all test methods."""
question = "What language did you first learn to speak?"
self.my_survey = AnonymousSurvey(question)
self.responses = ["English", "Spanish", "German"]
def test_store_single_response(self):
"""Test that single response is stored properly."""
self.my_survey.store_response(self.responses[0])
self.assertIn(self.responses[0], self.my_survey.responses)
def test_store_three_responses(self):
"""Test that three responses are stored properly."""
for response in self.responses:
self.my_survey.store_response(response)
for response in self.responses:
self.assertIn(response, self.my_survey.responses)
# NOTE: When a test case is running, Python prints one character for each unit test as it is
# completed. A passing test prints a dot, a failed test prints an E, and a test that results in a
# failed assertion prints an F. This is the reason why there is a varying number of dots and
# on the first line of output when test cases are run. If a test takes a long time to run because it
# contains many unit tests, these results can be watched to get a sense of how things are running.
# EMPLOYEE PRACTICE
class Employee:
"""Create a class for an employee object."""
def __init__(self, first_name, last_name, annual_salary):
"""Initialize values for the employee class."""
self.first_name = first_name
self.last_name = last_name
self.annual_salary = annual_salary
def give_raise(self, raise_amount=5_000):
"""Give the employee a raise with a default amount of $5_000, but can be changed."""
self.annual_salary += raise_amount
class TestEmployee(unittest.TestCase):
"""Tests for the employee class."""
def setUp(self):
"""Create an employee object to test."""
self.employee_test_object = Employee('Teo', 'Tanaka', 5_000)
def test_give_default_raise(self):
"""Test that giving a default raise works."""
self.employee_test_object.give_raise()
self.assertEqual(self.employee_test_object.annual_salary, 10_000)
def test_give_custom_raise(self):
"""Test that a custom raise value works."""
self.employee_test_object.give_raise(1_000)
self.assertEqual(self.employee_test_object.annual_salary, 6_000)
if __name__ == '__main__':
unittest.main()