文章目录
- 1. 测试函数
- 简单的函数测试
- 单元测试和测试用例
- 可通过的测试
- 不可通过的测试
- 测试未通过时怎么办
- 2. 测试类
- 各种断言方法
- 测试一个类
- 测试 AnonymousSurvey
- 方法setUp()
导言
在编写函数或类时,还可为其编写测试。通过测试,可以确定代码面对各种输入都能够按要求的那样工作。或者在程序添加新的代码功能时,你也可以对其进行测试,确认它们不会破坏程序既有的作为。程序员都会犯错,因此每个程序员都必须经常测试其代码,在用户发现问题前找出它们。
本章,我们将讲述如何使用 python 的 unittest 中的工具来测试代码,我们进行编写测试用例,核实一系列输入都将得到预期的输出。我们可以看到通过测试将会是什么样子,测试未通过又是什么样子,还将知道测试未通过如何有助于改进代码。你将学习如何测试函数和类,并将知道该为项目编写多少个测试。
1. 测试函数
简单的函数测试
进行函数测试的前提是有可以进行测试的代码。
我们先编写一个简单的函数,它接受名和姓并返回整洁的姓名:
def get_name(first,last):
name = first +" "+last
return name.title()
现在我们开始编写测试代码,
我们可以用前面章节学到的知识进行结合,模拟一个用户输入信息的场景,
当用户输入完成自己的用户名信息后,给其返回他录入的信息,告诉他定义的信息为:
from name_test import get_name
print('您已成功登陆系统,请录入您的用户名.....')
while True:
firstname = input('请录入您的姓氏:')
lastname = input('请您继续存储您的名称:')
names = get_name(firstname,lastname)
print("您注册的用户名称为:"+ names)
经测试运行后发现,我们编写的返回姓名信息的函数逻辑是正确可行的。
单元测试和测试用例
python 标准库中的模块 unittest提供了代码测试工具。单元测试用于核实函数的某个方面没有问题;
测试用例考虑到了函数可能收到的各种输入,包含针对所有这些情形的测试。全覆盖式测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。对于大型项目,要实现全覆盖可能很难。通常,最初只要针对代码的重要行为编写测试即可,等项目被广泛使用时再考虑全覆盖。
可通过的测试
创建测试用例的语法需要一段时间才能习惯,但测试用例创建后,再添加针对函数的单元测试就很简单了。
要为函数编写测试用例,首先需要导入 unittest 模块,以及要进行测试的函数。
再创建一个继承 unittest.TestCase的类,并编写一系列方法对函数行为的不同方面进行测试。
#导入单元测试
import unittest
#导入进行测试的函数
from name_test import get_name
#创建类,用于包含一系列针对测试函数的单元测试
class NamesTestCase(unittest.TestCase):
#开始进行函数测试
def test_first_last_name(self):
name = get_name('兔','c')
self.assertEqual(name,'兔 C')
unittest.main()
编写的这段单元测试代码,测试的上一小节知识点中定义的 get_name()函数。
在这个代码中,导入了两个类,我们先看一下运行结果,在来解释代码行。
看看运行结果,到底能否通过调用 get_name()函数 返回名字。
第一行的句点表明有一个测试通过了。接下来的一行指出python运行了一个测试,消耗的时间不到0.001秒。最后一行的ok表示该测试用例中的单元测试都通过了。
接下来,我们开始逐行解释单元测试中的代码行。
首先,我们导入了模块 unittest,还有要进行测试的 get_name() 函数。
然后,我们又创建了一个类,类名为 NamesTestCase,用于包含一系列针对get_name的单元测试。至于类的命名规范一定要与进行测试的内容相关,并且还要包含Test字样。除了这些规范,还一定要继承 unittest.TestCase类,这样 python 才会知道如何运行你编写的测试。
NamesTestCase 只包含一个方法,用于测试 get_name() 函数的一个方法。我们将这个方法命名为 test_name_last_name()。因为我们要核实的是只有名和姓的姓名能否被正确的格式化。
当我们运行当前这个类的后缀为.py 文件时,所有 以 test 打头的方法都将自动运行。在这个方法中,我们调用了要测试的函数,并存储了要测试的返回值。在这个示例中,我们使用实参 ‘兔’ 和 'c’调用 get_name(),并将返回的结果存储到 name 变量当中。
unittest 类最有用的功能之一:一个断言方法。断言方法用来核实得到的结果是否与期望的结果一致。在这里,我们知道 get_name() 应返回这样的姓名,集名和姓的拼接,如果是英文,就以首字母大写的形式返回。
为了检查返回的内容和我们输入的内容是否一致,我们调用 unittest 的方法:assertEqual() ,至于这个方法中需要传递的参数,1 是变量,也就是存储了 获得到get_name 函数返回内容的变量。2 是 与其相对应的内容。也就是手动录入字符串内容,和其变量中存储的内容进行比对,当然这个比对是通过 asertEqual() 函数进行的。
最后,如果内容相等,就会返回如上图中的结果,反之,它会告诉我们错误信息。
不可通过的测试
什么样的测试不可通过呢?
例如上述示例中,我们定义的 get_name函数只能接收两个参数,但是并非所有人的名字都是两个字的,如果出现三个字的情况呢?
接下里,我们就测试一下三个字的名字,在调用 unittest.assertEqual() 函数进行校对:
import unittest
from name_test import get_name
class NamesTestCase(unittest.TestCase):
#单元测试类中的函数一定要以test开头,才会在测试类启动时自动运行
def test_first_last_name(self):
name = get_name('兔','c','c')
self.assertEqual(name,'兔CC')
unittest.main()
看一下运行结果:
python 解释器开始向我们说明错误了:
可以看到其中包含的信息很多,因为测试未通过。
第一行输出了一个字母E,它指出测试用例中有一个单元测试导致了错误。并且也给我们指出了错误所在行。
测试用例在包含众多单元测试时,知道哪个测试未通过至关重要,traceback,指出函数调用的有问题。
这里我们知道,自己定义的get_name 函数只能接收两个形参,而我们却传递了三个实参。
测试未通过时怎么办
如果你检查的条件没有错误,那说明测试通过了,也意味着函数的行为是对的。
而测试未通过就意味着我们编写的代码有问题了,因此,测试未通过时,不要修改测试,而应修复导致测试不能通过的代码:检查刚对函数所做的修改,找出导致函数行为不符合预期的修改。
现在,我们要做的就是去修改get_name() 函数了:
def get_name(first,last,middle =''):
if middle:
name = first + ' ' + middle + ' ' + last
else:
name = first + ' ' + last
return name.title()
改好了 get_name 函数,我们在执行一下刚才的单元测试:
可以看到,现在的测试结果就通过了。
2. 测试类
上半部分,我们编写了针对函数的测试用例。下面,我们将接触针对类的测试。在很多程序中都会用到类,因此能够证明你的类能够正确地工作会大有裨益。如果针对类的测试通过了,你就能确信对类所做的改进没有意外地破坏其原有的行为。
各种断言方法
python 在 unittest 模块中提供了很多断言方法。
我们前面探讨过,断言方法检查认为应该满足的条件是否确实满足。如果该条件满足,你对程序行为的假设得到了确认,你就可以确信其中没有错误。如果你认为应该满足的条件实际上并不满足,python 将引发异常。
下面我们来列举 六个常用的断言方法:
使用试着方法可以核实返回的值等于或不等于预期的值,返回的值为True 或 False,返回的值在列表中或不在列表中。你只能在继承 unittest.TestCase的类中使用这些方法。
方法 | 用途 |
---|---|
assertEqual(a,b) | 核实 a == b |
assertNotEqual(a,b) | 核实 a != b |
assertTrue(x) | 核实x为True |
assertFalse(x) | 核实x为False |
assertIn(item,list) | 核实item在list中 |
assertNotIn(item,list) | 核实item不在list中 |
测试一个类
类的测试与函数的测试相似——你所做的大部分工作都是测试类中方法的行为,但存在一些不同之处,下面来编写一个类进行测试。
一个帮助管理匿名调查的类:
class AnonymousSurvey():
"""收集匿名调查问卷的答案"""
#初始化方法:存储一个问题,并为存储答案做准备
def __init__(self,question):
self.question = question
#空列表用于存储收集到的答案
self.response = []
#调查问题的方法
def show_question(self):
print(self.question)
#添加新答案的方法
def store_response(self,new_response):
self.response.append(new_response)
#显示收集到的所有答案
def show_results(self):
for response in self.response:
print('-调查结果: '+response)
我们用刚定义好的 AnonymousSurvey 类描述收集匿名调查问卷的答案。
其中:
__ init __ 初始化方法,用于收集一个问题,并将之后收集到的答案存储在空列表当中。
show_question 方法,用于调查问题。
store_response 方法,用于添加收集到的新答案。
show_results 方法,用于显示收集到的所有答案。
如果要想知道这个类能否正确地工作,我们需要编写一个使用它的类:
from survey import AnonymousSurvey
#定义一个问题
question ="可以邀请您来参与我们的程序员问卷活动吗?"
#并使用这个定义的问题创建 AnonymousSurvey对象
my_survey = AnonymousSurvey(question)
#显示问题
my_survey.show_question()
print('录入q键可终止调查活动.....')
while True:
response = input('请问您最喜欢哪一门编程语言?')
if response == 'q':
break
my_survey.store_response(response)
#显示调查结果:
print('感谢您对本次调查问卷活动的支持!')
my_survey.show_results()
现在,我们编写好了 用于进行匿名调查问卷的 AnonymousSurvey 类,还有对该类进行使用的类,我们看一下使用的效果:
如果现在要对 AnonymousSurvey 类进行修改,例如:允许每位用户输入多个答案时,就会导致可能不小心修改处理单个答案的方式。要确认在开发这个模块时没有被破坏既有行为,可以编写针对这个类的测试。
测试 AnonymousSurvey
下面来编写一个测试,对 AnonymousSurvey 类的行为一个方面进行验证:
如果用户面对调查问题时只提供了一个答案,这个答案也能被妥善地存储。为此,我们将在这个答案被存储后,使用方法 assertIn()来核实它保存在答案列表中:
import unittest
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
def test_store_single_language(self):
question = '请问可以邀请你参与程序员调查问卷吗?'
my_survey = AnonymousSurvey(question)
my_survey.store_response('c') #进行单个答案的存储
self.assertIn('c',my_survey.response)
unittest.main()
在这段代码中,我们先导入了 unittest模块和 AnonymousSurvey 类。
接着,我们开始定义测试类,并且让测试类继承 unittest模块下的 TestCase类,
然后开始在测试类中定义测试方法,注意:测试方法需要以test_开头,这样在执行这个文件时,类中的test方法都会被自动执行。
同样的,我们先进行提问的问题设置,设置好了之后,创建存储该问题的实例,接下来,我们在通过实例来调用它内部的存储方法,将参数存储进去。
之后,调用核实内容是否在其内部的函数进行校对。
我们来看一下测试结果:
这很好,证明我们的代码都没有什么问题。可是只能收集一个答案的调查用途不大。
下面我们来核实用户提供三个答案时,它们也将被妥善地存储,为此,我们需要在 TestAnonymousSurvey 中再添加一个方法:
def test_stroe_three_response(self):
#设置调查问题
question = '请问您最喜欢哪一门编程语言?'
#创建实例,并存储问题
my_survey = AnonymousSurvey(question)
#存储答案
responses =['c','java','python']
#遍历列表,将列表元素存储到调查问卷答案列表中
for response in responses:
my_survey.store_response(response)
#再次遍历列表,调用 校对是否包含内容的assertIn方法
for response in responses:
self.assertIn(response,my_survey.response)
这次,我将代码行的逐行含义写在了注释中。
我们来看一下这个测试函数的测试结果:
方法setUp()
在前面的测试实例中,我们在每个测试方法中都创建了一个 AnonymousSurvey 实例,并在每个方法中都创建了答案。unittest.TestCase类包含方法 setUp(),让我们只需创建这些对象一次,并能够在每个测试方法中使用它们。
如果你在 TestCase类中包含了 setUp() 方法,python 将先运行它,再运行各个以test_打头的方法。这样,在你编写的每个测试方法中都可使用在方法setUp()中创建的对象了。
现在我们就使用 setUp()来创建一个调查对象和一组答案,供方法test_store_single_response() 和 test_store_three_responses() 使用:
from survey import AnonymousSurvey
class TestAnonymousSurvey(unittest.TestCase):
def setUp(self):
"""创建一个对象和一组答案,供使用的测试方法使用"""
question = '请问你最喜欢哪一门编程语言?'
self.my_survey = AnonymousSurvey(question)
self.response = ['c','java','python']
def test_store_single_language(self):
self.my_survey.store_response(self.response[0])
self.assertIn(self.response[0],self.my_survey.response)
def test_store_three_responses(self):
for response in self.response:
self.my_survey.store_response(response)
for response in self.response:
self.assertIn(response,self.my_survey.response)
unittest.main()
测试自己编写的类时,方法 setUp() 让测试方法编写起来更容易:可在 setUp() 方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用实例。相比于在每个测试方法中都创建实例并设置其属性容易的多。