test (2)
1. Test class
We wrote a test for a single function earlier, let's write a test for a class. Classes are used in many programs, so it's helpful to prove that our classes work correctly. If the tests for the class pass, we can be confident that improvements made to the class did not accidentally break its original behavior.
2. Various assertion methods
Python provides many assertion methods in the unittest.TestCase class. As mentioned earlier, the assertion method checks whether a condition you think should be true is indeed true. If the condition is indeed met, your assumption about the behavior of the program is confirmed, and you can be confident that there are no errors in it. Python will raise an exception if a condition you think should be true is not true.
Table 11-1 describes six commonly used assertion methods. Use these methods to verify that the returned value is equal to or not equal to the expected value, that the returned value is True or False, and that the returned value is in the list or not. These methods can only be used in classes that inherit unittest.TestCase, then see how to use one of them when testing a class.
3. A class to test
Class testing is similar to function testing in that most of what you do is test the behavior of the methods in the class. However, there are still some differences. Let's write a class to test. Let's look at a class that helps manage anonymous polls:
class AnonymousSurvey:
"""收集匿名调查问卷的答案。"""
❶ def __init__(self, question):
"""存储一个问题,并为存储答案做准备。"""
self.question = question
self.responses = []
❷ def show_question(self):
"""显示调查问卷。"""
print(self.question)
❸ def store_response(self, new_response):
"""存储单份调查答卷。"""
self.responses.append(new_response)
❹ def show_results(self):
"""显示收集到的所有答卷。"""
print("Survey results:")
for response in self.responses:
print(f"- {
response}")
This class first stores a survey question (see ❶) and creates an empty list for storing the answers. This class contains methods for printing survey questions (see ❷), methods for adding new answers to the answer list (see ❸), and methods for printing all answers stored in the list (see ❹). To create an instance of this class, simply provide a question. Once we have an instance representing a survey, we can use show_question() to display the questions in it, store_response() to store the answers and show_results() to display the survey results.
To demonstrate that the AnonymousSurvey class works correctly, write a program that uses it:
from survey import AnonymousSurvey
# 定义一个问题,并创建一个调查。
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
# 显示问题并存储答案。
my_survey.show_question()
print("Enter 'q' at any time to quit.\n")
while True:
response = input("Language: ")
if response == 'q':
break
my_survey.store_response(response)
# 显示调查结果。
print("\nThank you to everyone who participated in the survey!")
my_survey.show_results()
This program defines a question ("What language did you first learn to speak?") and creates an AnonymousSurvey object using that question. Next, the program calls show_question() to display the question and prompt the user for an answer. Store each answer as it is received. After the user enters all answers (type q to quit), call show_results() to print the survey results:
What language did you first learn to speak?
Enter 'q' at any time to quit.
Language: English
Language: Spanish
Language: English
Language: Mandarin
Language: q
Thank you to everyone who participated in the survey!
Survey results:
- English
- Spanish
- English
- Mandarin
The AnonymousSurvey class can be used to conduct simple anonymous surveys. Suppose we put it in the module survey and want to improve it: let each user enter multiple answers; write a method that lists only the different answers and indicates how many times each answer appears; write another Class for managing non-anonymous surveys.
There are risks in making the above modifications and may affect the current behavior of the AnonymousSurvey class. For example, allowing multiple answers per user may inadvertently modify how individual answers are handled. To make sure that no existing behavior is broken when developing this module, write tests against this class.
4. Test the AnonymousSurvey class
Let's write a test that verifies one aspect of the behavior of the AnonymousSurvey class: If the user provides only one answer to a survey question, that answer is properly stored. To do this, we will use the method assertIn() to verify that this answer is indeed in the answers list after it has been stored:
import unittest
from survey import AnonymousSurvey
❶ class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试。"""
❷ def test_store_single_response(self):
"""测试单个答案会被妥善地存储。"""
question = "What language did you first learn to speak?"
❸ my_survey = AnonymousSurvey(question)
my_survey.store_response('English')
❹ self.assertIn('English', my_survey.responses)
if __name__ == '__main__':
unittest.main()
First import the module unittest and the class AnonymousSurvey to be tested. Name the test case TestAnonymousSurvey, which also inherits unittest.TestCase (see ❶). The first test method validates: After a single answer to a survey question is stored, it is included in the list of survey results. A good descriptive name for this method is test_store_single_response() (see ❷). If this test fails, we can tell from the method name in the output that there is a problem storing individual survey answers.
To test the behavior of a class, an instance of it needs to be created. At ❸, create an instance called my_survey with the question "Whatlanguage did you first learn to speak?", and then use the method store_response() to store the single answer English. Next, check that English is contained in the list my_survey.responses to verify that the answer was properly stored (see ❹).
The tests pass when we run test_survey.py:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
That's fine, but a survey that only collects one answer isn't very useful. Let's verify that when the user provides three answers, they are also stored properly. To do this, add one more method to TestAnonymousSurvey:
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试。"""
def test_store_single_response(self):
--snip--
def test_store_three_responses(self):
"""测试三个答案会被妥善地存储。"""
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
❶ responses = ['English', 'Spanish', 'Mandarin']
for response in responses:
my_survey.store_response(response)
❷ for response in responses:
self.assertIn(response, my_survey.responses)
if __name__ == '__main__':
unittest.main()
We name the method test_store_three_responses() and create a survey object inside it as we did for test_store_single_response(). Define a list of three different answers (see ❶), and call store_response() on each of them. After storing these answers, use a loop to verify that each answer is included in my_survey.responses (see ❷).
When test_survey.py is run again, both tests (one for a single answer and one for three answers) pass:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
The preceding approach works well, but the tests are somewhat repetitive. Let's use another feature of unittest to improve its efficiency.
5. Method setUp()
In the previous test_survey.py, we created an instance of AnonymousSurvey in each test method, and created the answers in each method. The unittest.TestCase class contains a method setUp() that allows us to create these objects once and use them in each test method. If the method setUp() is included in the TestCase class, Python will run it first, and then run each method starting with test_. This way, the object created in the method setUp() can be used in every test method you write.
The following uses setUp() to create a survey object and a set of answers for use by the methods test_store_single_response() and test_store_three_responses():
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""针对AnonymousSurvey类的测试。"""
def setUp(self):
"""
创建一个调查对象和一组答案,供使用的测试方法使用。
"""
question = "What language did you first learn to speak?"
❶ self.my_survey = AnonymousSurvey(question)
❷ self.responses = ['English', 'Spanish', 'Mandarin']
def test_store_single_response(self):
"""测试单个答案会被妥善地存储。"""
self.my_survey.store_response(self.responses[0])
self.assertIn(self.responses[0], self.my_survey.responses)
def test_store_three_responses(self):
"""测试三个答案会被妥善地存储。"""
for response in self.responses:
self.my_survey.store_response(response)
for response in self.responses:
self.assertIn(response, self.my_survey.responses)
if __name__ == '__main__':
unittest.main()
The method setUp() does two things: create a survey object (see ❶), and create a list of answers (see ❷). The names of the variables that store these two things include the prefix self (ie stored in attributes), and thus can be used anywhere in this class. This makes both test methods simpler, since they don't have to create survey objects and answers. The method test_store_single_response() verifies that the first answer self.responses[0] in self.responses is properly stored, and the method test_store_three_response() verifies that all three answers in self.responses are properly stored.
Both tests also pass when test_survey.py is run again. These tests are useful if you want to extend AnonymousSurvey to allow each user to enter multiple answers. After modifying the code to accept multiple answers, run these tests to confirm that the behavior of storing a single answer or a range of answers is not affected.
When testing a class you write, the method setUp() makes it easier to write test methods: you can create a series of instances and set their properties in the setUp() method, and then use these instances directly in the test method. This is much easier than creating an instance and setting its properties in every test method.