Python单元测试实例详解


Posted in Python onMay 25, 2018

本文实例讲述了Python单元测试。分享给大家供大家参考,具体如下:

在Python中进行单元测试需要用到自动单元测试框架PyUnit,Python2.1及其以后的版本都将PyUnit作为一个标准模块(即python的unittest模块),如果你很out,那么你需要从PyUnit网站下载源码安装后才能使用。

一、Python单元测试范例

测试最基本的原理是比较预期结果是否与实际执行结果相同,如果相同则测试成功,否则测试失败。为了更好地理解自动测试框架PyUnit,下面会以对Widget类进行测试为例说明之:

#widget.py
#将要被测试的类Widget
class Widget:
  def __init__(self, size = (40, 40)):
    self._size = size
  def getSize(self):
    return self._size
  def resize(self, width, height):
    if width < 0 or height < 0:
      raise ValueError, "illegal size"
    self._size = (width, height)
  def dispose(self):
    pass

二、测试用例TestCase

软件测试中最基本的组成单元式测试用例(test case),PyUnit使用TestCase类来表示测试用例,并要求所有用于执行测试的类都必须从该类继承。TestCase子类实现的测试代码应该是自包含的(self contained),即测试用例既可以单独运行,也可以和其它测试用例构成集合共同运行。TestCase类中常用的函数或方法有:

setUp:进行测试前的初始化工作。
tearDown:执行测试后的清除工作。
failedinfo:表示不成立打印信息faliedinfo,为可选参数。
self.assertEqual(value1, value2, failedinfo):会无条件的导致测试失败,不推荐使用。
self.assertTrue(, failedinfo):断言value1 == value2。
self.assertFalse(, failedinfo):断言value为真。
self.assertRaises(ValueError, self.widget.resize, -1, -1):断言肯定发生异常,如果没发生异常,则为测试失败。参数1为异常,参数2为抛出异常的调用对象,其余参数为传递给可调用对象的参数。
TestCase在PyUnit测试框架中被视为测试单元的运行实体,Python程序员可以通过它派生自定义的测试过程与方法(测试单元),利用Command和Composite设计模式,多个TestCase还可以组合成测试用例集合。PyUnit测试框架在运行一个测试用例时,TestCase子类定义的setUp()runTest()tearDown()方法被依次执行,最简单的测试用例只需要覆盖runTest()方法来执行特定的测试代码就可以了。

1、静态方法

一个测试用例只对软件模块中一个方法进行测试,采用覆盖runTest()方法来构造测试用例,这在PyUnit中称之为静态方法,举例说明如下:

#static.py
from widget import Widget
import unittest
#执行测试的类
class WidgetTestCase(unittest.TestCase):
  def runTest(self):
    widget = Widget()
    self.assertEqual(widget.getSize(), (40, 40))
#测试
if __name__ == "__main__":
  testCase = WidgetTestCase()
  testCase.runTest()

如果采用静态方法,Python程序员就不得不为每个要测试的方法编写一个测试类,该类通过覆盖runTest()方法来执行测试,并在每个测试类中生成一个待测试的对象,这样会非常繁琐与笨拙。

2、动态方法

鉴于静态方法的缺陷,PyUnit提供了另一种高帅富的解决方法,即动态方法,只编写一个测试类来完成对整个软件模块的测试,这样对象的初始化工作可以在setUp()方法中完成,而资源的释放则可以在tearDown()方法中完成,举例说明如下:

#dynamic.py
from widget import Widget
import unittest
class WidgetTestCase(unittest.TestCase):
  def setUp(self):
    self.widget = Widget()
  def tearDown(self):
    self.widget.dispose()
    self.widget = None
  def testSize(self):
    self.assertEqual(self.widget.getSize(), (40, 40))
  def testResize(self):
    self.widget.resize(100, 100)
    self.assertEqual(self.widget.getSize(), (100, 100))

动态方法不再覆盖runTest()方法,而是为测试类编写多个测试方法,按照惯例这些方法通常以test开头但这不是必须的,在创建TestCase子类的实例时必须给出测试方法的名称来为PyUnit测试框架指明运行该测试用例时应该调用测试类中的哪些方法,这通常会结合测试用例集TestSuite一起使用。

三、测试用例集TestSuite

完整的单元测试很少只执行一个测试用例,开发人员通常需要编写多个测试用例才能对某一软件功能进行比较完成的测试,这些相关的测试用例称为一个测试用例集,在PyUnit中是用TestSuite类来表示的。PyUinit测试框架允许Python程序员在单元测试代码中定义一个名为suite()的全局函数,并将其作为整个单元测试的入口,PyUnit通过调用它来完成整个测试过程:

def suite():
  suite = unittest.TestSuite()
  suite.addTest(WidgetTestCase("testSize"))
  suite.addTest(WidgetTestCase("testResize"))
  return suite
    也可以直接定义一个TestSuite的子类,并在其初始化方法__init__中完成所有测试用例的添加:
class WidgetTestSuite(unittest.TestSuite)
  def __init__(self):
    unittest.TestSuite.__init__(self, map(WidgetTestCase, ("testSize", "testResize")))
    这样只需要在suite()方法中返回该类的一个实例就可以了:
def suite():
  return WidgetTestSuite()

在PyUnit测试框架中,TestSuite类可以看成是TestCase类的一个容器,用来对多个测试用例进行组织,这样多个测试用例可以自动在一次测试中全部完成。事实上,TestSuite除了可以包含TestCase外,也可以包含TestSuite,从而可以构成一个更庞大的测试用例集:

suite1 = mysuite1.TheTestSuite()
suite2 = mysuite2.TheTestSuite()
alltests = unittest.TestSuite((suite1, suite2))

四、实施测试TestRunner

编写测试用例(TestCase)并将它们组织成测试用例集(TestSuite)的最终目的只有一个:实施测试并获得最终结果。PyUnit使用TestRunner类作为测试用例的基本执行环境,来驱动整个单元测试过程。但是Python开发人员在进行单元测试时一般不直接使用TestRunner类,而是使用其子类TextTestRunner来完成测试,并将测试结果以文本方式显示出来。举例说明如下:

#text_runner.py
from widget import Widget
import unittest
#执行测试的类
class WidgetTestCase(unittest.TestCase):
  def setUp(self):
    self.widget = Widget()
  def tearDown(self):
    self.widget.dispose()
    self.widget = None
  def testSize(self):
    self.assertEqual(self.widget.getSize(), (40, 40))
  def testResize(self):
    self.widget.resize(100, 100)
    self.assertEqual(self.widget.getSize(), (100, 100))
#测试
if __name__ == "__main__":
  #构造测试集
  suite = unittest.TestSuite()
  suite.addTest(WidgetTestCase("testSize"))
  suite.addTest(WidgetTestCase("testResize"))
  #执行测试
  runner = unittest.TextTestRunner()
  runner.run(suite)

使用如下命令执行该单元测试:

$python text_runner.py

Python单元测试实例详解

默认情况下,TextTestRunner将结果输出到sys.stdout/sys.stderr上,但是如果在创建TextTestRunner类实例时将一个文件对象传递给了构造函数,则输出结果将被重定向到该文件中。

五、大道至简main()

PyUnit模块中定义了一个名为main的全局方法,使用它可以很方便地将一个单元测试模块变成可以直接运行的测试脚本,main()方法使用TestLoader类来搜索所有包含在该模块中的测试方法,并自动执行它们。如果Python程序员能够按照约定(以test开头)来命令所有的测试方法,那么只需要在测试模块的最后加入如下几行代码即可:

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

下面是利用main()方法来进行测试的完整例子:

#main_runner.py
from widget import Widget
import unittest
#执行测试的类
class WidgetTestCase(unittest.TestCase):
  def setUp(self):
    self.widget = Widget()
  def tearDown(self):
    self.widget.dispose()
    self.widget = None
  def testSize(self):
    self.assertEqual(self.widget.getSize(), (40, 40))
  def testResize(self):
    self.widget.resize(100, 100)
    self.assertEqual(self.widget.getSize(), (100, 100))
#测试
if __name__ == "__main__":
  unittest.main()

使用如下命令执行上面的单元测试:

$python main_runner.py

如上这样将执行WidgetTestCase中的所有测试方法,但是如果只想执行testSize()方法,则可以如下这般:

$python main_runner.py WidgetTestCase.testSize

如果在单元测试脚本中定义了TestSuite,还可以指定要运行的测试集,使用-h参数可以查看运行该脚本所有可能用到的参数:

$python main_runner.py -h

Python单元测试实例详解

需要注意的是:PyUnit的TestCase中如果有多个test_xxx,则默认按照xxx的字母顺序执行测试用例函数,如果test_xxx之间有依赖关系的话就会出错,解决方法有二:1、解耦;2、编写xxx函数时人为地按字母顺序。

当然,如果你安装了Python 2.7.2及以上版本,你还可以利用discover函数来自动发现并执行测试用例:

$python2.7 -m unittest discover

希望本文所述对大家Python程序设计有所帮助。

Python 相关文章推荐
python模拟新浪微博登陆功能(新浪微博爬虫)
Dec 24 Python
python文件操作整理汇总
Oct 21 Python
Python标准库内置函数complex介绍
Nov 25 Python
python中如何使用朴素贝叶斯算法
Apr 06 Python
python实现list元素按关键字相加减的方法示例
Jun 09 Python
Python内置模块hashlib、hmac与uuid用法分析
Feb 12 Python
用python 实现在不确定行数情况下多行输入方法
Jan 28 Python
33个Python爬虫项目实战(推荐)
Jul 08 Python
python批量解压zip文件的方法
Aug 20 Python
django使用xadmin的全局配置详解
Nov 15 Python
Python中seaborn库之countplot的数据可视化使用
Jun 11 Python
Python函数中apply、map、applymap的区别
Nov 27 Python
python 请求服务器的实现代码(http请求和https请求)
May 25 #Python
django将图片上传数据库后在前端显式的方法
May 25 #Python
python3.6.3+opencv3.3.0实现动态人脸捕获
May 25 #Python
Django1.9 加载通过ImageField上传的图片方法
May 25 #Python
python matplotlib 在指定的两个点之间连线方法
May 25 #Python
基于python OpenCV实现动态人脸检测
May 25 #Python
使用matplotlib画散点图的方法
May 25 #Python
You might like
PHP file_exists问题杂谈
2012/05/07 PHP
PHP中鲜为人知的10个函数
2014/02/28 PHP
ThinkPHP3.1新特性之Action参数绑定
2014/06/19 PHP
通过Email发送PHP错误的方法
2015/07/20 PHP
yii2使用gridView实现下拉列表筛选数据
2017/04/10 PHP
Js组件的一些写法
2010/09/10 Javascript
关于递归运算的顺序测试代码
2011/11/30 Javascript
Javascript的常规数组和关联数组对比小结
2012/05/24 Javascript
JavaScript静态类型检查工具FLOW简介
2015/01/06 Javascript
javascript无刷新评论实现方法
2015/05/13 Javascript
让图片跳跃起来  javascript图片轮播特效
2016/02/16 Javascript
angularjs 中$apply,$digest,$watch详解
2016/10/13 Javascript
浅谈js中几种实用的跨域方法原理详解
2016/12/02 Javascript
微信小程序 Buffer缓冲区的详解
2017/07/06 Javascript
vue.js使用v-model实现表单元素(input) 双向数据绑定功能示例
2019/03/08 Javascript
nuxt踩坑之Vuex状态树的模块方式使用详解
2019/09/06 Javascript
[12:29]《一刀刀一天》之DOTA全时刻19:蝙蝠骑士田伯光再度不举
2014/06/10 DOTA
[01:30]我们共输赢 完美世界城市挑战赛开启全新赛季
2019/04/19 DOTA
Python中使用第三方库xlrd来读取Excel示例
2015/04/05 Python
python cs架构实现简单文件传输
2020/03/20 Python
python 与服务器的共享文件夹交互方法
2018/12/27 Python
Python3实现定时任务的四种方式
2019/06/03 Python
受希腊女神灵感的晚礼服、鸡尾酒礼服和婚纱:THEIA
2018/04/15 全球购物
英国最大的宠物商店:Pets at Home
2019/04/17 全球购物
Edwaybuy西班牙:小米在线商店
2019/12/04 全球购物
澳大利亚在线购买葡萄酒:The Wine Collective
2020/02/20 全球购物
日本语毕业生自荐信
2014/02/01 职场文书
战友聚会策划方案
2014/06/13 职场文书
外贸业务员求职信
2014/06/16 职场文书
中学学校门卫岗位职责
2014/08/15 职场文书
财务人员岗位职责
2015/02/03 职场文书
推销搭讪开场白
2015/05/28 职场文书
投诉信范文
2015/07/02 职场文书
初中地理教学反思
2016/02/19 职场文书
开发一个封装iframe的vue组件
2021/03/29 Vue.js
ubuntu20.04虚拟机无法上网的问题及解决
2022/12/24 Servers