锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

Pytest中@pytest.fixture()装饰器和conftest.py配置文件的使用

时间:2023-12-29 05:37:02 装饰连接器

前言

前一篇讲用例加setup和teardown在测试用例之前或之后可以添加一些操作,但如果我想实现以下场景,整个脚本将全面生效:
用例1需要先登录,用例2不需要登录,用例3需要先登录。显然,这是不可用的setup和teardown来实现吧。这就是本文学习的目的,自定义测试用例的预设条件

Pytest提供了fixture该机制可以在测试执行前后执行一些操作,类似于setup和teardown。

目录

fixture优势

scope="function",作用范围是在每个测试用例执行前运行一次

当加上scope="class"当前模块下的所有类别都将调整一次fixture,autouse=False时记得传参 。

多个测试用例调用一个fixture功能

#fixture为module等级,当前.py脚本中的所有用例在开始前只执行一次

fixture参数详解



fixture优势

1.firture相对于setup和teardown应该有以下优点

  • 命名方式灵活,不局限于setup和teardown这几个命名
  • conftest.py 数据共享可以在配置中实现,无需import配置可以自动找到
  • scope="module" 当前模块可以实现.py调用文件前后一次
  • scope="session" 以实现多个.py跨文件使用一个session完成多个用例

例如,在许多情况下,我们需要准备数据库连接,准备测试数据,断开数据库连接,清理测试脏数据。

@pytest.fixture函数的scope可能的取值有function,class,module,package 或 session。具体含义如下:

  1. function,表示fixture函数在测试方法执行前后执行一次。
  2. class,表示fixture函数在测试类执行前后执行一次。
  3. module,表示fixture函数在测试脚本执行前后执行一次。
  4. package,表示fixture函数在执行测试包(文件夹)中的第一个测试用例前后执行一次。
  5. session,表示所有测试的最开始和测试结束后执行一次。

通常,需要将数据库连接和断开,以及测试配置文件的读取session级别的fixture函数中,因为这些操作只需要为整个测试活动做一次。测试数据的读取通常是function级别或者class因为测试数据对于不同的测试方法或类别往往是不同的。

@pytest.fixture装饰函数函数名可作为测试方法的参数,用于测试方法fixture函数名作为变量,相当于调用fixture装饰函数。

scope="function",作用范围是每个测试用例执行之前运行一次

fixture(scope="function", params=None, autouse=False, ids=None, name=None)
# @pytest.fixture()如果不写参数,默认是scope="function",它的作用范围是在每个测试用例执行前运行一次
# 如果autouse为True,直接激活所有测试fixture, 不需要输入每个函数fixture可调用。 如果为False(默认值)fixture标记函数名。

# fixture使用案例scope="function" import pytest   @pytest.fixture(autouse=False) def fixture_for_func():     print('这是fixture装饰器标记的函数')   def test_1():     print(实施了测试用例test_1')   def test_2():     print(实施了测试用例test_2')   def test_3(fixture_for_func):     print(实施了测试用例test_3')   if __name__ == "__main__":     pytest.main(["-s", "-v", "test_fixturedemo1.py"]) 

执行结果:

"C:\Program Files\Python37\python.exe" E:/PycharmProjects/api_pytest/tests/test_fixturedemo1.py ============================= test session starts ============================= platform win32 -- Python 3.7.1, pytest-6.1.1, py-1.9.0, pluggy-0.13.1 rootdir: E:\PycharmProjects\api_pytest, configfile: pytest.ini plugins: allure-pytest-2.8.18 collected 3 items  test_fixturedemo1.py 这是session的fixture 实施测试用例test_1 .实施测试用例test_2 .这是fixture装饰标记的函数 实施测试用例test_3 .  ============================== 3 passed in 0.12s ==============================  Process finished with exit code 0 

2.可以看出,只有传入函数名称fixture_for_func的测试用例test_3执行测试用例调用一次fixture_for_func()函数


#如果autouse为True,直接激活所有测试fixture, 不需要输入每个函数fixture可调用。

#fixture使用案例scope="function" import pytest   @pytest.fixture(autouse=True) def fixture_for_func():     print('这是fixture装饰器标记的函数')   def test_1():     print(实施了测试用例test_1')  def test_2():     print(实施了测试用例test_2')  def test_3(fixture_for_func):     print(实施了测试用例test_3')  if __name__ = "__main__":

    pytest.main(["-s","-v","test_fixturedemo.py2"])

执行结果:

test_fixturedemo2.py::test_1 这是session的fixture
这是fixture装饰器标记的函数
执行了测试用例test_1
PASSED
test_fixturedemo2.py::test_2 这是fixture装饰器标记的函数
执行了测试用例test_2
PASSED
test_fixturedemo2.py::test_3 这是fixture装饰器标记的函数
执行了测试用例test_3
PASSED

============================== 3 passed in 0.10s ==============================

Process finished with exit code 0

当加上scope="class"时,当前模块下的所有类,都会调一次fixture,autouse=False时记得传参 。

#scope="class"范围的fixture'也可以在函数上执行,如下图,传参给测试用例test_2
#fixture使用案例scope="class"
import pytest


@pytest.fixture(scope="class")
def fixture_for_class():
    print('用在测试类上的fixture')
#当加上scope="class"时,当前模块下的所有类,都会调一次fixture,autouse=False时记得传参
def test_1():
    print('执行了测试用例test_1')

def test_2(fixture_for_class):
    print('执行了测试用例test_2')
#测试类上的fixture'也可以在函数上执行
def test_3():
    print('执行了测试用例test_3')

class Test_Demo1():
    def test_4(self,fixture_for_class):
        print ("执行了测试test4")

class Test_Demo2():
    def test_5(self):
        print ("执行了测试test5")

if __name__ == "__main__":

    pytest.main(["-s","test_fixturedemo3.py"])

执行结果:

test_fixturedemo3.py 这是session的fixture
执行了测试用例test_1
.用在测试类上的fixture
执行了测试用例test_2
.执行了测试用例test_3
.用在测试类上的fixture
执行了测试test4
.执行了测试test5
.

============================== 5 passed in 0.10s ==============================

Process finished with exit code 0

多个测试用例调用一个fixture功能

# 1.上面的案例是在同一个.py文件中,多个测试用例调用一个fixture功能,如果有多个.py的文件都需要调用这个fixture功能的话,那就不能把fixture写到用例里面去了。
# 此时应该要有一个配置文件,单独管理一些预置的操作场景,pytest提供了配置文件conftest.py。
# conftest.py配置文件需要注意以下点:
# conftest.py配置脚本名称是固定的,不能改名称
# conftest.py与运行的用例要在同一个pakage下,并且有__init__.py文件,conftest.py文件中的函数只在conftest.py所在目录及其子目录中的测试活动生效
# 不需要import导入 conftest.py,pytest用例执行时会自动查找

# @pytest.fixture(scope="session",autouse=True)
# def fixture_for_session():
#     print('这是session的fixture')
#当@pytest.fixture函数范围是scope="session"时,不要像function,class,module一样,和编写的测试case放在一起,我们一般放在另一个文件conftest.py下

#conftest.py文件中的函数只在conftest.py所在目录及其子目录中的测试活动生效
#request.config.rootdir属性,这个属性表示的是pytest.ini这个配置文件所在的目录
#注意:当根目录下没有pytest.ini配置文件时,会默认指向conftest.py所在目录;此时要指向项目根目录,则在项目目录下新建一个 pytest.ini 空文件即可
# 此处定义的函数env用于提取数据,模块下的用例执行时,会自动读取conftest.py文件中的数据
import pytest
import yaml
import os
#定义一个fixture标记的函数env,scope="session" 表示这个fixture函数的作用域是session级别的,在整个测试活动中开始前执行,并且只会被执行一次
#conftest.py文件中的函数只在conftest.py所在目录及其子目录中的测试活动生效
@pytest.fixture(scope="session")
def env(request):
    config_path = os.path.join(request.config.rootdir, 
                               "config", 
                               "test", 
                               "config.yaml")
    #os.path.join(path1[, path2[, ...]])	把目录和文件名合成一个路径,D:\python20190819\api_pytest\config\test\config.yaml
    #request.config.rootdir属性,这个属性表示的是pytest.ini这个配置文件所在的目录,D:\python20190819\api_pytest\
    #注意:当根目录下没有pytest.ini配置文件时,会默认指向conftest.py所在目录;此时要指向项目根目录,则在项目目录下新建一个 pytest.ini 空文件即可
    with open(config_path,encoding='utf-8') as f:
        env_config = yaml.load(f.read(), Loader=yaml.SafeLoader)
        #读取路径中的config.yaml文件中的数据
    return env_config

@pytest.fixture(scope="session",autouse=True)
def fixture_for_session():
    print('这是session的fixture')
#当加上scope="session"时,不要像function,class,module一样,和编写的测试case放在一起,我们一般放在文件conftest.py下
#fixture使用案例scope="session"
import pytest


def test_s1(): #不传
        print("用例1")
    
def test_s2(fixture_for_session):  #session在整个测试活动中开始前执行,只会被执行一次,此处传参也不会调用
        print("用例2")
    
def test_s3(fixture_for_session):
        print("用例3")

if __name__ == "__main__":
    pytest.main(["-s","test_fixturedemo4.py"])

 执行结果

test_fixturedemo4.py 这是session的fixture
用例1
.用例2
.用例3
.

通过测试结果可以看出,session在整个测试活动中开始前执行,只会被执行一次,即使传参给测试用例test_2也不会执行

 

#fixture为module级别时,在当前.py脚本里面所有用例开始前只执行一次

#fixture为module级别时,在当前.py脚本里面所有用例开始前只执行一次
import pytest
@pytest.fixture(scope="module")
def fixture_module():
    print("这是范围是module的fixture")
    a='Tom'
    return a

def test_1(fixture_module): #传参fixture_module
    '''用例传fixture'''
    print("测试账号:%s" % fixture_module)
    assert fixture_module == "Tom"
 
class TestCase():
    def test_2(self, fixture_module): #传参fixture_module
        '''用例传fixture'''
        print("测试账号:%s" % fixture_module)
        assert fixture_module == "Tom"
 
if __name__ == "__main__":
    pytest.main(["-vs", "test_fixturemodule.py"])

执行结果:


test_fixturemodule.py::test_1 这是范围是module的fixture
测试账号:Tom
PASSED
test_fixturemodule.py::TestCase::test_2 测试账号:Tom
PASSED

============================== 2 passed in 0.09s ==============================

Process finished with exit code 0

fixture参数详解 

 #fixture(scope="function", params=None, autouse=False, ids=None, name=None):

#使用装饰器@pytest.fixture()的name参数,指定测试固件(被装饰的函数)的新名字。

#通过装饰器@pytest.fixture()的参数params,实现测试固件的参数化。

#可以通过装饰器@pytest.fixture()的参数ids,设置测试用例的id。

#使用装饰器@pytest.fixture()的name参数,指定测试固件的名字。
#fixture(scope="function", params=None, autouse=False, ids=None, name=None):
import pytest
 
# 给装饰的测试函数重新命名为driver,如果不命名,默认login
@pytest.fixture(name = "driver")
def login():
    print('登录系统')
    token = 'a1b23c'
    yield token
    print('退出登录')
 
def test1(driver): #driver代替了login
    print('in test1: ', driver)
    print('测试1')
 
def test2(driver):
    print('in test2: ', driver)
    print('测试2')

#通过装饰器@pytest.fixture()的参数params,实现测试固件的参数化。
@pytest.fixture(params=['tom', 'jack'])
def login1(request):
    print('%s登录' % request.param)
 
def test_1(login1):
    print('执行测试1')
 
 
# 执行结果:
# setup_demo.py::test1[tom] tom登录
# 执行测试1
# PASSED
# setup_demo.py::test1[jack] jack登录
# 执行测试1
# PASSED

 
@pytest.fixture(params=[('tom', '123'), ('jack', '1234')])
def login2(request):
    user = request.param[0]
    passwd = request.param[1]
    print('登录系统: 用户名%s, 密码%s' %(user, passwd))
 
def test_2(login2):
    print('test 2')

# 执行结果:

# test_fixturename.py::test_2[login20] 登录系统: 用户名tom, 密码123   # 测试用例的id是login20
# test 2
# PASSED
# test_fixturename.py::test_2[login21] 登录系统: 用户名jack, 密码1234
# test 2

# 可以通过装饰器@pytest.fixture()的参数ids,设置测试用例的id。
@pytest.fixture(params=[('tom', '123'), ('jack', '1234')],
                ids=['user_a', 'user_b'])  # 这两个列表里,元素的数目要匹配
def login3(request):
    user = request.param[0]
    passwd = request.param[1]
    print('登录系统: 用户名%s, 密码%s' %(user, passwd))
 
def test3(login3):
    print('test 3')

#执行结果:
# test_fixturename.py::test3[user_a] 登录系统: 用户名tom, 密码123  #测试用例的id是user_a
# test 3
# PASSED
# test_fixturename.py::test3[user_b] 登录系统: 用户名jack, 密码1234
# test 3
# PASSED

 

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章