pytest是python的第三方测试框架,是基于unittest的扩展框架,比unittest更简洁,更高效。
pytest是可以兼容unittest脚本的,unittest测试用例可以通过pytest框架去运行。
编写规则
- 测试文件以test_开头(以_test结尾也可以)
- 测试类以Test开头,并且不能带有 init 方法
- 测试函数以test_开头
- 断言使用基本的assert即可
一个最简单的案例
编辑下面代码,然后在命令行中执行命令pytest
,会自动在当前目录下搜索符合上面规则的文件名test_demo.py
及函数test_demo
,然后去进行运行测试。
事例代码如下: test_demo.py
def demo(a):
return a + 1
def test_demo():
assert demo(1) == 3
执行后会发现测试的结果是错误,结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kKl6NMaA-1667317222718)(en-resource://database/1934:1)]
测试类案例
会执行该类下所有test_*
的函数。
class TestDemo:
def test_demo1(self):
assert 1 == 2
def test_demo2(self):
assert 1 == 1
def test_demo3(self):
assert 1 == 3
结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N6slqMXm-1667317222724)(en-resource://database/1936:1)]
pytest 插件
使用pytest生成html测试报告
安装
pip install pytest-HTML
运行
pytest -q --html=report_test.html
异常测试
执行test_mytest
函数,判断f()
会抛出SystemExit异常
def f():
raise SystemExit(1)
def test_mytest():
with pytest.raises(SystemExit):
f()
参数化
执行用例规则
- 执行该文件夹所有测试用例.
pytest 文件夹/
- 执行该脚本中所有测试用例
pytest 脚本名称
- -k 按关键字匹配,运行包含该字符串匹配的测试用例
pytest -k "class or filename or function "
- -x 遇到错误停止运行
pytest -x
- –maxfail=num, 遇到给定次数的错误时,停止测试
pytest --maxfail=3
Fixtures
依赖注入
Fixtures作为函数参数
测试函数可以接收fixture对象作为输入参数,使用@pytest.fixture
test_demo.py
SMTP
实例是由smtp_connection
创建,test_echlo
将smtp_connection
作为参数。
import pytest
@pytest.fixture
def smtp_connection():
import smtplib
return smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert 0
使用装饰器的方式使用fixture
import pytest
@pytest.fixture()
def init_data():
print("1111")
yield
print("222")
@pytest.mark.usefixtures('init_data')
def test_demo():
print("333")
assert 0
print("444")
共享fixture函数: conftest.py
在多个测试文件需要用到同一个fixture函数,可以将该函数放到
conftest.py
中,pytest会自动的将该文件中获取。
测试数据
想要获取可用的测试数据时,可以使用
fuxture
加载到测试中,pytest有自动缓存机制。
另外有一些插件使用:
pytest-datadir和pytest-datafiles
scope
在指定范围内共享future实例
scope | 简介 |
---|---|
session | 在一次Run或Debug中执行的所有case共享一个session,第一个case开始执行的时候session开始,最后一个case执行结束的时候session结束,这些case可能分布在不同的class或module中。 |
module | 一个.py文件可以看作一个module,其表示的范围指该文件中第一个case到最后一个case之间的范围 |
class | 表示的范围即class的范围 |
function | 表示的范围即function的范围 |
scope案例
是一个module的案例。
conftest.py
@pytest.fixture(scope="module")
def smtp():
return smtplib.SMTP("smtp.qq.com", 587, timeout=5)
test_demo.py
这两个使用的是同一个
smpt
,都在module的范围内
def test_ehlo(smtp):
response, msg = smtp.ehlo()
assert response == 250
assert b"smtp.qq.com" in msg
assert 0
def test_noop(smtp):
response, msg = smtp.noop()
assert response == 250
assert 0
test_demo2.py
这个得到的
smpt
和test_demo.py
的两个方法中的smpt
不一样,因为两个文件是两个module范围。
def test_demo(smtp):
response, msg = smtp.ehlo()
assert response == 250
assert b"smtp.qq.com" in msg
assert 0
yield 替代 return
所有yield后的代码可以用来释放资源
conftest.py
在module范围内最后一个test执行结束后,才会调用yield之后的代码
print('teardown smtp')
和smtp_connection.close()
。
import pytest
import smtplib
@pytest.fixture(scope='module')
def smtp_connection():
smtp_connection = smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)
yield smtp_connection
print('teardown smtp')
smtp_connection.close()
注意: 在test中出错后,抛出异常,不会执行yield
之后的代码,所以这种方法还不是很完美
使用 with 替代 yield
conftest.py
@pytest.fixture(scope='module')def smtp_connection():
with smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5) as smtp_connection:
yield smtp_connection
request
fixture
可以传入一个request
参数,该参数中包含fixture
的一些函数信息
request.addfinalizer()
方法和yield
类似,但是,就算test抛出异常,也会关闭所有资源。
request.param
参数化, 每个参数都会执行一次测试
conftest.py
@pytest.fixture(scope="module",params=["smtp.qq.com", "mail.python.org"])
def smtp(request):
smtp = smtplib.SMTP(request.param, 587, timeout=5)
def fin():
print("执行结束!")
smtp.close()
def fin1():
print('this ok!')
request.addfinalizer(fin)
request.addfinalizer(fin1) #可以注册多个 功能比 yield 强大!
return smtp
test_demo.py
def test_demo(smpt):
assert 0
setup/teardown
module级别
如果有多个test函数或test类在一个模块内,可以选择的实现setup_module和teardown_module,一般模块内的所有函数都会调用一次
def setup_module(module):
pass
def teardown_module(module):
pass
class 级别
@classmethod
def setup_class(cls):
pass
@classmethod
def teardown_class(cls):
pass
method 级别
def setup_method(self, method):
pass
def teardown_method(self, method):
pass
function 级别
def setup_function(function):
pass
def teardown_function(function):
pass
参数化
使用pytest.mark.parametrize进行参数化配置
可以对test传入多个条件和期待的结果进行测试。
from datetime import datetime, timedelta
import pytest
testdata = [
(datetime(2001, 12, 12), datetime(2001, 12, 11), timedelta(1)),
(datetime(2001, 12, 11), datetime(2001, 12, 12), timedelta(-1)),
]
@pytest.mark.parametrize("a,b,expected", testdata, ids=["forward", "backward"])
def test_timedistance_v0(a, b, expected):
diff = a - b
assert diff == expected
assert 0
mock
该库可以将测试所以来的对象替换成虚拟对象的库,并且虚拟对象可以在事后进行查看。
patch
通过patch模拟
os.remove
方法,然后调用,断言该方法被调用过一次,并且该方法并不会真正的执行。
import pytest
import os
class UnixFS:
@staticmethod
def rm(filename):
os.remove(filename)
def test_unix_fs(mocker):
mocker.patch('os.remove')
UnixFS.rm("test.txt")
os.remove.assert_called_once_with('test.txt')
可以使用装饰器的方式使用patch
import mock
class UnixFS:
@staticmethod
def rm(filename):
os.remove(filename)
@mock.patch('os.remove')
def test_unix_fs(rm):
UnixFS.rm("test.txt")
os.remove.assert_called_once_with('test.txt')
spy
spy
的bar
方法和Foo中的bar
方法功能执行一直,并且可以使用模拟功能,如计算被调用次数。
def test_spy(mocker):
class Foo(object):
def bar(self):
return 42
foo = Foo()
mocker.spy(foo, 'bar')
assert foo.bar() == 42
assert foo.bar.call_count == 1
自定义标记mark
可以对指定的test进行标记,规定哪些test跑,哪些test不跑
import pytest
@pytest.mark.runtest
def test_run():
print("run")
def test_not_run():
print("not run")
只有标记为runtest才跑
pytest -m=runtest
欢迎关注,互相学习,共同进步~
我的个人博客
我的微信公众号:编程黑洞