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

本文详细介绍了Pytest的fixture机制,包括fixture的优势、不同scope的用法,如function、class、module和session级别。通过fixture,可以在测试前后执行特定操作,如数据库连接和断开。还展示了fixture的参数化使用,以及如何在conftest.py中实现跨文件的数据共享。此外,讲解了如何通过fixture参数ids定制测试用例的id。
摘要由CSDN通过智能技术生成

前言

前面一篇讲到用例加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

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值