调用
在模块中运行测试
pytest test_mod.py
在目录中运行测试
pytest testing/
通过关键字表达式运行测试
pytest -k "MyClass and not method"
按节点 ID 运行测试
每个收集到的测试都被分配了一个唯一的nodeid ,它由模块文件名后跟类名、函数名和参数化参数等说明:: 符组成,用字符分隔。
要在模块中运行特定测试:
pytest test_mod.py::test_func
pytest test_mod.py::TestClass::test_method
通过标记表达式运行测试
pytest -m slow
将运行所有用@pytest.mark.slow 装饰器装饰的测试。
从 Python 代码调用 pytest
pytest.main()
pytest 使用 . 标识测试成功(PASSED )
Pytest 查找测试策略
默认情况下,pytest 会递归查找当前目录下所有以 test 开始或结尾的 Python 脚本,并执行文件内的所有以 test 开始或结束的函数和方法。
第一种:指定函数名
pytest /test_no_mark.py::test_func1
第二种:模糊匹配
pytest -k raise test_sample.py
第三种,使用 pytest.mark 在函数上进行标记。
@pytest.mark.finished
def test_func1():
assert 1 == 1
@pytest.mark.unfinished
def test_func2():
assert 1 != 1
pytest -m finished test_sample.py
跳过测试
@pytest.mark.skip()
def test_connect():
pass
pytest 使用 s 表示测试被跳过(SKIPPED)。
Pytest 使用 pytest.mark.xfail 实现预见错误功能
@pytest.mark.xfail(gen.__version__ < '0.2.0',
reason='not supported until v0.2.0')
def test_api():
id_1 = gen.unique_id()
id_2 = gen.unique_id()
assert id_1 != id_2
pytest 使用 x 表示预见的失败(XFAIL )。
创建第一个测试用例
def func(x):
return x + 1
def test_answer():
assert func(3) == 5
命令行输入pytest运行,[100%] 指运行所有测试案例的全面进步。完成后, pytest 会显示失败报告,因为func(3) 不返回5 .
Python 测试发现的约定
pytestrootdir 为每个测试运行确定一个,这取决于命令行参数(指定的测试文件、路径)和配置文件的存在
--rootdir=path 命令行选项可以用来强制一个特定的目录。请注意,与其他命令行选项相反,--rootdir 不能与addopts inside一起使用, pytest.ini 因为rootdir 已经用于查找 pytest.ini 。
断言某个异常被引发
import pytest
def f():
raise SyntaxError(1)
def test_raise():
with pytest.raises(SyntaxError):
f()
def test_raises():
with pytest.raises(TypeError) as e:
connect('localhost', '6379')
exec_msg = e.value.args[0]
assert exec_msg == 'port type must be int'
以“安静”报告模式执行测试功能 pytest -q test_sample.py
可以使用 -v 选项,显示测试的详细信息。
使用 pytest -h 查看 pytest 的所有选项。
pytest 使用 F 标识测试失败(FAILED )。
在类中分组多个测试
class TestClass(object):
def test_one(self):
x = "this"
assert 'h' in x
def test_two(self):
x = "hello"
assert hasattr(x, 'check')
pytest 发现遵循其Python 测试发现约定的所有测试,因此它会找到两个带test_ 前缀的函数。不需要子类化任何东西,但请确保在您的类前加上前缀,Test 否则该类将被跳过。
在类内对测试进行分组时需要注意的是,每个测试都有一个唯一的类实例。
参数测试化
在 pytest 中,我们有更好的解决方法,就是参数化测试,即每组参数都独立执行一次测试。使用的工具就是 pytest.mark.parametrize(argnames, argvalues) 。
@pytest.mark.parametrize('passwd',
['123456',
'abcdefdfs',
'as52345fasdf4'])
def test_passwd_length(passwd):
assert len(passwd) >= 8
Fixture
Fixture是一些函数,pytest 会在执行测试函数之前(或之后)加载运行它们,Pytest 使用 pytest.fixture() 定义fixture
Pytest 使用文件 conftest.py 集中管理固件。
不要自己显式调用 conftest.py ,pytest 会自动调用,可以把 conftest 当做插件来理解。
预处理和后处理
Pytest 使用 yield 关键词将固件分为两部分,yield 之前的代码属于预处理,会在测试前执行;yield 之后的代码属于后处理,将在测试完成后执行。
例如在执行测试前后连接关闭数据库
@pytest.fixture()
def db():
print('Connection successful')
yield
print('Connection closed')
def search_user(user_id):
d = {
'001': 'xiaoming'
}
print("stop...")
return d[user_id]
def test_search(db):
assert search_user('001') == 'xiaoming'
如果想更细的跟踪固件执行,可以使用 --setup-show 选项
fixture作用域
分为函数级 类级 模块级 会话级
@pytest.fixture(scope='function')
def func_scope():
pass
@pytest.fixture(scope='module')
def mod_scope():
pass
@pytest.fixture(scope='session')
def sess_scope():
pass
执行顺序 为会话级 模块级 函数级
对于类使用作用域要使用pytest.mark.usefixtures
@pytest.mark.usefixtures('class_scope')
class TestClassScope:
def test_1(self):
pass
def test_2(self):
pass
fixture自动执行
加参数autouse
import pytest
import time
DATE_FORMAT = '%Y-%m-%d %H:%M:%S'
@pytest.fixture(scope='session', autouse=True)
def timer_session_scope():
start = time.time()
print('\nstart: {}'.format(time.strftime(DATE_FORMAT, time.localtime(start))))
yield
finished = time.time()
print('finished: {}'.format(time.strftime(DATE_FORMAT, time.localtime(finished))))
print('Total time cost: {:.3f}s'.format(finished - start))
@pytest.fixture(autouse=True)
def timer_function_scope():
start = time.time()
yield
print(' Time cost: {:.3f}s'.format(time.time() - start))
def test_1():
print("test_1")
time.sleep(1)
def test_2():
print("test_2")
time.sleep(2)
fixture重命名
@pytest.fixture(name="age")
def age():
return 1
def test_age(age):
assert age == 2
fixtrue参数化
需借助request
@pytest.fixture(params=[('redis', '6379'), ('elasticsearch', '9200')])
def param(request):
return request.param
@pytest.fixture(autouse=True)
def db(param):
print('\nSucceed to connect %s:%s' % param)
yield
print('\nSucceed to close %s:%s' % param)
def test_api():
assert 1 == 1
与函数参数化使用 @pytest.mark.parametrize 不同,fixtrue在定义时使用 params 参数进行参数化。
固件参数化依赖于内置固件 request 及其属性 param 。
内置固件
tmpdir & tmpdir_factory
用于临时文件和目录管理,默认会在测试结束时删除。
tmpdir 只有 function 作用域,只能在函数内使用。
使用 tmpdir.mkdir() 创建目临时录,tmpdir.join() 创建临时文件(或者使用创建的目录)。
def test_tmpdir(tmpdir):
a_dir = tmpdir.mkdir('mytmpdir')
a_file = a_dir.join('tmpfile.txt')
a_file.write('hello, pytest!')
assert a_file.read() == 'hello, pytest!'
tmpdir_factory 可以在所有作用域使用,包括 function, class, module, session 。
@pytest.fixture(scope='module')
def my_tmpdir_factory(tmpdir_factory):
a_dir = tmpdir_factory.mktemp('mytmpdir')
a_file = a_dir.join('tmpfile.txt')
a_file.write('hello, pytest!')
return a_file
pytestconfig
使用 pytestconfig ,可以很方便的读取命令行参数和配置文件。
def pytest_addoption(parser):
parser.addoption('--host', action='store', default="127.0.0.1",
help='host of db')
parser.addoption('--port', action='store', default='8888',
help='port of db')
然后就可以在测试函数中通过 pytestconfig 获取命令行参数:
def test_option1(pytestconfig):
print('host: %s' % pytestconfig.getoption('host'))
print('port: %s' % pytestconfig.getoption('port'))
pytestconfig 其实是 request.config 的快捷方式,所以也可以自定义固件实现命令行参数读取。
@pytest.fixture
def config(request):
return request.config
def test_option2(config):
print('host: %s' % config.getoption('host'))
print('port: %s' % config.getoption('port'))
capsys
capsys 用于捕获 stdout 和 stderr 的内容,并临时关闭系统输出。
def ping(output):
print('Pong...', file=output)
def test_stdout(capsys):
ping(sys.stdout)
out, err = capsys.readouterr()
assert out == 'Pong...\n'
assert err == ''
def test_stderr(capsys):
ping(sys.stderr)
out, err = capsys.readouterr()
assert out == ''
assert err == 'Pong...\n'
monkeypatch
monkeypath 用于运行时动态修改类或模块。
monkey patch这个术语仅指在运行时对类或模块进行的动态修改,其动机是为现有的第三方代码打补丁
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
Pytest 内置 monkeypatch 提供的函数有:
setattr(target, name, value, raising=True) ,设置属性;delattr(target, name, raising=True) ,删除属性;setitem(dic, name, value) ,字典添加元素;delitem(dic, name, raising=True) ,字典删除元素;setenv(name, value, prepend=None) ,设置环境变量;delenv(name, raising=True) ,删除环境变量;syspath_prepend(path) ,添加系统路径;chdir(path) ,切换目录。
下面使用保存配置文件示例说明 monkeypatch 的作用和使用。
假设我们需要切换某个服务到国内科大源以加速,有以下脚本用于修改配置文件 .conf.json :
def dump_config(config):
path = os.path.expanduser('~/.conf.json')
with open(path, 'w', encoding='utf-8') as wr:
json.dump(config, wr, indent=4)
def test_config():
dump_config(config)
path = os.path.expanduser('~/.conf.json')
expected = json.load(open(path, 'r', encoding='utf-8'))
assert expected == config
recwarn
recwarn 用于捕获程序中 warnings 产生的警告。
def warn():
warnings.warn('Deprecated function', DeprecationWarning)
def test_warn(recwarn):
warn()
assert len(recwarn) == 1
w = recwarn.pop()
assert w.category == DeprecationWarning
此外,pytest 可以使用 pytest.warns() 捕获警告:
def test_warn2():
with pytest.warns(None) as warnings:
warn()
assert len(warnings) == 1
w = warnings.pop()
assert w.category == DeprecationWarning
|