Python 各种测试框架简介
转载:https://blog.csdn.net/yockie/article/details/47415265
一、doctest
doctest 是一个 Python 发行版自带的标准模块。本篇将分别对使用 doctest 的两种方式——嵌入到源代码中和做成独立文件做基本介绍。
1.doctest的概念模型
在 Python 的官方文档中,对 doctest 的介绍是这样的:
doctest 模块会搜索那些看起来像交互式会话的 Python 代码片段,然后尝试执行并验证结果
即使从没接触过 doctest,我们也可以从这个名字中窥到一丝端倪。“它看起来就像代码里的文档字符串(docstring)一样” 如果你这么想的话,就已经对了一半了。
doctest 的编写过程就仿佛你真的在一个交互式 shell(比如 idle)中导入了要测试的模块,然后开始一条条地测试模块里的函数一样。实际上有很多人也是这么做的,他们写好一个模块后,就在 shell 里挨个测试函数,最后把 shell 会话复制粘贴成 doctest 用例。
2.嵌入源代码模式
下面使用的例子是一个只有一个函数的模块,其中签入了两个 doctest 的测试用例。
unnecessary_math.py:
"""
这里也可以写
"""
def multiply(a,b):
"""
>>> multiply(2,3)
6
>>> multiply('baka~',3)
'baka~baka~baka~'
"""
return a*b
if __name__ == '__main__':
import doctest
doctest.testmod(verbose=True)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
注意测试代码的位置,前面说过 doctest 的测试用例就像文档字符串一样,这句话的内涵在于:测试用例的位置必须放在整个模块文件的开头,或者紧接着对象声明语句的下一行。也就是可以被 _ doc _ 这个属性引用到的地方。并非像普通注释一样写在哪里都可以。另:verbose 参数用于控制是否输出详细信息,默认为 False,如果不写,那么运行时不会输出任何东西,除非测试 fail。
示例的运行输出为:
Trying:
multiply(2,3)
Expecting:
6
ok
Trying:
multiply(‘baka~’,3)
Expecting:
‘baka~baka~baka~’
ok
1 items had no tests:
main
1 items passed all tests:
2 tests in main.multiply
2 tests in 2 items.
2 passed and 0 failed.
Test passed.
值得一提的是,如果将这个脚本保存为doctest.py,并且运行,你会得到以下结果:
Traceback (most recent call last):
File “doctest.py”, line 62, in
doctest.testmod()
AttributeError: ‘module’ object has no attribute ‘testmod’
原因是被重写了,把文件名改成doctest1.py(或其他名字)之后,需要删除之前的pyc文件。再运行即可。
上例中启动测试的方式是在 _ main _ 函数里调用了 doctest.testmod() 函数。这对于纯容器型模块文件来说是一个好办法——正常使用时只做导入用,直接运行文件则进行测试。而对于 _ main _ 函数另有他用的情况,则还可以通过命令行来启动测试:
$ python -m doctest unnecessary_math.py
$ python -m doctest -v unnecessary_math.py
- 1
- 2
这里-m 表示引用一个模块,-v 等价于 verbose=True。运行输出与上面基本一样。
3.独立文件模式
如果不想(或不能)把测试用例写进源代码里,则还可以使用一个独立的文本文件来保存测试用例。
可选的一些解释性内容...
>>> from test import multiply
>>> multiply(2,3)
6
>>> multiply('baka~',3)
'baka~baka~baka~'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
几乎同样的格式。运行方法可以分为在 Python shell 里运行或者在系统 shell 里运行:
>>> import doctest
>>> doctest.testfile('example.txt')
- 1
- 2
bash/cmd.exe:
$ python -m doctest -v example.txt
- 1
【摘自:链接1】
二、unittest
unittest 与 doctest 一样也是 Python 发行版自带的包。如果你听说过 PyUnit(OSC 开源项目页面中就有 PyUnit 的页面),那么这俩其实是同一个东西——PyUnit 是 unittest 的曾用名,因为 PyUnit 最早也是来源于 Kent 和 Erich 的 JUnit(xUnit 测试框架系列的 Java 版本)
1.unittest 概览
上一篇介绍的 doctest 不管是看起来还是用起来都显得十分简单,可以与源码写在一起,比较适合用作验证性的功能测试。而本篇的 unittest 从名字上看,它是一个单元测试框架;从官方文档的字数上看,它的能力应该比 doctest 强一些。
使用 unittest 的标准流程为:
1. 从 unittest.TestCase 派生一个子类
2. 在类中定义各种以 “test_” 打头的方法
3. 通过 unittest.main() 函数来启动测试
unittest 的一个很有用的特性是 TestCase 的 setUp() 和 tearDown() 方法,它们提供了为测试进行准备和扫尾工作的功能,听起来就像上下文管理器一样。这种功能很适合用在测试对象需要复杂执行环境的情况下。
2.举个例子
这里依旧使用上篇中那个极简的例子:unnecessary_math.py 文件中有一个 multiply() 函数,功能与 * 操作符完全一样。
test_um_test.py:
import unittest
from unnecessary_math import multiply
class TestUM(unittest.TestCase):
def setUp(self):
pass
def test_number_3_4(self):
self.assertEqual(multiply(3,4),12)
def test_string_a_3(self):
self.assertEqual(multiply('a',3),'aaa')
if __name__ == '__main__':
unittest.main()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
这个例子里,我们使用了 assertEqual() 方法。unittest 中还有很多类似的 assert 方法,比如 NotEqual、Is(Not)None、True(False)、Is(Not)Instance 等针对变量值的校验方法;另外还有一些如 assertRaises()、assertRaisesRegex() 等针对异常、警告和 log 的检查方法;以及如 assertAlmostEqual() 等一些奇怪的方法。
较详细的 assert 方法可以参考 unittest 的文档页面。
3.启动测试
上例中的结尾处,我们定义了一个对 unittest.main() 的调用,因此这个脚本是可以直接运行的:
$ python test_um_test.py
..
--------------------------------------
Ran 2 tests in 0.01s
OK
- 1
- 2
- 3
- 4
- 5
- 6
同样 -v 参数是可选的,也可以在 unittest.main() 函数里直接指定:verbosity=1。
4.Test Discovery
这个分段标题我暂时没想到好的翻译方法,就先不翻了。
Test Discovery 的作用是:假设你的项目文件夹里面四散分布着很多个测试文件。当你做回归测试的时候,一个一个地执行这些测试文件就太麻烦了。TestLoader.discover() 提供了一个可以在项目目录下自动搜索并运行测试文件的功能,并可以直接从命令行调用:
$ cd project_directory
$ python -m unittest discover
discover 可用的参数有 4 个(-v -s -p -t),其中 -s 和 -t 都与路径有关,如上例中提前 cd 到项目路径的话这俩参数都可以无视;-v 喜闻乐见;-p 是 –pattern 的缩写,可用于匹配某一类文件名。
5.测试环境
当类里面定义了 setUp() 方法的时候,测试程序会在执行每条测试项前先调用此方法;同样地,在全部测试项执行完毕后,tearDown() 方法也会被调用。验证如下:
import unittest
class simple_test(unittest.TestCase):
def setUp(self):
self.foo = list(range(10))
def test_1st(self):
self.assertEqual(self.foo.pop(),9)
def test_2nd(self):
self.assertEqual(self.foo.pop(),9)
if __name__ == '__main__':
unittest.main()
注意这里两次测试均对同一个实例属性 self.foo 进行了 pop() 调用,但测试结果均为 pass,即说明,test_1st 和 test_2nd 在调用前都分别调用了一次 setUp()。
那如果我们想全程只调用一次 setUp/tearDown 该怎么办呢?就是用 setUpClass() 和 tearDownClass() 类方法啦。注意使用这两个方法的时候一定要用 @classmethod 装饰器装饰起来:
import unittest
class simple_test(unittest.TestCase):
@classmethod
def setUpClass(self):
self.foo = list(range(10))
def test_1st(self):
self.assertEqual(self.foo.pop(),9)
def test_2nd(self):
self.assertEqual(self.foo.pop(),8)
if __name__ == '__main__':
unittest.main()
这个例子里我们使用了一个类级别的 setUpClass() 类方法,并修改了第二次 pop() 调用的预期返回值。运行结果显示依然是全部通过,即说明这次在全部测试项被调用前只调用了一次 setUpClass()。
再往上一级,我们希望在整个文件级别上只调用一次 setUp/tearDown,这时候就要用 setUpModule() 和 tearDownModule() 这两个函数了,注意是函数,与 TestCase 类同级:
import unittest
def setUpModule():
pass
class simple_test(inittest.TestCase):
...
一般 assert*() 方法如果抛出了未被捕获的异常,那么这条测试用例会被记为 fail,测试继续进行。但如果异常发生在 setUp() 里,就会认为测试程序自身存在错误,后面的测试用例和 tearDown() 都不会再执行。即,tearDown() 仅在 setUp() 成功执行的情况下才会执行,并一定会被执行。
最后,这两个方法的默认实现都是什么都不做(只有一句 pass),所以覆盖的时候直接写新内容就可以了,不必再调用父类的此方法。
三、nose
本篇将介绍的 nose 不再是 Python 官方发行版的标准包,但它与 unittest 有着千丝万缕的联系。比如 nose 的口号就是:
扩展 unittest,nose 让测试更简单。
1.简单在哪
自古(1970)以来,任何标榜“更简单”的工具所使用的手段基本都是隐藏细节,nose 也不例外。nose 不使用特定的格式、不需要一个类容器,甚至不需要 import nose ~(这也就意味着它在写测试用例时不需要使用额外的 api)
前两篇中一直使用的 unnecessary_math.py 的 nose 版测试用例是这样子的:
from unnecessary_math import multiply
def test_numbers():
assert multiply(3,4)==12
def test_strings():
assert multiply('a',3)=='aaa'
看上去完全就是一个普通的模块文件嘛,甚至连 main 函数都不用。这里唯一需要一点“讲究”的语法在于:测试用例的命名仍需以 test_ 开头。
2.运行 nose
nose 在安装的时候也向你 Python 根目录下的 Scripts 文件夹内添加了一个名为 nosetests 的可执行文件,这个可执行文件就是用来执行测试的命令;当然你也仍可以使用 -m 参数来调用 nose 模块:
$ nosetests test.py
$ python -m nose test.py
··
------------------------------------------------
Ran 2 tests in 0.001s
OK
另外非常棒的一点是,nosetests 兼容对 doctest 和 unittest 测试脚本的解析运行。如果你认为 nose 比那两个都好用的话,完全可以放弃 doctest 和 unittest 的使用。
3.测试环境
由于扩展自 unittest,nose 也支持类似于 setUp() setUpClass() setUpModule() 的测试环境创建方式,只不过函数命名规则最好改一改,我们可以使用更符合 Python 规范的命名规则。另外因为 nose 支持上例中所展示的函数式测试用例,所以还有一种为单个函数创建运行环境的装饰器可用。下面我们将使用一个例子来展示这四种功能的用法。
test.py:
from nose import with_setup
from unnecessary_math import multiply
def setup_module(module):
print('setup_module 函数执行于一切开始之前')
def setup_deco():
print('setup_deco 将用于 with_setup')
def teardown_deco():
print('teardown_deco 也将用于 with_setup')
@with_setup(setup_deco,teardown_deco)
def test_2b_decorated():
assert multiply(3,4)==12
class TestUM():
def setup(self):
print('setup 方法执行于本类中每条用例之前')
@classmethod
def setup_class(cls):
print('setup_class 类方法执行于本类中任何用例开始之前,且仅执行一次')
def test_strings(self):
assert multiply('a',3)=='aaa'
运行 $ nosetests -v test.py 结果如下:
test.TestUM.test_strings … ok
test.test_2b_decorated … ok
Ran 2 tests in 0.002s
OK
我们的 print() 函数一点东西都没打出来,如果你想看的话,给 nosetests 添加一个 -s 参数就可以了。
4.Test Discovery
nose 的 discovery 规则为:
1.长得像测试用例,那就是测试用例。路径、模块(文件)、类、函数的名字如果能和 testMatch 正则表达式匹配上,那就会被认为是一个用例。另外所有 unittest.TestCase 的子类也都会被当做测试用例。(这里的 testMatch 可能是个环境变量之类的东西,我没有去查,因为反正你只要以 test_ 开头的格式来命名就可以保证能被发现)
2.如果一个文件夹既长得不像测试用例,又不是一个包(路径下没有 init.py)的话,那么 nose 就会略过对这个路径的检查。
3.但只要一个文件夹是一个包,那么 nose 就一定会去检查这个路径。
4.显式避免某个对象被当做测试用例的方法为:给其或其容器添加一个 _ test _ 属性,并且运算结果不为 True。并不需要直接指定为 False,只要 bool( _ test _ ) == False 即可。另外,这个属性的添加方式比较特别,确认自己已经掌握使用方法前最好都试试。例如在类里面需要添加为类属性而非实例属性(即不能写在 _ init _(self) 里),否则不起作用。这里因为只是简介,就不挨个试了。(官方文档里就没解释清楚…)
调用 discovery 的语法为,cd 到目录后直接调用 $ nosetests,后面不跟具体的文件名。另外这种方法其实对 unittest 也适用。
四、pytest
pytest 有时也被称为 py.test,是因为它使用的执行命令是 $ py.test。本文中我们使用 pytest 指代这个测试框架,py.test特指运行命令。
1.较于 nose
这里没有使用像前三篇一样(简介-举例-discovery-环境)式的分段展开,是因为 pytest 与 nose 的基本用法极其相似。因此只做一个比较就好了。他俩的区别仅在于
1.调用测试的命令不同,pytest 用的是 $ py.test
2.创建测试环境(setup/teardown)的 api 不同
下面使用一个例子说明 pytest 的 setup/teardown 使用方式。
some_test.py:
import pytest
@pytest.fixture(scope='function')
def setup_function(request):
def teardown_function():
print("teardown_function called.")
request.addfinalizer(teardown_function)
print('setup_function called.')
@pytest.fixture(scope='module')
def setup_module(request):
def teardown_module():
print("teardown_module called.")
request.addfinalizer(teardown_module)
print('setup_module called.')
def test_1(setup_function):
print('Test_1 called.')
def test_2(setup_module):
print('Test_2 called.')
def test_3(setup_module):
print('Test_3 called.')
pytest 创建测试环境(fixture)的方式如上例所示,通过显式指定 scope=” 参数来选择需要使用的 pytest.fixture 装饰器。即一个 fixture 函数的类型从你定义它的时候就确定了,这与使用 @nose.with_setup() 十分不同。对于 scope=’function’ 的 fixture 函数,它就是会在测试用例的前后分别调用 setup/teardown。测试用例的参数如 def test_1(setup_function) 只负责引用具体的对象,它并不关心对方的作用域是函数级的还是模块级的。
有效的 scope 参数限于:’function’,’module’,’class’,’session’,默认为 function。
运行上例:$ py.test some_test.py -s。 -s 用于显示 print() 函数
============================= test session starts =============================
platform win32 -- Python 3.3.2 -- py-1.4.20 -- pytest-2.5.2
collected 3 items
test.py setup_function called.
Test_1 called.
.teardown_function called.
setup_module called.
Test_2 called.
.Test_3 called.
.teardown_module called.
========================== 3 passed in 0.02 seconds ==========================
这里需要注意的地方是:setup_module 被调用的位置。
2.pytest 与 nose 二选一
首先,单是从不需要使用特定类模板的角度上,nose 和 pytest 就较于 unittest 好出太多了。doctest 比较奇葩我们在这里不比。因此对于 “选一个自己喜欢的测试框架来用” 的问题,就变成了 nose 和 pytest 二选一的问题。
pythontesting.net 的作者非常喜欢 pytest,并表示
“如果你挑不出 pytest 的毛病,就用这个吧”。
于是下面我们就来挑挑 pytest 的毛病:
它的 setup/teardown 语法与 unittest 的兼容性不如 nose 高,实现方式也不如 nose 直观
第一条足矣
毕竟 unittest 还是 Python 自带的单元测试框架,肯定有很多怕麻烦的人在用,所以与其语法保持一定兼容性能避免很多麻烦。即使 pytest 在命令行中有彩色输出让我很喜欢,但这还是不如第一条重要。
实际上,PyPI 中 nose 的下载量也是 pytest 的 8 倍多。
所以假如再继续写某一个框架的详解的话,大概我会选 nose 吧。
Python 各种测试框架简介的更多相关文章
- Python 各种测试框架简介(三):nose
转载:https://blog.csdn.net/qq_15013233/article/details/52527260 摘要 这里将从(pythontesting.net)陆续编译四篇 Pytho ...
- pytest测试框架 -- 简介
一.pytest测试框架简介: (1)pytest是python的第三方测试框架,是基于unittest的扩展框架,比unittest更简洁,更高效. (2)pytest框架可以兼容unittest用 ...
- python nose测试框架全面介绍十---用例的跳过
又来写nose了,这次主要介绍nose中的用例跳过应用,之前也有介绍,见python nose测试框架全面介绍四,但介绍的不详细.下面详细解析下 nose自带的SkipTest 先看看nose自带的S ...
- python nose测试框架全面介绍七--日志相关
引: 之前使用nose框架时,一直使用--logging-config的log文件来生成日志,具体的log配置可见之前python nose测试框架全面介绍四. 但使用一段时间后,发出一个问题,生成的 ...
- python nose测试框架全面介绍六--框架函数别名
之前python nose测试框架全面介绍二中介绍了nose框架的基本构成,但在实际应该中我们也会到setup_function等一系列的名字,查看管网后,我们罗列下nose框架中函数的别名 1.pa ...
- python主流测试框架的简介
1.python自动化的一些测试框架 1).unitest(也称为PyUnit) 地址:https://docs.python.org/2/library/unittest.html 2).Nose ...
- python nose测试框架全面介绍一
一.简介 nose 是python自带框架unttest的扩展,使测试更简单高效:nose是一个开源的项目,可以在官网上下载源码 1.快速安装 有以下几中安装方式: easy_install ...
- python pytest测试框架介绍二
在介绍一中简单介绍了pytest的安装和简单使用,接下来我们就要实际了解pytest了 一.pytest的用例发现规则 pytest可以在不同的函数.包中发现用例,发现的规则如下 文件名以test_开 ...
- Assignment3:白盒测试以及测试框架简介
一. 白盒测试简介 白盒测试又称结构测试.透明盒测试.逻辑驱动测试或基于代码的测试.白盒测试是一种测试用例设计方法,盒子指的是被测试的软件,白盒指的是盒子是可视的,你清楚盒子内部的东西以及 ...
随机推荐
- ChromeDriver启动Chrome浏览器后,地址栏只显示data;——chromeDriver版本不对
ChromeDriver启动Chrome浏览器后,地址栏只显示data; 错误原因: chromeDriver版本不对,不同版本的chromeDriver对应不同版本的chrome浏览器 chrome ...
- python服务端内存泄露的处理过程
http://xiaorui.cc http://xiaorui.cc/2017/08/20/python服务端内存泄露的处理过程/
- php开发之命名规则
类命名 1.使用大写的字母作为词的切割,其它字母均使用小写字母 2.名字的首字母使用大写字母 3.不要使用下划线"_" 类属性的命名 1.属性的命名应该以'm'为前缀 2.前缀'm ...
- [Winform]通过钩子监控键盘操作和鼠标移动
摘要 有这样一个需求,在程序隐藏之后,需要监控当前电脑是否有操作,如果1分钟内,无操作,则弹出视频,循环播放. 解决办法 从网上找的一个解决办法,可以通过钩子的方式实现,这里记录一下. /// < ...
- Visual Studio 2015 update 2 setup fails with "missing or damaged package kb3022398"
Question Hi, I wanted to install Visual Studio Professional 2015 Update 2 from my MSDN abo (web ...
- Chrome 如何知道网站启用了SPDY 协议?
地址栏输入chrome://net-internals/#spdy 在host后查看协议,google和dropbox用https协议的开启了 3. 也可以通过安装插件来查看(SPDY Indicat ...
- iOS: 计算 UIWebView 的内容高度
- (void)webViewDidFinishLoad:(UIWebView *)wb { //方法1 CGFloat documentWidth = [[wb stringByEvaluating ...
- 直接将DataTable存入oracle数据库中(转)
注意 1:传入的DataTable的列必须和数据库中表列必须一致,否则数据会默认往前几列存 2:sql语句只要是对要插入的表的一个查询,目的是为了确定表名 3:取得连接字符串的方法为GetOracle ...
- 【spring boot】spring boot 前台GET请求,传递时间类型的字符串,后台无法解析,报错:Failed to convert from type [java.lang.String] to type [java.util.Date]
spring boot 前台GET请求,传递时间类型的字符串,后台无法解析,报错:Failed to convert from type [java.lang.String] to type [jav ...
- idea jni
javah -jni -classpath (搜寻类目录) -d (输出目录) (类名) nm -D **.so idea setting $JDKPath$/bin/javah -jni -cla ...