为了保证软件的可靠性和安全性,我们可以为函数或类编写测试代码,这样不仅仅可以简化我们手动测试的工作,也帮助我们未来对软件的维护和再开发更加方便。
比如我们创建一个函数,将用户的姓和名作为输入,然后返回一个格式化之后的全名:
# name_function.py
def get_formatted_name(first, last):
full_name = f"{first} {last}"
return full_name.title()
若要测试函数的正确性,我们在names.py中创建一个程序不断地提示用户输入名字,并打印出格式化之后的全名,然后我们可以自己来判断输出的正确性:
from name_function import get_formatted_name
print("Enter 'q' at any time to quit.")
while True:
first = input("\nfirst name: ")
if first == 'q':
break
last = input("\nlast name: ")
break
formatted_name = get_formatted_name(first, last)
print(f"\tNeatly formatted name: {formatted_name}.")
测试函数
目的:把上述测试进行自动化
若要创建一个测试用例,我们需要创建一个类,继承unittest.TestCase,然后在类的内部根据程序的需求,加入测试的具体方法:
注意:单元测试需要直接python xxx.py
# name_function_testing.py
import unittest
from name_function import get_formatted_name
class NamesTestCase(unittest.TestCase):
def test_first_last_name(self):
formatted_name = get_formatted_name('andrew', 'yang')
self.assertEqual(formatted_name, 'Andrew Yang')
if __name__ == '__main__':
unittest.main()
在上面的代码中,我们使用简单的self.assertEqual就能检验代码的正确性,运行代码后,我们会发现终端有以下的信息出现,来告诉我们测试的通过率:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
- 第一行的句号告诉我们一个测试通过了
- 下一行告诉我们所有测试花费的时间
- 最后一行的OK告诉我们这个测试用例通过了。
如果我们把格式化名字的函数进行以下的修改,需要用户输入自己的middle name:
# name_function.py
def get_formatted_name(first, middle, last):
full_name = f"{first} {middle} {last}"
return full_name.title()
这个时候如果我们运行测试代码,我们就会得到以下的错误信息:
E
===========================================================
ERROR: test_first_last_name (__main__.NamesTestCase)
-----------------------------------------------------------
Traceback (most recent call last):
File "test_name_function.py", line 8, 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.000s
FAILED (errors=1)
- 上面的信息提示测试没有通过,第一行的E代表测试中有一个错误(Error)
- 下一行告诉我们具体失败的测试函数 test_first_last_name
- 之后的Trackback告诉我们具体的错误信息,在这个例子中就是漏了一个参数last。
- 最后两行则是测试数量统计。当我们运行很多的测试用例时,这些具体信息对我们debug非常有帮助。
为了修复上面测试出现的问题,我们可以对格式化函数做以下的修改:
# name_function.py
def get_formatted_name(first, last, middle=''):
"""Generate a neatly formatted full name."""
if middle:
full_name = f"{first} {middle} {last}"
else:
full_name = f"{first} {last}" return full_name.title()
我们再给测试用例添加额外的测试:
# test_name_function.py
class NamesTestCase(unittest.TestCase):
def test_first_last_name(self):
formatted_name = get_formatted_name('andrew', 'yang')
self.assertEqual(formatted_name, 'Andrew Yang')
def test_first_last_middle_name(self):
get_formatted_name( 'wolfgang', 'mozart', 'amadeus')
self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')
if __name__ == '__main__':
unittest.main()
当我们再次运行测试代码的时候,我们则会得到以下信息:
..
---------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
测试一个类
除了为单个函数写测试外,我们也可以为类写测试,来帮助类的内部函数运行正确。以下是常用的assert方法,帮助我们进行各种不同的测试:
测试类和测试函数很相似,大部分测试工作也是测试类内部的方法。我们先来创建一个能统计问卷调查的类:
class AnonymousSurvey:
"""Collect anonymous answers to a survey question."""
def __init__(self, question):
"""Store a question, and prepare to store responses."""
self.question = question
self.responses = []
def show_question(self):
"""Show the survey questions."""
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 the responses that have been given."""
print("Survey results:")
for response in self.responses:
print(f"- {response}")
这个类可以根据调查的问题,收集答案,并且可以把全部回答打印出来。如果要测试答案,我们可以写以下的测试:
# test_survey.py
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
"""Tests for the class AnonymousSurvey"""
def test_store_single_response(self):
"""Test that a single response is stored properly."""
self.assertIn('Fuzhouness', my_survey.responses)
def test_store_three_responses(self):
question = "What language did you first learn to speak?"
my_survey = AnonymousSurvey(question)
responses = ['Fuzhouness', "Chinese", "English"]
for response in responses:
my_survey.store_response(response)
self.assertIn('English', my_survey.responses)
if __name__ == '__main__':
unittest.main()
在上面的代码中,我们在每一个测试函数内创建了一个AnonymousSurvey实例,为了简化测试,我们可以在测试类中创建一个setUp()函数,并在其中创建一个公用的AnonymousSurvey实例。
在Python中,当我们在一个TestCase类中创建setUp函数后,再次运行测试函数时,Python会在运行内部方法前优先运行setUp函数:
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
def setUp(self):
question = "What language did you first learn to speak?"
self.my_survey = AnonymousSurvey(question)
self.responses = ['Fuzhounese', 'Chinese', 'English']
def test_store_single_response(self):
question = "What language did you first learn to speak?"
slef.my_survey.store_response(self.response(self.response[0])
self.assertIn('Fuzhouness', my_survey.responses)
def test_store_responses(self):
question = "What language did you first learn to speak?"
for response in responses:
self.my_survey.store_response(response)
for response in responses:
self.assertIn(response, self.my_survey.responses)
if __name__ == '__main__':
unittest.main()
Homework
请大家在先创建一个要被测试的字典类:
# mydict.py
class MyDict(dict):
def __init__(self, **kw):
super().__init__(**kw)
def put(self, key, value):
self[key] = value
def get(self, key):
return self[key]
然后请大家编写自己的测试用例,来测试字典的set和get函数。
from mydict import MyDict
test1 = MyDict()
test1.put('man', 'zhiwei zhou')
test1.get('man')
'zhiwei zhou'
注意点:
最后需要隐藏程序入口,需要加上:
if __name__ == '__main__':
unittest.main()