Python单元测试框架(The Python unit testing framework),简称为PyUnit, 是Kent Beck和Erich Gamma这两位聪明的家伙所设计的 JUnit 的Python版本。 而JUnit又是Kent设计的Smalltalk测试框架的Java版本。它们都是各自语言的标准测试框架。
此文档仅阐述针对Python的单元测试PyUnit的设计与使用。如需单元测试框架基本设计的背景 信息,请查阅Kent的原始文章"Simple Smalltalk Testing: With Patterns"。
自从 Python2.1 版本后,PyUnit成为 Python标准库的一部分。
以下内容默认您已经了解Python。我觉得Python 非常简单易学而且让人欲罢不能。
PyUnit可以在Python 1.5.2及更高版本上运行。
作者已经在Linux(Redhat 6.0和6.1以及Debian Potato)和Python 1.5.2, 2.0和2.1上对PyUnit 进行了测试。而且PyUnit已知可以在其它操作系统平台上工作,如Windows和Mac。如果您在 任何系统平台或Python版本中遇到麻烦,请让我知道。
如需了解在JPython和Jython中使用PyUnit的细节,请阅读 在JPython和Jython中使用PyUnit部分。
编写测试所需的类可以在“unittest”模块中找到。此模块是Python 2.1和更高版本的标准 库的一部分。如果你在使用更早版本的Python,你应该从单独的PyUnit发布中获得此模块。
为使此模块能在你的代码中正常工作,你只需确保包含“unittest.py”文件的目录 在你的Python搜索路径中。为此,你可以修改环境变量“$PYTHONPATH”或将此文件 放入当前Python搜索路径中的某一个目录中,比如在Redhat Linux系统中的 /usr/lib/python1.5/site-packages
目录。
注意,你只有完成此项工作才能运行PyUnit所自带的例子,除非你将“unittest.py”复制到 例子目录。
单元测试是由一些测试用例(Test Cases)构建组成的。测试用例是被设置用来检测正确性的 单独的场景。在PyUnit中,unittest
模块中的TestCase
类代表测试用例。
TestCase
类的实例是可以完全运行测试方法和可选的设置 (set-up)以及清除(tidy-up)代码的对象。
TestCase
实例的测试代码必须是自包含的,换言之,它 可以单独运行或与其它任意数量的测试用例共同运行。
通过覆盖runTest
方法即可得到最简单的测试用例子类以运行 一些测试代码:
3 |
class DefaultWidgetSizeTestCase(unittest.TestCase): |
5 |
widget = Widget( "The widget" ) |
6 |
assert widget.size() = = ( 50 , 50 ), 'incorrect default size' |
注意:为进行测试,我们只是使用了Python内建的“assert”语句。如果在测试用例 运行时断言(assertion)为假,AssertionError
异常会被抛出,并且 测试框架会认为测试用例失败。其它非“assert”检查所抛出的异常会被测试框架认为是“errors”。 (参见"更多关于测试条件")
运行测试用例的方法会在后面介绍。现在我们只是通过调用无参数的构造器(constructor) 来创建一个测试用例的实例:
1 |
testCase = DefaultWidgetSizeTestCase() |
现在,这样的测试用例数量巨大且它们的设置需要很多重复性工作。在上面的测试用例中, 如若在100个Widget测试用例的每一个子类中都创建一个“Widget”,那会导致难看的重复。
幸运的是,我们可以将这些设置代码提取出来并放置在一个叫做setUp
的 钩子方法(hook method)中。测试框架会在运行测试时自动调用此方法:
03 |
class SimpleWidgetTestCase(unittest.TestCase): |
05 |
self .widget = Widget( "The widget" ) |
07 |
class DefaultWidgetSizeTestCase(SimpleWidgetTestCase): |
09 |
assert self .widget.size() = = ( 50 , 50 ), 'incorrect default size' |
11 |
class WidgetResizeTestCase(SimpleWidgetTestCase): |
13 |
self .widget.resize( 100 , 150 ) |
14 |
assert self .widget.size() = = ( 100 , 150 ), \ |
15 |
'wrong size after resize' |
如果setUp
方法在测试运行时抛出异常,框架会认为测试遇到了错误并且runTest
不会被执行。
类似的,我们也可以提供一个tearDown
方法来完成在runTest
运行之后的清理工作:
3 |
class SimpleWidgetTestCase(unittest.TestCase): |
5 |
self .widget = Widget( "The widget" ) |
如果setUp
执行成功, 那么无论runTest
是否成功,tearDown
方法都将被执行。
Such a working environment for the testing code is termed a fixture. 这个测试代码的运行环境被称为固件(fixture,译者注:此为暂定译法,意为固定的构件或方法)。
很多小型测试用例经常会使用相同的固件。在这个用例中,我们最终从SimpleWidgetTestCase
继承产生很多仅包含一个方法的类,如DefaultWidgetSizeTestCase
。这是很耗时且不被鼓励的,因此,沿用JUnit的风格,PyUnit提供了一个更简便的方法:
03 |
class WidgetTestCase(unittest.TestCase): |
05 |
self .widget = Widget( "The widget" ) |
09 |
def testDefaultSize( self ): |
10 |
assert self .widget.size() = = ( 50 , 50 ), 'incorrect default size' |
12 |
self .widget.resize( 100 , 150 ) |
13 |
assert self .widget.size() = = ( 100 , 150 ), \ |
14 |
'wrong size after resize' |
在这个用例中,我们没有提供runTest
方法,而是两个不同的测试方法。类实例将创建和销毁各自的self.widget
并运行某一个test
方法。 当创建类实例时,我们必须通过向构造器传递方法的名称来指明哪个测试方法将被运行:
1 |
defaultSizeTestCase = WidgetTestCase( "testDefaultSize" ) |
2 |
resizeTestCase = WidgetTestCase( "testResize" ) |
测试用例实例可以根据它们所测试的特性组合到一起。PyUnit为此提供了一个机制叫做”测试套件“(test suite)。它由unittest
模块中的TestSuite
类表示:
1 |
widgetTestSuite = unittest.TestSuite() |
2 |
widgetTestSuite.addTest(WidgetTestCase( "testDefaultSize" )) |
3 |
widgetTestSuite.addTest(WidgetTestCase( "testResize" )) |
我们稍后会看到,在每个测试模块中提供一个返回已创建测试套件的可调用对象,会是一个使测试更加便捷的好方法:
2 |
suite = unittest.TestSuite() |
3 |
suite.addTest(WidgetTestCase( "testDefaultSize" )) |
4 |
suite.addTest(WidgetTestCase( "testResize" )) |
甚至可写成:
1 |
class WidgetTestSuite(unittest.TestSuite): |
3 |
unittest.TestSuite.__init__( self , map (WidgetTestCase, |
(诚然,第二种方法不是为胆小者准备的)
因为创建一个包含很多相似名称的测试方法的TestCase
子类是一种很常见的模式,所以unittest
模块提供一个便捷方法,makeSuite
,来 创建一个由测试用例类内所有测试用例组成的测试套件:
1 |
suite = unittest.makeSuite(WidgetTestCase, 'test' ) |
需要注意的是,当使用makeSuite
方法时,测试套件运行每个测试用例的顺序是由测试方法名根据Python内建函数cmp
所排序的顺序而决定的。
我们经常希望将一些测试套件组合在一起来一次性的测试整个系统。这很简单,因为多个TestSuite
可以被加入进另一个TestSuite
,就如同 多个TestCase
被加进一个TestSuite
中一样:
1 |
suite1 = module1.TheTestSuite() |
2 |
suite2 = module2.TheTestSuite() |
3 |
alltests = unittest.TestSuite((suite1, suite2)) |
在发布的软件包中的“examples
”目录中,"alltests.py
”提供了使用嵌套测试套件的例子
你可以将测试用例定义与被测试代码置于同一个模块中(例如“widget.py”),但是将测试代码放置在单独的模块中(如“widgettests.py”)会有一些优势:
- 测试模块可以从命令行单独执行
- 测试代码可以方便地从发布代码中分离
- 少了在缺乏充足理由的情况下为适应被测试代码而更改测试代码的诱惑
- 相对于被测试代码,测试代码不应该被频繁的修改
- 被测试代码可以更方法的进行重构
- 既然C语言代码的测试应该置于单独的模块,那何不保持这个一致性呢?
- 如果测试策略改变,也无需修改被测试源代码
我们编写测试的主要目的是运行它们并检查我们的软件是否工作正常。测试框架使用“TestRunner”类来为运行测试提供环境。最常用的TestRunner是TextTestRunner
, 它可以以文字方式运行测试并报告结果:
1 |
runner = unittest.TextTestRunner() |
2 |
runner.run(widgetTestSuite) |
TextTestRunner
默认将输出发送到sys.stderr
,但是你可以通过向它的构造器传递一个不同的类似文件(file-object)对象来改变默认方式。
如需在Python解释器会话中运行测试,这样使用TextTestRunner
是一个理想的方法。
unittest
模块包含一个main
方法,可以方便地将测试模块转变为可以运行测试的脚本。main
使用unittest.TestLoader
类来自动查找和加载模块内测试用例。
因此,如果你之前已经使用test*
惯例对测试方法进行命名,那么你就可以将以下代码插入测试模块的结尾:
1 |
if __name__ = = "__main__" : |
这样,当你从命令行执行你的测试模块时,其所包含的所有测试都将被运行。使用“-h”选项运行模块可以查看所有可用的选项。
如需从命令行运行任意测试,你可以将unittest
模块作为脚本运行,并将所需执行的测试套件中的测试用例名称作为参数传递给此脚本:
1 |
% python unittest.py widgettests.WidgetTestSuite |
or
1 |
% python unittest.py widgettests.makeWidgetTestSuite |
你还可以在命令行指明特定的测试(方法)来执行。如要运行“listtests”模块中的TestCase
类的子类 'ListTestCase
'(参见发布软件包中的“examples”子目录), 你可以执行以下命令:
1 |
% python unittest.py listtests.ListTestCase.testAppend |
“testAppend”是测试用例实例将要执行的测试方法的名称。你可以执行以下代码来创建ListTestCase
类实例并执行其所包含的所有“test*”测试方法:
1 |
% python unittest.py listtests.ListTestCase |
你还可以使用图形化窗口运行你的测试。它是用Tkinter
编写的。在多数平台上,这个窗口工具与Python是捆绑在一起发布的。它看上去和JUnit窗口很相似。
你只需运行以下命令来使用测试运行窗口:
1 |
% python unittestgui.py |
or
1 |
% python unittestgui.py widgettests.WidgetTestSuite |
这里需要注意的是,所输入的测试名称必须是一个可以返回TestCase
或TestSuite
类实例的对象名称,不可以是事先创建好的测试名称, 因为每个测试必须在每次运行是重新创建。
使用窗口测试会因为更新那些窗口而带来额外的时间开销。在我系统上,每一千个测试,它会多花七秒钟。你的消耗可能会不同。
通常当测试运行时,TestRunner
将显示其名称。这个名称是由测试用例类名和所运行的测试方法名组成的。
但是如果你为测试方法提供了doc-string,则当测试运行时,doc-string的第一行将被显示出来。这为编写测试文档提供了一个很便捷的机制:
1 |
class WidgetTestCase(unittest.TestCase): |
2 |
def testDefaultSize( self ): |
3 |
"""Check that widgets are created with correct default size""" |
4 |
assert self .widget.size() = = ( 50 , 50 ), 'incorrect default size' |
我之前建议过应使用Python内建断言机制来检查测试用例中的条件,而不应使用自己编写的替代品,因为assert
更简单,简明且为大家所熟悉。
但是值得注意的是,如果在运行测试的同时Python优化选项被打开(生成“.pyo"字节码文件),那么assert
语句将会被跳过,使得测试用例变得无用。
我为那些需要使用Python优化选项的用户编写了一个assert_
方法并添加进TestCase
类内。它的功能和内建的assert
相同且 不会被优化删除,但是使用较麻烦且所输出错误信息帮助较小:
2 |
self .assert_( self .widget.size() = = ( 100 , 100 ), "size is wrong" ) |
我还在TestCase
类中提供了failIf
和failUnless
两个方法:
2 |
self .failIf( self .widget.size() <> ( 100 , 100 )) |
测试方法还可以通过调用fail
方法使得测试立即失败:
3 |
if not hasattr (something, "blah" ): |
4 |
self .fail( "blah missing" ) |
5 |
# or just 'self.fail()' |
最常用的断言是测试相等性。如果断言失败,开发者通常希望看到实际错误值。
TestCase
包含一对方法assertEqual
和assertNotEqual
用于此目的(如果你喜欢,你还可以使用别名:failUnlessEqual
和 failIfEqual
):
1 |
def testSomething( self ): |
2 |
self .widget.resize( 100 , 100 ) |
3 |
self .assertEqual( self .widget.size, ( 100 , 100 )) |
测试经常希望检查在某个环境中是否出现异常。如果期待的异常没有抛出,测试将失败。这很容易做到:
3 |
self .widget.resize( - 1 , - 1 ) |
7 |
fail( "expected a ValueError" ) |
通常,预期异常源(译者注:将抛出异常的代码)是一个可调用对象;为此,TestCase
有一个assertRaises
方法。此方法的前两个参数是应该出现在“except”语句中的异常和可调用对象。剩余的参数是应该传递给可调用对象的参数。
2 |
self .assertRaises(ValueError, self .widget.resize, - 1 , - 1 ) |
一些用户希望将已有的测试代码不需转变为TestCase
子类而直接从PyUnit中运行。
为此,PyUnit提供了一个FunctionTestCase
类。这个TestCase
子类可以用来包装已有测试函数。设置和清理函数也可以选择性地被包装。
对于以下测试函数:
2 |
something = makeSomething() |
3 |
assert something.name is not None |
我们可以创建一个等同的测试用例实例:
1 |
testcase = unittest.FunctionTestCase(testSomething) |
如果有附加的设置和清理方法需要由测试用例调用,可以如下操作:
1 |
testcase = unittest.FunctionTestCase(testSomething, |
3 |
tearDown = deleteSomethingDB) |
虽然PyUnit主要是为“C” Python所编写,你仍然可以用Jython编写PyUnit测试,来测试你的Java或Jython软件。这比用Jython编写JUnit测试更可 取。PyUnit也可以正确的与Jython前期版本,Jython 1.0和1.1协同工作。
当然,Java不包含TK GUI接口,所以PyUnit的基于TKinter的GUI是不能在Jython下工作的,但是基于文本的接口是可以正常工作的。
要在Jython中使用PyUnit的文本接口,只需简单的将标准C Python库模块文件‘traceback.py
', 'linecache.py
', 'stat.py
' 和 'getopt.py
'复制到可以被JPython引用到的位置上。你可以在任何C Python发布版中找到这些文件。(这是针对C Python 1.5.x版本的标准库,可能对其它版本Python不适用)
现在你完全可以像在C Python中那样编写你的PyUnit测试了。
参见 "更多关于测试条件" 部分所述注意事项。
当异常在测试套件运行过程中被抛出时,因此产生的追溯(traceback)对象将被保存,以使失败信息可以在测试运行结束后被格式化输出。除了简便性, 这样做的另一个优点就是未来的GUI TestRunner可以在后期查看保存在追溯对象中的本地和全局变量。
一个可能的副作用就是,当运行一个失败频率很高的测试套件时,为保存所有这些追溯对象而需要的内存使用量将成为一个问题。当然,如果很多测试是失败的,内存的消耗也只是你的问题中最微不足道的一个。
- [译]PyUnit—Python单元测试框架(1)
1. 原文及参考资料 原文链接:http://docs.python.org/2/library/unittest.html# 参考文档: http://pyunit.sourceforge.net/ ...
- Python单元测试框架
目录 概况 系统要求 使用PyUnit构建自己的测试 安装 测试用例介绍 创建一个简单测试用例 复用设置代码:创建固件 包含多个测试方法的测试用例类 将测试用例聚合成测试套件 嵌套测试用例 测试代码的 ...
- Python单元测试框架之pytest 4 -- 断言
From: https://www.cnblogs.com/fnng/p/4774676.html Python单元测试框架之pytest -- 断言 2015-08-31 23:57 by 虫师, ...
- Python单元测试框架之pytest 3 -- fixtures
From: https://www.cnblogs.com/fnng/p/4769020.html Python单元测试框架之pytest -- fixtures 2015-08-29 13:05 b ...
- Python单元测试框架之pytest 2 -- 生成测试报告
From: https://www.cnblogs.com/fnng/p/4768239.html Python单元测试框架之pytest -- 生成测试报告 2015-08-29 00:40 by ...
- Python单元测试框架unittest使用方法讲解
这篇文章主要介绍了Python单元测试框架unittest使用方法讲解,本文讲解了unittest概述.命令行接口.测试案例自动搜索.创建测试代码.构建测试套件方法等内容,需要的朋友可以参考下 概 ...
- 【转】nose-parameterized是Python单元测试框架实现参数化的扩展
原文地址: http://www.cnblogs.com/fnng/p/6580636.html 相对而言,Python下面单元测试框架要弱上少,尤其是Python自带的unittest测试框架,不支 ...
- Python单元测试框架:unittest(一)
Python单元测试框架unittest使用方法讲解 主要介绍了Python单元测试框架unittest使用方法讲解,本文讲解了unittest概述.命令行接口.测试案例自动搜索.创建测试代码.构建测 ...
- Python单元测试框架unittest之深入学习
前言 前几篇文章该要地介绍了python单元测试框架unittest的使用,本篇文章系统介绍unittest框架. 一.unittest核心工作原理 unittest中最核心的四个概念是:test c ...
随机推荐
- 委托delegate与Dictionary实现action选择器
大家一定都有这种情况,1.前台页面信息是通过Ajax请求的方法加载的;2.或者是通过请求本页面加载的;3.请求的页面不仅仅是一个Http请求在 这咱情况下我们一般会加一个action的参数,用于区别是 ...
- Android从相册选取视频
1. /** * 从相册中选择视频 */ private void choiceVideo() { Intent i = new Intent(Intent.ACTION_PICK, android. ...
- 替换Ubuntu默认的登录背景
Ubuntu默认的登录背景看起来还是比较高贵的,但是作为一个爱折腾的人,当然要换成自己喜欢的背景图了.一开始跟着百度走,进了不少坑,最后还是自己走出来的.先上一个成品照 百度得到的答案几乎都是安装ub ...
- Walls and Gates -- LeetCode
You are given a m x n 2D grid initialized with these three possible values. -1 - A wall or an obstac ...
- 【bzoj4403】【序列统计】不降转升+组合数添项合并
(上不了p站我要死了,侵权度娘背锅) Description 给定三个正整数N.L和R,统计长度在1到N之间,元素大小都在L到R之间的单调不降序列的数量.输出答案对10^6+3取模的结果. Input ...
- [POI2014]Around the world
题目大意: 一个环上有$n(n\le10^6)$个点,每个点之间的距离为$l_i(l_i\le10^9)$.有$m(m\le100)$架飞机,每架飞机单次最大航行距离为$d_i$.飞机只能在点上起飞. ...
- SQL 连表更新
现在数据如下: 需求如下: 要求 更新 Earnings 表中的Earnings字段 当 Table_2 KPI 大于等于1时 Earinings = KPI* 2000,否则等于 KPI* 1500 ...
- TranslateAnimation详解 Android动画。
TranslateAnimation详解 Android JDK为我们提供了4种动画效果,分别是: AlphaAnimation,RotateAnimation, ScaleAnimation, Tr ...
- C# html的Table导出到Excel中
C#中导出Excel分为两大类.一类是Winform的,一类是Web.今天说的这一种是Web中的一种,把页面上的Table部分导出到Excel中. Table导出Excel,简单点说,分为以下几步: ...
- 国内 docker 仓库镜像对比
http://www.datastart.cn/tech/2016/09/28/docker-mirror.html