自动化测试框架pytest系列之强大的fixture功能,为什么fixture强大?一文拆解它的功能参数。(三)

发布时间:2024年01月11日

自动化测试框架pytest系列之基础概念介绍(一)-CSDN博客

自动化测试框架pytest系列之21个命令行参数介绍(二)-CSDN博客

接上两篇文章继续 :

3.3 pytest支持的初始化和清除函数

学过unittest的都知道 ,unittest有四个函数 ,分别是 :setUp() 、 tearDown 、setUpClass ,tearDownClass 它们的作用就是在用例运行前和运行后所做的操作 ,往往是在运行前做准备工作 ,运行后做恢复 工作 。

而在pytest中,不仅支持了这四个函数 ,而且又进行了扩展 ,分别为:模块级别、类级别、函数级别、方法级别、方法细化级别,分别如下:

而在pytest中,不仅支持了这四个函数 ,而且又进行了扩展 ,分别为:模块级别、类级别、函数级别、方法级别、方法细化级别,分别如下:

支持方法功能描述
setup_module()在每个模块之前执行
teardown_module()在每个模块之后执行
setup_class()在每个类之前执行,即在一个测试类只运行一次setup_class
teardown_class()在每个类之后执行,即在一个测试类只运行一次teardown_class
setup_function()在每个函数之前执行。
teardown_function()在每个函数之后执行。
setup_method()在每个方法之前执行
teardown_method()在每个方法之后执行
setup()在每个方法之前执行
teardown()在每个方法之后执行

首先要明确的是,这些函数确实很重要 ,举个例子你就知道 ,比如你要做web自动化测试 ,通常的操作是:

  1. 打开浏览器

  2. 运行一条测试用例

  3. 关闭浏览器

  4. 重复上面的3个步骤 ,只到所有测试用例运行完毕 。

好了 ,现在的问题是:这个打开浏览器的操作 ,它本身不属于测试用例 ,但是又是一个必须要做的操作 ,怎么办 ?

你就可以将打开浏览器操作放在setup_method()或者setup函数 ,同样关闭浏览器放在teardown_method()或teardown()中。

所以,在编写测试用例中你几乎都会用到它们 。但是在pytest中,这些函数的实现只是为了习惯unittest用户的使用 ,或者是为了兼容unittest框架的那些函数 。如果你不了解unittest中的setup和teardown ,建议你先看看 。如何搭建接口自动化框架系列之unittest测试框架详解(二) - 知乎 (zhihu.com)

但是你如果用过pytest的fixture的话,你几乎就不想用它们了 ,为什么呢 ? 因为它们有10个函数 ,不好记。而如果使用fixture只需要编写一个函数就可以搞定以上所有的需求 。所以 ,在这里建议你使用fixture就可以了 ,它的强大超出你的想象 。

3.4 强大的fixture

在pytest中 ,fixture是核心的功能 ,通过装饰器所使用的 ,而在pytest中使用的装饰器有很多 ,这个我们后来单独分一个板块再说 。

在这里 ,我们先问自己一个问题 ,为什么这个fixture非常强大 ?这的要从执行测试用例说起 ,因为执行测试用例其实就是如下的3个步骤 :

  1. 做初始化操作

  2. 执行测试用例

  3. 进行清除操作

例如,我要编写一条测试用例 ,一般会在这条用例中编写三个方法 ,分别是:

  • 初始化函数:setUp()

  • 参数化的测试用例 :test_case()

  • 清除函数:tearDown()

那如果是10条测试用例呢 ? 那就是30个方法 ,同样编写1000条测试用例 ,就是3000个方法 。虽然每个方法都大同小异 ,但是你使用其他测试框架是无法把他们聚合到一起的 ,这就为我们编写脚本效率是有大大折扣的 。

但是如果你使用pytest的fixture就可以通过一个函数搞定 ,在这个函数中既可以实现setUp()和tearDown() ,也可以通过参数化将测试数据返回给测试用例。这是什么意思呢 ?我们还是拿上面例子来说明 ,如果你是编写3000个方法 ,而使用fixture的话,只需要编写一个fixture函数可以搞定了 。是不是极大的提升了开发效率呢 ? 这就是fixture的强大之处。这种实现在我的自动化高级课程中就有说明。

虽然我们说了这个fixture的强大 ,如果你没有真正把这个强大功能利用出来 ,那你就浪费了这个功能 。废话少说,接下来我们来介绍fixture这个功能 。

先说这个功能的主要用途 ,就两点 :

  1. 可以进行参数化

  2. 可以解决初始化和清除的操作,在上一小节中介绍的那10个函数 ,都可以通过这个fixture来实现 。

函数格式 :

fixture(scope,autouse,params,ids,name):
 ? ?scope :在什么层级下运行 ,它的值只有 :session ,package ,module ,class ,function(默认值)
 ? ?autouse : 代表的是否自动执行 ,若此参数不加或者设置为False的话,代表不会自动运行 ,设置为True自动执行,无需调用
 ? ?params : 进行的数据参数化 ,参数化的数据就是通过此参数传入到测试用例中。
 ? ?ids : 它是给生成的结果的fixture进行重命名 ,主要是为了好理解 ,前提是必须要有参数params 
 ? ?name : 对fixture的函数名起别名, 或者叫重命名 ,没啥大用处 。

使用时只需要将fixture通过装饰器标记到对应的函数上就可以了 。

fixture这个函数中,其中前3个参数是有用的,后面两个无所谓了,可用可不用 。接下来我们介绍它的每个参数使用 :

参数名 :autouse ,它只有两个值,分别是 :

  • True : 如果等于此值 ,此fixture将被自动调用 ,而且都是在测试用例前执行 。

  • False : 该值是此参数的默认值 ,如果等于此值 ,此fixture将不会自动调用,需要主动调用。

接下来我们编写一个fixture函数 ,将autouse设置为True, 里面只打印一句代码 ,然后又编写了两条测试用例 。

?
@pytest.fixture(autouse=True)
def fixture_demo():
 ? ?print("每次运行用例前都执行下这个函数")
?
?
# case1 : 输入正确的用户名和正确的密码进行登录
def test_login_success():
 ? ?print("1")
 ? ?expect_result = 0
 ? ?actual_result = login('admin','123456').get('code')
 ? ?assert expect_result == actual_result
?
?
# case2 : 输入正确的用户名和错误的密码进行登录
def test_password_is_wrong():
 ? ?print("2")
 ? ?expect_reesult = 3
 ? ?actual_result = login('admin','1234567').get('code')
 ? ?assert expect_reesult == actual_result

运行结果 :

通过上面的结果我们看到 ,fixture_demo并没有被主动调用 ,就是因为autouse=Ture ,所以它会被自动调用。

以上我们介绍的是autouse=True的情况 ,那如果它的值是False呢 ? 该如何调用呢 。

首先需要明确一点,如果autouse=False ,这是它的默认值 ,也就是说如果是默认值的话就可以省略 。我们将上面的代码修改下 :

@pytest.fixture(autouse=False)			# 其实这里的autouse可以不传,因为False就是它的默认值
def fixture_demo():
    return "hello pytest"


# case1 : 输入正确的用户名和正确的密码进行登录
def test_login_success():
    print("1")
    expect_result = 0
    actual_result = login('admin','123456').get('code')
    assert expect_result == actual_result


# case2 : 输入正确的用户名和错误的密码进行登录
def test_password_is_wrong(fixture_demo):
    print("2")
    print("fixture_demo:{}".format(fixture_demo))
    expect_reesult = 3
    actual_result = login('admin','1234567').get('code')
    assert expect_reesult == actual_result

注意 :我把fixture函数编写为有返回值了 ,同时在test_password_is_wrong方法中传入了这个fixture函数 。相当于把fixture_demo作为测试方法的参数了 。接下来我们看运行结果:

总结:就是将fixture函数的返回值,直接传给测试用例了 ,测试用例可以接受到fixture的返回值 。

当然 ,你也可以同时编写多个fixture函数 ,同时将这些fixture函数传入到一个用例或者多个用例 ,这都没有问题 。

参数名 :scope ,它有5个值,分别是 :

  • session :如果等于此值 ,那么这个fixture在整个项目下只运行一次 ,这个特别适合于登录 ,登录在项目中只需要登录一次。

  • package :如果设置为此值 ,那么这个fixture在这个包下只运行一次 。

  • module : 如果设置为此值 ,那么这个fixture在这个文件中只运行一次 ,文件中可能既有函数又有类 。

  • class : 如果设置为此值 ,那么这个fixture在这个类中只运行一次

  • function:它是这个函数的默认值,如果为此值,那么这个fixture在每个测试函数前运行一次 ,

以下先看一个示例, 这就是scope等于session级别的 。

接下来我们再看一个scope=function级别的,这是它的默认设置 ,所以可以不设置 ,代码依旧这段代码。 ?


@pytest.fixture(autouse=True)
def fixture_demo():
    print("每次运行用例前都执行下这个函数")


# case1 : 输入正确的用户名和正确的密码进行登录
def test_login_success():
    print("1")
    expect_result = 0
    actual_result = login('admin','123456').get('code')
    assert expect_result == actual_result


# case2 : 输入正确的用户名和错误的密码进行登录
def test_password_is_wrong():
    print("2")
    expect_reesult = 3
    actual_result = login('admin','1234567').get('code')
    assert expect_reesult == actual_result

?运行结果 :

说明 :scope=function时 ,它会每次在执行测试用例前都会自动先执行一次fixture_demo函数 ,因为这个测试用例是一个函数级别的 。总结为 :当scope=function时 ,它会每次在执行测试用例前执行一次 。

接下来我们再看一种scope=class的情况,具体看代码 ,

@pytest.fixture(scope='class',autouse=True)
def fixture_demo():
    print("在每个类前面只运行一次")


class TestLogin():

    # case1 : 输入正确的用户名和正确的密码进行登录
    def test_login_success(self):
        print("1")
        expect_result = 0
        actual_result = login('admin','123456').get('code')
        assert expect_result == actual_result

    # case2 : 输入正确的用户名和错误的密码进行登录
    def test_password_is_wrong(self):
        print("2")
        expect_reesult = 3
        actual_result = login('admin','1234567').get('code')
        assert expect_reesult == actual_result

?运行结果 :

因为这里面只有一个类 ,所以只运行了一次 。

在上面我们介绍过 ,fixture可以搞定所有的seup和teardown的各种情况 ,上面我们介绍了10个函数 ,而fixture这一个函数就可以搞定 。让我们再次回顾下setup和teardown是啥意思 ,编写一个用例就知道了 。

  1. 编写一个函数 :setup() ,主要用来做用例执行前的初始化工作

  2. 编写一个函数 :teardown() , 主要用例做用例执行后的恢复操作 。

  3. 编写一个测试用例 :test_case() , 所要运行的测试用例 。

那么,它的运行顺序将是 :setup() -> testcase() ->teardown() .

如果使用fixture的话 ,这里就需要用到Python的一个关键字 :yield , 这个关键字非常重要 。不解释,直接上代码 。

@pytest.fixture(autouse=True)
def fixture_demo():
    print("每次运行用例前都执行下这个函数")
    yield 
    print("每次运行用例后再此执行下这个函数")
    
"""
说明 : 针对以上的fixture, 只是在中间加了个yield ,下面又加了一行代码 。
"""


# case1 : 输入正确的用户名和正确的密码进行登录
def test_login_success():
    print("1")
    expect_result = 0
    actual_result = login('admin','123456').get('code')
    assert expect_result == actual_result


# case2 : 输入正确的用户名和错误的密码进行登录
def test_password_is_wrong():
    print("2")
    expect_reesult = 3
    actual_result = login('admin','1234567').get('code')
    assert expect_reesult == actual_result

直接看运行结果 :

我们再回来看它的运行流程 : ?

你看 ,通过fixture和yield就完美的解决了setup和teardown的问题 ,再加上scope值的控制 ,它就可以解决在不同层次上的初始化和清除操作 。比如你想实现个setup_class和teardown_class() . 你只需要在fixture将scope的值设置为class就可以了 。

看到这里 ,你是否会觉得开发pytest的人真是个天才 。

接下来,我们介绍fixture中最牛的功能,数据参数化 。

在介绍这个参数化之前 ,再回头看看前面的测试用例 ,编写的几条测试用例中 ,你是否发现它们有着高度的相似点 。比如:同样都有预期结果、实际结果以及两者的断言 。所不同的只是测试数据 ,这就为我们做数据参数化提供了条件 。

让我们再回顾下什么是数据参数化 :就是操作步骤相同 ,数据不同 ,那就可以通过编写一个用例 ,传入不同的数据就可以搞定 。

而fixture中的params就可以做数据参数化 ,让我们先来了解下这个参数 :

参数名 :params , 它的值主要接受的是列表 ,而列表中的值存放的就是测试数据 。先看个例子 :

import pytest


@pytest.fixture(params=[1, 2, 3])
def fixture_demo(request): # 传入参数request 系统参数
    return request.param  # 取列表中获取单个值,默认的取值方式

def test_number(fixture_demo):
    print("------->fixture_demo")
    assert fixture_demo != 3 # 断言fixture_demo不等于3

    
if __name__ == '__main__':
    pytest.main("-q  test05_fixture_params.py")
 

#执行结果:可以发现结果运行了三次
============================= test session starts =============================
collecting ... collected 3 items

test05_fixture_params.py::test_number[1] PASSED                          [ 33%]------->fixture_demo

test05_fixture_params.py::test_number[2] PASSED                          [ 66%]------->fixture_demo

test05_fixture_params.py::test_number[3] FAILED                          [100%]------->fixture_demo

test05_fixture_params.py:8 (test_number[3])
3 != 3

Expected :3
Actual   :3

以上运行的结果是什么意思呢 ?列表中传入3个数 ,它就运行了3次 ;那如果传8个数呢 ,肯定也就会运行8次 。这里面有几个关键参数需要说明 :

  • request : 在fixture_demo中传入的参数名,这是fixture函数中的系统参数 ,你想要接收params列表里的值,这个参数必须传 ,也必须这样写,这是规则。

  • request param : 在fixture_demo中的返回值,这也是固定格式 ,必须这样写 ,用来返回params列表里的值 ,每次只返回列表里的一个值 。这也是为什么在测试用例中只调用了一次fixture_demo函数 ,而被运行了三次的原因 。

在这里要特别说明return 和 yeild的区别 。可以看到,在fixture_demo函数目前使用的返回语句关键字是return .但它是可以替换为yield的 ,唯一的不同 ,yield的运行流程较长,具体如下:

  1. 先运行yield之前的代码

  2. 接着返到测试用例中 ,将yeild后面的返回值 返回给测试用例 ,然后运行测试用例 。

  3. 接着再运行yeild下面的代码

如果是用return语句 ,它相当于只走了上面的第2步 ,不会有第1步和第3步 。

接下来,我们我们通过fixture来实现登录的测试用例 ,具体如下。

import pytest
from package_pytest.login import login

cases = [(0,'admin','123456'),(3,'admin','1234567'),(2,'admin',''),(1,'','123456')]


@pytest.fixture(params=cases)
def fixture_demo(request):
    print("===初始化方法===")
    yield request.param             # request.param :代表将params接受到的数据返回给测试用例中。
    print("===清除方法===")


# case :运行登录测试用例
def test_login(fixture_demo):
    # fixture_demo : 每次循环进来以后,都给到一组数据 ,而这组数据其实就是一个元组
    print(fixture_demo)
    expect_result = fixture_demo[0]
    username = fixture_demo[1]
    password = fixture_demo[2]
    actual_result = login(username,password).get('code')
    assert expect_result == actual_result

运行结果 :

从上面我们可以看到它的运行逻辑为 :

  1. 将cases里的第一组数据传入到params参数中 。

  2. 然后fixture_demo通过request.param接受到这组数据 。

  3. 然后通过yield 将这组数据再返回到测试用例中,

  4. 测试用例通过参数名为:fixture_demo接受到这组数据 ,然后测试用例再进一步处理 。

  5. 接下来再返回到cases中获取第二组数据 ,然后依次循环以上的四个步骤 。

所以 ,当你列表中的数据是元组 ,那么在测试用例就以元组的形式去处理 ,就比如上面的这个案例就是保存的元组 。而如果列表的值是字符串,你在列表中就以字符串的方式进行处理 ,如果列表里的值是字典 ,那么在测试用例就以字典的形式来处理 。

而使用参数化进行测试用例实现时,在列表中存放的主要还是字典 。

参数名 :ids ,它是给生成的结fixture进行重命名 ,因为它之前生成的模式是按照fixture_demo[index] 进行命名的,其中这个index就是每次循环的数字 ,第一次为0 。如果这个不太好理解 ,就可以使用ids进行重命名 ,但是前提必须要有参数化:params。

?具体代码为 :

import pytest
from package_pytest.login import login

cases = [(0,'admin','123456'),(3,'admin','1234567'),(2,'admin',''),(1,'','123456')]

ids = ['正确的用户名和正确的密码登录','正确的用户名和错误的密码登录','空的密码登录','空的用户名登录']


@pytest.fixture(params=cases,ids=ids)
def fixture_demo(request):
    print("===初始化方法===")
    yield request.param             # request.param :代表将params接受到的数据返回给测试用例中。
    print("===清除方法===")


# case1 : 输入正确的用户名和正确的密码进行登录
def test_login(fixture_demo):
    # fixture_demo : 每次循环进来以后,都给到一组数据 ,而这组数据其实就是一个元组
    print(fixture_demo)
    expect_result = fixture_demo[0]
    username = fixture_demo[1]
    password = fixture_demo[2]
    actual_result = login(username,password).get('code')
    assert expect_result == actual_result

运行结果 :

参数名 :name ,它是给fixture函数重名名的,或者叫起别名 ,主要是为了调用时方便使用 ,一旦起别名,在测试用例中就必须的使用别名 。

import pytest
from package_pytest.login import login

cases = [(0,'admin','123456'),(3,'admin','1234567'),(2,'admin',''),(1,'','123456')]



@pytest.fixture(params=cases,name='fd')
def fixture_demo(request):
    print("===========初始化方法===============")
    yield request.param             # request.param :代表将params接受到的数据返回给测试用例中。
    print("============清除方法================")


# case: 进行登录参数化
def test_login_success(fd):
    # fixture_demo : 每次循环进来以后,都给到一组数据 ,而这组数据其实就是一个元组
    print("1")
    expect_result = fd[0]
    username = fd[1]
    password = fd[2]
    actual_result = login(username,password).get('code')
    assert expect_result == actual_result

可以看到,给fixture_demo起了个别名为fd ,那么在测试用例中传入的fixture名字就必须是fd .

以上就是fixture装饰器的5个参数的介绍 ,其中scope 、autouse 、params这个三个是最主要的 ,把这三个参数用好即可 。

文章来源:https://blog.csdn.net/venustech0919/article/details/135522722
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。