在pytest中的fixture是在测试函数运行前后,由pytest执行的外壳函数,fixture中的代码可以定制,满足多变的测试需求:包括定义传入测试中的数据集、配置测试前系统的初始化状态、为批量测试提供数据源。
import pytest @pytest.fixture()
def return_data():
return 1000 def test_someting(return_data):
assert return_data == 1000
执行结果如下:
(venv) E:\Programs\Python\Python_Pytest\TestScripts>pytest -v test_fixture.py
============= test session starts ====================================
platform win32 -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 -- c:\python37\python.exe
cachedir: .pytest_cache
rootdir: E:\Programs\Python\Python_Pytest\TestScripts
plugins: xdist-1.29.0, timeout-1.3.3, repeat-0.8.0, instafail-0.4.1, forked-1.0.2, emoji-0.2.0, allure-pytest-2.6.3
collected 1 item test_fixture.py::test_someting PASSED [100%]
===================== 1 passed in 0.10 seconds ======================
通过conftest.py共享fixture
fixture可以放在单独的测试文件里,也可以通过在某个公共目录下新建一个conftest.py文件,将fixture放在其中实现多个测试文件共享fixture。
conftest.py是Python模块,但他不能被测试文件导入,pytest视其为本地插件库。
使用fixture执行配置及销毁逻辑
借助fixture的yield实现setup和teardown
# encoding = utf-8
import pytest
from selenium import webdriver
from time import sleep driver = webdriver.Chrome()
url = "https://www.baidu.com" @pytest.fixture(scope='module')
def automation_testing():
# 执行测试前
driver.get(url)
yield # 测试结束后
driver.quit() def test_fixture(automation_testing):
driver.find_element_by_id("kw").send_keys("davieyang")
driver.find_element_by_id("su").click()
sleep(10)
执行结果如下:
DY@MacBook-Pro TestScripts$pytest -v test_seven.py
================ test session starts ======================
platform darwin -- Python 3.6.5, pytest-5.1.2, py-1.8.0, pluggy-0.13.0 -- /Library/Frameworks/Python.framework/Versions/3.6/bin/python3.6
cachedir: .pytest_cache
rootdir: /Volumes/Extended/PythonPrograms/Pytest/TestScripts
collected 1 item test_seven.py::test_fixture PASSED [100%] =================== 1 passed in 20.45s =============================
使用–setup-show回溯fixture的执行过程
同样的代码,在执行命令中加上参数–setup-show,便可以看到fixture中实际的执行过程,执行结果如下:
DY@MacBook-Pro TestScripts$pytest --setup-show test_seven.py
================= test session starts ==================================
platform darwin -- Python 3.6.5, pytest-5.1.2, py-1.8.0, pluggy-0.13.0
rootdir: /Volumes/Extended/PythonPrograms/Pytest/TestScripts
collected 1 item test_seven.py
SETUP F automation_testing
test_seven.py::test_fixture (fixtures used: automation_testing).
TEARDOWN F automation_testing
================= 1 passed in 14.69s =============================
使用fixture传递测试数据
fixture非常适合存放测试数据,并且它可以返回任何数据
# encoding = utf-8
import pytest @pytest.fixture()
def return_tuple():
return ('davieyang', '', {'mail': '[email protected]'}) def test_return_tuple(return_tuple):
assert return_tuple[2]['mail'] == '[email protected]'
执行结果如下:
DY@MacBook-Pro TestScripts$pytest --setup-show -v test_seven.py
============================================================================ test session starts =============================================================================
platform darwin -- Python 3.6.5, pytest-5.1.2, py-1.8.0, pluggy-0.13.0 -- /Library/Frameworks/Python.framework/Versions/3.6/bin/python3.6
cachedir: .pytest_cache
rootdir: /Volumes/Extended/PythonPrograms/Pytest/TestScripts
collected 1 item test_seven.py::test_return_tuple
SETUP F return_tuple
test_seven.py::test_return_tuple (fixtures used: return_tuple)FAILED
TEARDOWN F return_tuple ============================= FAILURES ============================
________________________ test_return_tuple _______________________________ return_tuple = ('davieyang', '', {'mail': '[email protected]'}) def test_return_tuple(return_tuple):
> assert return_tuple[2]['mail'] == '[email protected]'
E AssertionError: assert '[email protected]' == '[email protected]'
E - [email protected]
E + [email protected]
E ? + test_seven.py:11: AssertionError
============================= 1 failed in 0.08s ======================
假设在fixture函数中出现了异常,那么执行结果中报的会是Error而不是Fail
DY@MacBook-Pro TestScripts$pytest --setup-show -v test_seven.py
=================== test session starts ============================
platform darwin -- Python 3.6.5, pytest-5.1.2, py-1.8.0, pluggy-0.13.0 -- /Library/Frameworks/Python.framework/Versions/3.6/bin/python3.6
cachedir: .pytest_cache
rootdir: /Volumes/Extended/PythonPrograms/Pytest/TestScripts
collected 0 items / 1 errors ============================= ERRORS ==============================
_______________________ ERROR collecting test_seven.py ____________________
如果是测试函数异常,那么会报Fail而不是Error
指定fixture作用范围
Fixture中包含一个叫scope的可选参数,用于控制fixture执行配置和销毁逻辑的频率,该参数有4个值分别是:function、class、module和session,其中function为默认值。
import pytest @pytest.fixture(scope='function')
def func_scope():
"""The function scope fixture""" @pytest.fixture(scope='module')
def mod_scope():
"""The module scope fixture""" @pytest.fixture(scope='class')
def class_scope():
"""The class scope fixture""" @pytest.fixture(scope='session')
def sess_scope():
"""The session scope fixture""" def test_one(sess_scope, mod_scope, func_scope):
"""
Test using session, module, and function scope fixtures
:param sess_scope:
:param mod_scope:
:param func_scope:
:return:
""" def test_two(sess_scope, mod_scope, func_scope):
"""
Again!
:param sess_scope:
:param mod_scope:
:param func_scope:
:return:
""" @pytest.mark.usefixtures('class_scope')
class TestSomething:
def test_three(self):
"""
Test using class scope fixture
:return:
""" def test_four(self):
"""
Again
:return:
"""
执行结果如下:
DY@MacBook-Pro TestScripts$pytest --setup-show test_eight.py
====================== test session starts =======================
platform darwin -- Python 3.6.5, pytest-5.1.2, py-1.8.0, pluggy-0.13.0
rootdir: /Volumes/Extended/PythonPrograms/Pytest/TestScripts
collected 4 items test_eight.py
SETUP S sess_scope
SETUP M mod_scope
SETUP F func_scope
test_eight.py::test_one (fixtures used: func_scope, mod_scope, sess_scope).
TEARDOWN F func_scope
SETUP F func_scope
test_eight.py::test_two (fixtures used: func_scope, mod_scope, sess_scope).
TEARDOWN F func_scope
SETUP C class_scope
test_eight.py::TestSomething::test_three (fixtures used: class_scope).
test_eight.py::TestSomething::test_four (fixtures used: class_scope).
TEARDOWN C class_scope
TEARDOWN M mod_scope
TEARDOWN S sess_scope
====================== 4 passed in 0.07s ============================
从执行结果中,可以看到不但出现了代表函数级别和会话级别的F和S,还出现了代表类级别和模块级别的C和M
scope参数是在定义fixture时定义的,而不是在调用fixture时定义,使用fixture的测试函数是无法改变fixture作用范围的
fixture只能使用同级别的fixture或者高级别的fixture,比如函数级别的fixture可以使用同级别的fixture,也可以使用类级别、模块级别和会话级别的fixture,反过来则不可以
使用usefixtures指定fixture
在之前的代码示例中都是在测试函数的参数列表中指定fixture,除了这种方法可以让测试函数使用fixture外,还可以使用@pytest.mark.usefixtures(‘fixture1’, ‘fixture2’)标记测试函数或测试类,虽然这种方式这对测试函数来说意义不大,因为分别放在测试函数的参数中跟为每个函数做这样的标记比起来或许更简单,但是对于测试类来说,使用这种标记的方式就简便了很多。
@pytest.mark.usefixtures('class_scope')
class TestSomething:
def test_three(self):
"""
Test using class scope fixture
:return:
""" def test_four(self):
"""
Again
:return:
"""
使用usefixtures和在测试方法中指定fixture参数大体上差不多,但只有后者才能够使用fixture的返回值
为常用fixture添加autouse选项
通过指定autouse=True使作用域内的测试函数都运行该fixture,在需要多次运行但不依赖任何的系统状态或者外部数据的代码配合起来更加便利。
import pytest
import time @pytest.fixture(autouse=True, scope='session')
def footer_session_scope():
"""Report the time at the end of a session"""
yield
now = time.time()
print('- -')
print('finish:{}'.format(time.strftime('%d %b %X', time.localtime(now))))
print('---------------') @pytest.fixture(autouse=True)
def footer_function_scope():
"""Report test durations after each function"""
start = time.time()
yield
stop = time.time()
delta = stop - start
print('\ntest duration:{:0.3} seconds'.format(delta)) def test_one():
"""Simulate long-ish running test."""
time.sleep(1) def test_two():
"""Simulate slightly longer test"""
time.sleep(1.23)
执行结果如下:
DY@MacBook-Pro TestScripts$pytest -v -s test_nine.py
====================== test session starts ===================
platform darwin -- Python 3.6.5, pytest-5.1.2, py-1.8.0, pluggy-0.13.0 -- /Library/Frameworks/Python.framework/Versions/3.6/bin/python3.6
cachedir: .pytest_cache
rootdir: /Volumes/Extended/PythonPrograms/Pytest/TestScripts
collected 2 items test_nine.py::test_one PASSED
test duration:1.01 seconds test_nine.py::test_two PASSED
test duration:1.24 seconds
- -
finish:15 Sep 17:12:25
--------------- ======================== 2 passed in 2.26s =========================
为fixture重命名
import pytest @pytest.fixture(name='davieyang')
def rename_fixture_as_davieyang():
""""""
return 36 def test_rename_fixture(davieyang):
assert davieyang == 36
执行结果如下:
DY@MacBook-Pro TestScripts$pytest --setup-show test_ten.py -v
======================= test session starts ==========================
platform darwin -- Python 3.6.5, pytest-5.1.2, py-1.8.0, pluggy-0.13.0 -- /Library/Frameworks/Python.framework/Versions/3.6/bin/python3.6
cachedir: .pytest_cache
rootdir: /Volumes/Extended/PythonPrograms/Pytest/TestScripts
collected 1 item test_ten.py::test_rename_fixture
SETUP F davieyang
test_ten.py::test_rename_fixture (fixtures used: davieyang)PASSED
TEARDOWN F davieyang ========================== 1 passed in 0.01s ==========================
如果想找到‘davieyang’在哪里定义的,则可以使用pytest的参数 --fixtures
DY@MacBook-Pro TestScripts$pytest --fixtures test_ten.py
===================== test session starts ===========================
platform darwin -- Python 3.6.5, pytest-5.1.2, py-1.8.0, pluggy-0.13.0
rootdir: /Volumes/Extended/PythonPrograms/Pytest/TestScripts
collected 1 item
------------------- fixtures defined from TestScripts.test_ten ----------------------------------
davieyang
Return where we renamed fixture
==================== no tests ran in 0.04s ============================
Fixture的参数化
对测试函数进行参数化,可以多次运行的只是该测试函数;对fixture进行参数化,每个使用该fixture的测试函数都可以执行多次
task_tuple = (Task('sleep',None, False),
Task('wake', 'brain',False),
Task('breathe', 'BRAIN', True),
Task('exercise', 'BrIaN', False)) task_ids = ['Task({},{},{})'.format(t.summary, t.owner, t.done)for t in task_tuple] def equivalent(t1, t2):
"""Check two tasks for equivalence"""
return ((t1.summary == t2.summart)and
(t1.owner == t2.owner)and
(t1.done == t2.done)) @pytest.fixture(params=task_tuple)
def a_task(request):
return request.param def test_dosomething(tasks_db, a_task):
task_id = tasks.add(a_task)
t_from_db = tasks.get(task_id)
assert equivalent(t_from_db, a_task)
task_tuple = (Task('sleep',None, False),
Task('wake', 'brain',False),
Task('breathe', 'BRAIN', True),
Task('exercise', 'BrIaN', False)) task_ids = ['Task({},{},{})'.format(t.summary, t.owner, t.done)for t in task_tuple] def equivalent(t1, t2):
"""Check two tasks for equivalence"""
return ((t1.summary == t2.summart)and
(t1.owner == t2.owner)and
(t1.done == t2.done)) @pytest.fixture(params=task_tuple, ids=task_ids)
def b_task(request):
return request.param def test_dosomething(tasks_db, b_task)):
task_id = tasks.add(b_task)
t_from_db = tasks.get(task_id)
assert equivalent(t_from_db, b_task)
task_tuple = (Task('sleep',None, False),
Task('wake', 'brain',False),
Task('breathe', 'BRAIN', True),
Task('exercise', 'BrIaN', False)) def id_function(fixture_value):
t = fixture_value
return 'Task({},{},{})'.format(t.summary, t.owner, t.done) def equivalent(t1, t2):
"""Check two tasks for equivalence"""
return ((t1.summary == t2.summart)and
(t1.owner == t2.owner)and
(t1.done == t2.done)) @pytest.fixture(params=task_tuple, ids=id_function)
def c_task(request):
return request.param def test_dosomething(tasks_db, c_task):
task_id = tasks.add(c_task)
t_from_db = tasks.get(task_id)
assert equivalent(t_from_db, c_task)