Python 单元测试(unittest)的使用小结


Posted in Python onNovember 14, 2018

测试目录

项目的整体结构可以参考“软件目录开发规范”,这里单说测试目录。一般都是在项目里单独创建一个测试目录,目录名就是“tests”。

关于目录的位置,一种建议是,在项目名(假设项目名是Foo)的一级子目录下创建二级子目录 “Foo/foo/tests” 。但是这样可能是因为用起来不方便,有很多是按下面的做法。不过下面的示例我还是用这个方法来创建测试目录。
还可以把测试目录向上移一层,作为一级子目录,直接创建在项目之下 “Foo/tests”。参考django、scrapy、flask都是这样的做法。

测试函数

标题的意思是对函数(def)进行测试,相对于测试类(class)。

学习测试,得有要测试的代码。下面是一个简单的函数,接收城市名和国家名,返回一个格式为“City, Country“这样的字符串:

# UnitTest/unit_test/utils/city_functions.py
def get_city_info(city, country):
  city_info = "%s, %s" % (city, country)
  return city_info.title()

接下来就对上面的这个函数进行测试。

手动测试

现在来写一个使用这个函数的程序:

# UnitTest/unit_test/test/cities.py
try:
  from unit_test.utils.city_functions import get_city_info
except ModuleNotFoundError:
  import sys
  sys.path.append('../..')
  from unit_test.utils.city_functions import get_city_info

print("Enter 'q' at any time to quit.")
while True:
  city = input("city name: ")
  if city == 'q':
    break
  country = input("country name: ")
  if country == 'q':
    break
  fullname = get_city_info(city, country)
  print("\tcity info:", fullname)

然后运行的结果:

Enter 'q' at any time to quit.
city name: shanghai
country name: china
    city info: Shanghai, China
city name: q

Process finished with exit code 0

上面这样是手动测试,还是得有一种自动测试函数输出的高效方式。如果能够对get_fullname()进行自动测试,就能始终确信,给这个函数提供测试过的姓名后,它能返回正确的结果。尤其是在对函数进行修改的前后。

模块导入路径的问题

PyCharm会自动把项目目录加到环境变量里去,在PyCharm里执行都没问题。但是如果不用PyCharm而是单独运行,这个目录结构应该会有点问题,会找不到需要测试的函数。简单点就是把测试用例和被测试的函数放到同一个目录里,然后改一下 from import 就可以正常运行了。或者自己手动添加环境变量,就像例子里那样。

单元测试-unittest

Python标准库中的模块unittest提供了代码测试工具。

创建测试用例

为函数编写测试用例,可先导入模块unittest以及要测试的函数,再创建一个继承unittest.TestCase的类,并编写一系列方法对函数行为的不同方面进行测试。

下面是一个只包含一个方法的测试用例:

# UnitTest/unit_test/test/test_city_functions.py
import unittest
try:
  from unit_test.utils.city_functions import get_city_info
except ModuleNotFoundError:
  import sys
  sys.path.append('../..')
  from unit_test.utils.city_functions import get_city_info

class CitiesTestCase(unittest.TestCase):
  """测试city_functions.py"""
  def test_city_country(self):
    city_info = get_city_info('shanghai', 'china')
    self.assertEqual(city_info, 'Shanghai, China')

  def test_New_York(self):
    city_info = get_city_info('new york', 'America')
    self.assertEqual(city_info, 'New York, America')

if __name__ == '__main__':
  unittest.main()

命名的规则和建议:

  • 类名,可以任意起名,但是最好看起来和测试有关并包含Test字样。
  • 方法名,名字必须以“test_”开头,所有以“test_”开头的方法,都会自动运行

在测试的方法的最后,使用了unittest类最有用的功能之一:一个断言方法。来检查得到的结果和我们预期的结果是否一致。

输出的效果

最后一行 unittest.main() 让Python运行这个文件中的测试。执行程序后得到如下的输出:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

运行测试用例时,每完成一个单元测试,Python都打印一个字符:

  • 测试通过时打印一个句点;
  • 测试引发错误时打印一个E;
  • 测试导致断言失败时打印一个F。

这就是你运行测试用例时,在输出的第一行中看到的句点和字符数量各不相同的原因。如果测试用例包含很多单元测试,需要运行很长时间,就可通过观察这些结果来获悉有多少个测试通过了。

PyCharm对单元测试做了自己的优化,输出看不到上面的点,而是有更加漂亮的展示方式。

测试不通过

现在看下测试不通过的效果。这里不修改测试用例,而是对get_city_info()函数做一个update,现在还要显示城市的人口数量:

def get_city_info(city, country, population):
  city_info = "%s, %s - 人口: %d" % (city, country, population)
  return city_info.title()

这次再执行测试用例,输出如下:

E
======================================================================
ERROR: test_city_country (__main__.CitiesTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_city_functions.py", line 17, in test_city_country
    city_info = get_city_info('shanghai', 'china')
TypeError: get_city_info() missing 1 required positional argument: 'population'

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (errors=1)

这里看的是E而不是之前的点,表示有一个错误。

测试未通过的处理

这里不要去修改之前的测试用例。假设update之前的函数已经在项目内使用起来了,现在测试不通过,表示之前调用这个函数的代码都有问题。如果不想改项目里其它的代码,这里先尝试修改get_city_info()函数,让它能够通过测试,也可以加上新的功能:

# UnitTest/unit_test/utils/city_functions.py
def get_city_info(city, country, population=None):
  if population:
    city_info = "%s, %s - 人口: %d" % (city, country, population)
  else:
    city_info = "%s, %s" % (city, country)
  return city_info.title()

现在的各个版本的update才是兼容旧版本的代码,这次测试用例就可以通过了。

添加新测试

之前的测试用例只能验证就的功能,现在添加了新功能,是否没问题,还得通过测试来进行验证:

# UnitTest/unit_test/test/test_city_functions.py
class CitiesTestCase(unittest.TestCase):
  """测试city_functions.py"""
  def test_city_country(self):
    city_info = get_city_info('shanghai', 'china')
    self.assertEqual(city_info, 'Shanghai, China')

  def test_New_York_population(self):
    city_info = get_city_info('new york', 'America', 8537673)
    self.assertEqual(city_info, 'New York, America - 人口: 8537673')

现在新功能的测试用例也用了,并且2个测试都能通过。以后如果还需要对get_city_info()函数进行修改,只要再运行测试就可以知道新的代码是否会对原有的项目有影响。

断言方法

模块在unittest.TestCase类中提供了很多断言方法,之前已经用一个了。下面是6个常用的断言方法:

  • 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中

你只能在继承unittest.TestCase的类中使用这些方法。

测试类

前面的内容只是对函数进行测试。很多时候都会用到类,因为还需要能够证明类也可以正常的运行。类的测试与函数的测试相似,其中大部分工作都是测试类中方法的行为,但存在一些不同之处。

准备要测试的类

先编写一个类来进行测试,这个类里存储了一个课程名,以及学习该课程的学员:

# UnitTest/unit_test/course.py
class CourseManage(object):

  def __init__(self, course):
    self.course = course
    self.students = []

  def show_course(self):
    print("课程:", self.course)

  def add_student(self, name):
    self.students.append(name)

  def show_students(self):
    print("所有学员:")
    for student in self.students:
      print('-', student)

为证明CourseManage类工作正常,再编写一个使用它的程序:

from unit_test.course import CourseManage

course = CourseManage("Python")
course.show_course()
print("准备录入学员...")
print("Enter 'q' at any time to quit.\n")
while True:
  resp = input("Student's Name: ")
  if resp == 'q':
    break
  if resp:
    course.add_student(resp.title())
print("\n录入完毕...")
course.show_students()

这段程序定义了一门课程,并使用课程名创建了一个CourseManage对象。接下来主要就是调用对象的add_student()方法来录入学员名字。输入完毕后,按q能退出。最后会打印所有的学员。
所有的输入和输出如下:

课程: Python
准备录入学员...
Enter 'q' at any time to quit.

Student's Name: oliver queen
Student's Name: barry allen
Student's Name: kara
Student's Name: sara lance
Student's Name: q

录入完毕...
所有学员:
- Oliver Queen
- Barry Allen
- Kara
- Sara Lance

Process finished with exit code 0

编写类的测试用例

下面来编写一个测试,对CourseManage类的行为的一个方面进行验证。如果用户输入了某个学员的名字,这个名字可以被存储在self.students的列表里。所以,需要做的是在学员被录入后,使用assertIn()这个断言方法:

# UnitTest/unit_test/test/test_course.py
import unittest
from unit_test.course import CourseManage

class TestCourseManage(unittest.TestCase):

  def test_add_student(self):
    course = CourseManage("Python")
    name = 'snart'
    course.add_student(name.title())
    self.assertIn('Snart', course.students)

if __name__ == '__main__':
  unittest.main()

上面的方法只验证了录入一个学员的情况,而大多数情况下都是有很多学员的。所以,还要添加一个方法,验证录入多个学员是否正常:

class TestCourseManage(unittest.TestCase):

  def test_add_student(self):
    course = CourseManage("Python")
    name = 'snart'
    course.add_student(name.title())
    self.assertIn('Snart', course.students)

  def test_add_students(self):
    course = CourseManage("Python")
    name_list = ['oliver queen', 'barry allen', 'kara', 'sara lance']
    for name in name_list:
      course.add_student(name.title())
    for name in name_list:
      self.assertIn(name.title(), course.students)

setUp() 方法

在上面的例子里,每个测试方法中都创建了一个实例。但是还有一种需求是,我希望只创建一个实例,但是要在多个方法里对这个实例进行操作来反复验证。在unittest.TestCase类包含方法setUp(),就可以只实例化一次,并可以在每个测试方法中使用。如果在TestCase类中包含了方法setUp(),Python会先运行它,再运行各个以test_打头的方法。
简单点说,setUp()方法就是在父类里预留的一个钩子,会在其他测试方法运行前先运行:

import unittest
from unit_test.course import CourseManage

class TestCourseManage(unittest.TestCase):

  def setUp(self):
    self.course = CourseManage("Python")
    self.name_list = ['oliver queen', 'barry allen', 'kara', 'sara lance']

  def test_add_student(self):
    name = 'snart'
    self.course.add_student(name.title())
    self.assertIn('Snart', self.course.students)

  def test_add_students(self):
    for name in self.name_list:
      self.course.add_student(name.title())
    for name in self.name_list:
      self.assertIn(name.title(), self.course.students)

if __name__ == '__main__':
  unittest.main()

测试自己编写的类时,使用setUp()方法会让测试方法编写起来更容易,下面是建议的做法:

在setUp()方法中创建一系列实例并设置它们的属性,再在测试方法中直接使用这些实例。相比于在每个测试方法中都创建实例并设置其属性,这要容易得多。

小结

如果你在项目中包含了初步测试,其他程序员将更敬佩你,他们将能够更得心应手地尝试使用你编写的代码,也更愿意与你合作开发项目。如果你要跟其他程序员开发的项目共享代码,就必须证明你编写的代码通过了既有测试,通常还需要为你添加的新行为编写测试。

请通过多开展测试来熟悉代码测试过程。对于自己编写的函数和类,请编写针对其重要行为的测试,但在项目早期,不要试图去编写全覆盖的测试用例,除非有充分的理由这样做。

pytest

这篇讲的是Python内置的单元测试模块。作为初学者先用着熟悉起来就很不错了。

pytest是Python最流程的单测框架之一。具体可以上GitHub参考下那些开源项目的单元测试,很多用的是这个。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
用Python实现QQ游戏大家来找茬辅助工具
Sep 14 Python
python实现带错误处理功能的远程文件读取方法
Apr 29 Python
python3.6 +tkinter GUI编程 实现界面化的文本处理工具(推荐)
Dec 20 Python
读取json格式为DataFrame(可转为.csv)的实例讲解
Jun 05 Python
python3实现磁盘空间监控
Jun 21 Python
python 切换root 执行命令的方法
Jan 19 Python
Django之提交表单与前后端交互的方法
Jul 19 Python
微信小程序python用户认证的实现
Jul 29 Python
django 微信网页授权认证api的步骤详解
Jul 30 Python
Django中密码的加密、验密、解密操作
Dec 19 Python
浅析Python requests 模块
Oct 09 Python
python多线程方法详解
Jan 18 Python
python for循环输入一个矩阵的实例
Nov 14 #Python
python获取中文字符串长度的方法
Nov 14 #Python
对python插入数据库和生成插入sql的示例讲解
Nov 14 #Python
python正向最大匹配分词和逆向最大匹配分词的实例
Nov 14 #Python
对python中的乘法dot和对应分量相乘multiply详解
Nov 14 #Python
在python中实现对list求和及求积
Nov 14 #Python
python 统计一个列表当中的每一个元素出现了多少次的方法
Nov 14 #Python
You might like
使用PHP 5.0创建图形的巧妙方法
2010/10/12 PHP
解析PHP正则提取或替换img标记属性
2013/06/26 PHP
PHP使用Redis长连接的方法详解
2018/02/12 PHP
javascript的键盘控制事件说明
2008/04/15 Javascript
javascript,jquery闭包概念分析
2010/06/19 Javascript
修复ie8&chrome下window的resize事件多次执行
2011/10/20 Javascript
css transform 3D幻灯片特效实现步骤解读
2013/03/27 Javascript
JQuery中dataGrid设置行的高度示例代码
2014/01/03 Javascript
jQuery中Ajax全局事件引用方式及各个事件(全局/局部)执行顺序
2016/06/02 Javascript
JavaScript中获取时间的函数集
2016/08/16 Javascript
js获取时间函数及扩展函数的方法
2016/10/30 Javascript
Node.JS 循环递归复制文件夹目录及其子文件夹下的所有文件
2017/09/18 Javascript
Node.js学习之TCP/IP数据通讯(实例讲解)
2017/10/11 Javascript
Popup弹出框添加数据实现方法
2017/10/27 Javascript
JS生成随机打乱数组的方法示例
2017/12/23 Javascript
JavaScript实现微信红包算法及问题解决方法
2018/04/26 Javascript
JavaScript解析及序列化JSON的方法实例分析
2019/01/04 Javascript
微信小程序云开发之使用云函数
2019/05/17 Javascript
Vue学习笔记之计算属性与侦听器用法
2019/12/07 Javascript
JavaScript动态生成表格的示例
2020/11/02 Javascript
js实现弹幕墙效果
2020/12/10 Javascript
python实现连接mongodb的方法
2015/05/08 Python
pandas groupby 分组取每组的前几行记录方法
2018/04/20 Python
python数据预处理 :数据抽样解析
2020/02/24 Python
Python使用文件操作实现一个XX信息管理系统的示例
2020/07/02 Python
python 利用jieba.analyse进行 关键词提取
2020/12/17 Python
使用CSS3美化HTML表单的技巧演示
2016/05/17 HTML / CSS
英国领先的大码时装品牌之一:Elvi
2018/08/26 全球购物
上海雨人软件技术开发有限公司测试题
2015/07/14 面试题
数学系毕业生求职信
2014/05/29 职场文书
质量负责人岗位职责
2015/02/15 职场文书
小学教师个人工作总结2015
2015/04/20 职场文书
提档介绍信范文
2015/10/22 职场文书
导游词之广州陈家祠
2019/10/21 职场文书
php微信小程序解包过程实例详解
2021/03/31 PHP
Python 数据可视化工具 Pyecharts 安装及应用
2022/04/20 Python