在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', '36', {'mail': 'davieyang@qq.com'}) def test_return_tuple(return_tuple): assert return_tuple[2]['mail'] == 'davieyang1@qq.com'
执行结果如下:
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', '36', {'mail': 'davieyang@qq.com'}) def test_return_tuple(return_tuple): > assert return_tuple[2]['mail'] == 'davieyang1@qq.com' E AssertionError: assert 'davieyang@qq.com' == 'davieyang1@qq.com' E - davieyang@qq.com E + davieyang1@qq.com 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)