兔c同学的一篇:使用python 的 unittest模块对类和函数进行测试

导言

在编写函数或类时,还可为其编写测试。通过测试,可以确定代码面对各种输入都能够按要求的那样工作。或者在程序添加新的代码功能时,你也可以对其进行测试,确认它们不会破坏程序既有的作为。程序员都会犯错,因此每个程序员都必须经常测试其代码,在用户发现问题前找出它们。
本章,我们将讲述如何使用 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() 方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用实例。相比于在每个测试方法中都创建实例并设置其属性容易的多。

猜你喜欢

转载自blog.csdn.net/tianlei_/article/details/129272026