一、问题说明
之前讲了setup、teardown可以实现在用例执行之前或执行之后加入一些操作,但这种都是针对整个脚本全局生效的,如果有以下场景:
- 用例 1 需要先登录,用例 2 不需要登录,用例 3 需要先登录。
这是就无法用 setup 和 teardown 来实现了,而fixture可以让我们自定义测试用例的前置条件
二、fixture
2.1.优点如下:
- 命名方式灵活,不局限于 setup 和teardown 这几个命名
- conftest.py 配置里可以实现数据共享,不需要 import 就能自动找到fixture
- scope="module" 可以实现多个.py 跨文件共享前置
- scope="session" 以实现多个.py 跨文件使用一个 session 来完成多个用例
2.2.fixture参数
参数如下:
@pytest.fixture(scope="function", params=None, autouse=False, ids=None, name=None)
def test_01():
print("fixture初始化参数列表说明")
参数列表
- scope:可以理解成fixture的作用域,默认值为function,剩余还有class、module、package、session四个常用值
- autouse:默认值为False,需要用例手动调用该fixture;如果是True,所有作用域内的测试用例都会自动调用该fixture
- name:默认:装饰器的名称,同一模块的fixture相互调用建议写个不同的name
注意
- session的作用域:是整个测试会话,即开始执行pytest到结束测试
三、测试用例如何调用fixture呢?
测试用例调用fixture步骤如下:
- 将fixture名称作为测试用例函数的输入参数
- 测试用例加上装饰器:@pytest.mark.usefixtures(fixture_name)
- fixture设置autouse=True
3.1.调用方式一:
准备代码如下:
import pytest
@pytest.fixture
def login():
print("****************输入账户、密码先登录****************")
def test_01(login):
print("****************用例1:生成订单****************")
def test_02():
print("****************用例2:添加购物车****************")
执行后结果如下:
3.2.调用方式二:
准备代码如下:
import pytest
@pytest.fixture
def login():
print("****************输入账户、密码先登录****************")
def test_01(login):
print("****************用例1:生成订单****************")
def test_02():
print("****************用例2:添加购物车****************")
# 调用方式二:
@pytest.fixture
def login02():
print("*************login02输入账户、密码进行登录*****************")
@pytest.mark.usefixtures("login","login02")
def test_03():
print("****************用例3:修改个人头像****************")
执行用例test_03,后如下:
发现login和login02中的代码都执行了
3.3.调用方式三:
通过autouse参数指定
import pytest
@pytest.fixture
def login():
print("****************输入账户、密码先登录****************")
def test_01(login):
print("****************用例1:生成订单****************")
def test_02():
print("****************用例2:添加购物车****************")
# 调用方式二:
@pytest.fixture
def login02():
print("*************login02输入账户、密码进行登录*****************")
@pytest.mark.usefixtures("login","login02")
def test_03():
print("****************用例3:修改个人头像****************")
# 调用方式三
@pytest.fixture(autouse=True)
def login03():
print("************************auto*******************")
执行用例test_03,后如下,会发现login、login02、login03都执行了,login03由于装饰器参数autouse=True,默认:function,所以会自动执行,如下:
3.4.总结
规则说明如下:
- 在类上面加 @pytest.mark.usefixtures() 装饰器,代表这个类里面所有测试用例都会调用该fixture
- 可以叠加多个 @pytest.mark.usefixtures() ,先执行的放底层,后执行的放上层(由近到远)
- 可以传多个fixture参数,先执行的放前面,后执行的放后面(由前到后)
- 如果fixture有返回值,用 @pytest.mark.usefixtures() 是无法获取到返回值的,必须用传参的方式(方式一)
四、fixture的实例化顺序
4.1.作用域含义说明
在Pytest中,作用域(scope)定义了测试函数、类、模块和会话(Session)的生命周期和可见性。Pytest提供了四个常用的作用域,包括function、class、module和session。
- Function作用域:默认的作用域,每个测试函数在测试执行前后都会被调用一次。每个测试函数都在独立的环境中执行,互相之间不会相互影响。这是最常用的作用域,适用于需要对每个测试函数进行独立设置和清理的情况。
- Class作用域:将多个测试函数组织在一个类中时,可以使用class作用域。类作用域会在测试类的所有方法之前调用
setup_class
方法,并在所有方法执行完成后调用teardown_class
方法。这意味着每个测试类只会创建一次实例,并且可以在不同的测试方法之间共享设置和清理操作。 - Module作用域:在测试模块级别应用作用域。模块范围的操作在每个测试模块开始和结束时执行一次。它适用于需要在整个模块的测试之间共享设置和清理操作的情况。
- package:作用域是针对整个目录而已,可以创建一个名为"conftest.py"的特殊模块,在其中定义一个package作用域的fixture
- Session作用域:在整个测试会话中应用作用域,也就是在所有测试运行之前和之后执行一次。会话范围的操作适用于需要在整个测试会话之间共享设置和清理操作的情况,比如创建数据库连接、启动/停止服务器等。
这些作用域可以通过使用@pytest.fixture
装饰器来定义,作为测试函数、类、模块或会话级别的fixture。fixture是用于提供测试所需资源的对象,可以在测试函数中作为参数使用。
4.2.作用域执行顺序
fixture实例化顺序说明:
- fixture中scope 范围的顺序由高到低:session > package > module > class > function
- 具有相同作用域的fixture遵循测试函数中声明的顺序,并遵循fixture之间的依赖关系(在fixture_A里面依赖的fixture_B优先实例化,然后到fixture_A实例化)
- 自动使用(autouse=True)的fixture将在显式使用(传参或装饰器)的fixture之前实例化
4.3.案例
可以使用@pytest.fixture(scope="function")
来定义一个函数级别的fixture。类似地,可以使用@pytest.fixture(scope="class")
、@pytest.fixture(scope="module")
和@pytest.fixture(scope="session")
来定义其他作用域的fixture。
import pytest
@pytest.fixture(scope="function",autouse=True)
def my_fixtrue():
# 准备操作
print("**********function:每个测试函数在测试用例执行前都会被调用一次**********")
yield
print("**********function:每个测试函数在测试用例执行前都会被调用一次**********")
# 测试用例
def test_func():
print("****************测试用例test_func****************")
assert 8 == 8
@pytest.fixture(scope="class",autouse=True)
def class_fixtrue():
# 准备操作
print("**********class:类作用域会在测试类的所有方法之前调用setup_class方法**********")
yield
# 清理操作
print("**********class:并在所有方法执行完成后调用teardown_class方法**********")
class Test_pass:
def test_01(self):
print('********用例01********')
assert 1 == 1
def test_02(self):
print('********用例02********')
assert 2 == 2
def test_03(self):
print('********用例03********')
assert 3 == 3
@pytest.fixture(scope="module", autouse=True)
def module_fixtrue():
# 准备操作
print("**********module:模块范围的操作在每个测试模块开始时执行一次**********")
yield
# 清理操作
print("**********module:模块范围的操作在每个测试模块结束时执行一次**********")
def test_module():
print("****************测试用例test_module****************")
assert 1 == 1
@pytest.fixture(scope="session", autouse=True)
def session_fixtrue1():
# 准备操作
print("**********Session:在整个测试会话中应用作用域,也就是在所有测试运行之前执行一次**********")
yield
# 清理操作
print("**********Session:在整个测试会话中应用作用域,也就是在所有测试运行之后执行一次**********")
def test_session():
print("****************测试用例test_session****************")
assert 1 == 1
注意上面使用autouse,会隐式的自动添加(也可以在测试用例中自己手动的添加执行的fixtrue),执行后结果如下:
yield注意事项
- 如果yield前面的代码,即setup部分已经抛出异常了,则不会执行yield后面的teardown内容
- 如果测试用例抛出异常,yield后面的teardown内容还是会正常执行
4.4.关于fixture的注意点
添加了 @pytest.fixture ,如果fixture还想依赖其他fixture,需要用函数传参的方式,不能用 @pytest.mark.usefixtures() 的方式,否则会不生效,案例代码如下:
import pytest
@pytest.fixture(scope="session")
def open_browser():
print("****************打开浏览器***************")
@pytest.fixture
# 在这个fixture中调用其他的fixture,不能使用@pytest.mark.usefixtures装饰器,而是需要把之前的fixture当成参数传入
# @pytest.mark.usefixtures(open_browser)
def login(open_browser):
# 方法级别的前置操作
print("****************输入账号、密码登录******************")
# 测试用例
def test_order(login):
print("*****************测试用例:test_order********************")
执行的结果如下:
五、Pytest中yield和with的组合
在Pytest中,yield
和with
结合使用可以用于在测试函数执行过程中进行设置和清理操作。yield
被用于实现上下文管理器,它在进入和退出上下文时分别执行特定的代码块。使用yield
可以确保在测试函数执行之前和之后进行必要的资源分配和清理,即使在测试函数抛出异常时也能正确执行清理操作。
下面案例,展示了如何结合yield
和with
来管理数据库连接的设置和清理:
import pytest
import pymysql
@pytest.fixture(scope="function")
def db_connection():
# 在测试函数之前设置数据库连接
with pymysql.Connection(host="192.168.42.136", port=3307, user="admin", password="123456") as conn:
# 将连接对象作为fixture的返回值
yield conn
# 在测试函数之后会自动关闭数据库的连接# 测试用例
def test_query_data(db_connection):
# 使用数据库连接进行查询操作的测试函数
cursor = db_connection.cursor()
# 执行查询操作
在上述示例中,db_connection
是一个函数级别的fixture,它通过上下文管理器方式创建数据库连接。在测试函数执行之前,with
pymysql.Connection()
语句会建立数据库连接,并将连接对象作为fixture的返回值。测试函数中可以使用db_connection
参数来访问已经建立的数据库连接进行查询等操作。
当测试函数执行完毕或者遇到异常时,代码流会回到yield
语句,从而执行with
块之后的清理操作,这里可以关闭数据库连接或进行其他必要的清理操作,确保资源的正确释放。
使用yield
和with
结合可以有效地管理测试函数中的资源,并确保在测试过程中进行适当的设置和清理。这种写法使得测试函数的代码更紧凑和可读,并且可以避免资源泄漏和不正确的状态转换。
六、addfinalizer 函数
addfinalizer
函数是Pytest中的一个方法,用于在测试函数执行完成后注册一个清理函数(finalizer)。该清理函数会在测试函数执行完成后自动调用,无论测试函数是否成功执行或者遇到异常。
addfinalizer
方法可以在fixture中使用,通过将清理函数(可以是一个函数、方法、lambda表达式等)作为参数传递给addfinalizer
方法来注册。当测试函数执行完成后,注册的清理函数会被调用。
下面案例演示如何在fixture中使用addfinalizer
函数:
import pytest
from selenium import webdriver
# 打开浏览器
def open_browser():
# 设置浏览器不关闭
option = webdriver.ChromeOptions()
option.add_experimental_option("detach", True)
# 打开一个浏览器对象
driver = webdriver.Chrome(options=option)
# 最大化
driver.maximize_window()
# 设置隐式等待
driver.implicitly_wait(10)
return driver
# 关闭浏览器
def close_browser(driver):
driver.quit()
@pytest.fixture(scope="function")
def my_fixtrue(request):
# 前置操作
print("***********第二次打开浏览器*********")
driver = open_browser()
def cleanup():
# 后置操作teardown,清理
print("***********第二次关闭浏览器*********")
close_browser(driver)
request.addfinalizer(cleanup)
# 返回前置操作
return driver
# 测试用例
def test_function(my_fixtrue):
# 使用fixtrue提供的资源进行测试
my_fixtrue.get("https://www.baidu.com")
在上述示例中,my_fixture
是一个函数级别的fixture。在fixture中,通过request.addfinalizer()
方法来注册一个清理函数cleanup
。在测试函数执行完成后,Pytest会自动调用清理函数执行一些资源的清理操作。
- 在
cleanup
函数内部,可以编写所有需要进行的资源清理操作,如释放文件句柄、删除临时文件、关闭数据库连接等等。 - 通过使用
addfinalizer
,可以确保在测试函数执行完成后,清理函数会被自动调用,从而保证了资源的正确释放和状态的清理。 - 需要注意的是,
addfinalizer
只能在fixture中使用,不能在普通的测试函数中使用。这是因为Pytest的fixture机制会自动处理fixture的生命周期并运行相应的清理函数。