04-pytest 数据驱动

4. pytest 数据驱动

4.1 参数化介绍

    在一些测试场景中,需要输入的测试数据比较多,但流程却是一样的,例如测试登录场景、注册等。针对这种场景,我们可以使用参数化功能来完成相应的测试,即数据驱动测试。
    在这种场景中,测试数据和测试用例是多对一的关系,因此可以将它们分开来看,即把数据抽象成参数,通过参数赋值来驱动执行测试,这种被称之为 参数化。通过参数化功能要,可以实现测试数据与测试用例分离,在pytest中可以使用 @pytest.mark.parameterize

4.2 参数化应用

    通过 @pytest.mark.parameterize 可以实现数据驱动测试,其作用是在收集测试用例的过程中,通过对指定参数的赋值新增被标记对象的调用。

4.2.1 单参数化应用

    单参数化应用场景通常为测试方法中仅有一个数据是变化的,即通过一个参数把多组测试数据传递给测试用例。示例代码如下所示:

# @IDE:      PyCharm
# @Project:  PyCharmProjects
# @File:     test_parameterize_01.py
# @Time      2025-03-13 22:07
# @Author:   Surpass Lee
# @E-mail:   surpassmebyme@gmail.com

import pytest

@pytest.mark.parametrize("test_data",[1,(2,3),"Surpass","Shanghai"])
def test_parameterize_01(test_data):
    print(f"\n获取到的数据为:{test_data},数据类型为:{type(test_data)}")

    代码运行结果如下所示:

============================= test session starts =============================
collecting ... collected 4 items

test_parameterize_01.py::test_parameterize_01[1] PASSED                  [ 25%]
获取到的数据为:1,数据类型为:<class 'int'>

test_parameterize_01.py::test_parameterize_01[test_data1] PASSED         [ 50%]
获取到的数据为:(2, 3),数据类型为:<class 'tuple'>

test_parameterize_01.py::test_parameterize_01[Surpass] PASSED            [ 75%]
获取到的数据为:Surpass,数据类型为:<class 'str'>

test_parameterize_01.py::test_parameterize_01[Shanghai] PASSED           [100%]
获取到的数据为:Shanghai,数据类型为:<class 'str'>


============================== 4 passed in 0.03s ==============================

4.2.2 多参数化应用

    测试数据可以是表达式,输入参数也可以是多个。如果存在多个数据的话,可以通过元组方式来组织数据。示例代码如下所示:

# @IDE:      PyCharm
# @Project:  PyCharmProjects
# @File:     test_parameterize_02.py
# @Time      2025-03-13 22:12
# @Author:   Surpass Lee
# @E-mail:   surpassmebyme@gmail.com
import pytest

@pytest.mark.parametrize("expression,expect",[("1+2",3),("4*8",32),("10-9",2)])
def test_parameterize_01(expression,expect):
    actual=eval(expression)
    print(f"\n获取到的数据为:{expression},计算结果为{actual},期望结果为:{expect}")
    assert actual == expect

    运行结果如下所示:

============================= test session starts =============================
collecting ... collected 3 items

test_parameterize_02.py::test_parameterize_01[1+2-3] 
test_parameterize_02.py::test_parameterize_01[4*8-32] 
test_parameterize_02.py::test_parameterize_01[10-9-2] 

========================= 1 failed, 2 passed in 0.06s =========================
PASSED              [ 33%]
获取到的数据为:1+2,计算结果为3,期望结果为:3
PASSED             [ 66%]
获取到的数据为:4*8,计算结果为32,期望结果为:32
FAILED             [100%]
获取到的数据为:10-9,计算结果为1,期望结果为:2

test_parameterize_02.py:8 (test_parameterize_01[10-9-2])
1 != 2

Expected :2
Actual   :1
<Click to see difference>

expression = '10-9', expect = 2

    @pytest.mark.parametrize("expression,expect",[("1+2",3),("4*8",32),("10-9",2)])
    def test_parameterize_01(expression,expect):
        actual=eval(expression)
        print(f"\n获取到的数据为:{expression},计算结果为{actual},期望结果为:{expect}")
>       assert actual == expect
E       assert 1 == 2

test_parameterize_02.py:13: AssertionError

4.2.3 多个参数化

    一个测试用例是可以被多个参数化数据标记的,示例代码如下所示:

# @IDE:      PyCharm
# @Project:  PyCharmProjects
# @File:     test_parameterize_03.py
# @Time      2025-03-13 22:18
# @Author:   Surpass Lee
# @E-mail:   surpassmebyme@gmail.com
import pytest

@pytest.mark.parametrize("firtst_data",[1,2,3])
@pytest.mark.parametrize("second_data,expect",[(1,2),(4,6)])
def test_parameterize_01(firtst_data,second_data,expect):
    print(f"\n获取数据的数据分别为:{firtst_data} - {second_data} - {expect}")

    运行结果如下所示:

============================= test session starts =============================
collecting ... collected 6 items

test_parameterize_03.py::test_parameterize_01[1-2-1] PASSED              [ 16%]
获取数据的数据分别为:1 - 1 - 2

test_parameterize_03.py::test_parameterize_01[1-2-2] PASSED              [ 33%]
获取数据的数据分别为:2 - 1 - 2

test_parameterize_03.py::test_parameterize_01[1-2-3] PASSED              [ 50%]
获取数据的数据分别为:3 - 1 - 2

test_parameterize_03.py::test_parameterize_01[4-6-1] PASSED              [ 66%]
获取数据的数据分别为:1 - 4 - 6

test_parameterize_03.py::test_parameterize_01[4-6-2] PASSED              [ 83%]
获取数据的数据分别为:2 - 4 - 6

test_parameterize_03.py::test_parameterize_01[4-6-3] PASSED              [100%]
获取数据的数据分别为:3 - 4 - 6


============================== 6 passed in 0.02s ==============================

    从以上结果可以看出,如果存在多个 @pytest.mark.parameterize 时,输入数据则会是多个parameterize的全组合

4.3 paramterize 参数

4.3.1 argnames参数

    parameterize方法的第一个参数是argnames是一个用逗号分隔的字符串元组。用以表明指定的参数名。argnames通常是与被标记的测试用例的输入参数对应,但在实际使用时,也会受到一些限制。

4.3.1.1 argnames 与测试方法中参数对应关系

1.测试方法未声明,parameterize中声明

# @IDE:      PyCharm
# @Project:  PyCharmProjects
# @File:     test_parameterize_04.py
# @Time      2025-03-13 22:35
# @Author:   Surpass Lee
# @E-mail:   surpassmebyme@gmail.com
import pytest

@pytest.mark.parametrize(["actual","expect"],[(1,1),("Surpass","Surpass")])
def test_parameterize_01(actual):
    assert actual == 1

    测试用例运行会出现报错,如下所示:

In test_parameterize_01: function uses no argument 'expect'

2.测试方法单独声明parametrize中参数

import pytest

@pytest.mark.parametrize(["actual","expect"],[(1,1),("Surpass","Surpass")])
def test_parameterize_02(actual,expect=10):
    assert actual == expect

    测试用例运行会出现报错,如下所示:

function already takes an argument 'expect' with a default value

4.3.1.2 argname 与 fixture 同名

    通常在使用 fixture 和 parameterize 时,测试用例中传入参数可以一个使用fixture,一个使用 parameterize ,但当出现同名时,parameterize会覆盖同名fixture的参数,示例代码如下所示:

# @IDE:      PyCharm
# @Project:  PyCharmProjects
# @File:     test_parameterize_05.py
# @Time      2025-03-13 22:43
# @Author:   Surpass Lee
# @E-mail:   surpassmebyme@gmail.com

import pytest

@pytest.fixture()
def get_prefix():
    return "Test-Fixture-"

@pytest.mark.parametrize("get_prefix,expect",[("Surpass","Test-Parameterize-Surpass"),("Shanghai","Test-Parameterize-Shanghai")])
def test_parameterize_01(get_prefix,expect):
    print(f"\n获取到的数据分别为:{get_prefix} - {expect}")

    运行结果如下所示:

============================= test session starts =============================
collecting ... collected 2 items

test_parameterize_05.py::test_parameterize_01[Surpass-Test-Parameterize-Surpass] PASSED [ 50%]
获取到的数据分别为:Surpass - Test-Parameterize-Surpass

test_parameterize_05.py::test_parameterize_01[Shanghai-Test-Parameterize-Shanghai] PASSED [100%]
获取到的数据分别为:Shanghai - Test-Parameterize-Shanghai


============================== 2 passed in 0.01s ==============================

4.3.2 argvalues 参数

    parameterize方法的第二个参数是arvalues,类型为一个可迭代对象,表示对argvalues参数的赋值。如果包含多个参数,则argvalues的迭代返回元素必须是可度量的值,即支持len()方法,并且长度与argnames中所声明的参数个数相等,因此argvalues可以是元组列表集合等。示例代码如下所示:

# @IDE:      PyCharm
# @Project:  PyCharmProjects
# @File:     test_parameterize_06.py
# @Time      2025-03-13 22:56
# @Author:   Surpass Lee
# @E-mail:   surpassmebyme@gmail.com
import pytest

@pytest.mark.parametrize("actual,expect",[(1,2),[20,21],{3,4}])
def test_parameterize_02(actual,expect):
    print(f"\n传入的参数分别为:{actual} - {expect}")
    assert actual + 1 == expect

    运行结果如下所示:

============================= test session starts =============================
collecting ... collected 3 items

test_parameterize_06.py::test_parameterize_02[1-2] PASSED                [ 33%]
传入的参数分别为:1 - 2

test_parameterize_06.py::test_parameterize_02[20-21] PASSED              [ 66%]
传入的参数分别为:20 - 21

test_parameterize_06.py::test_parameterize_02[3-4] PASSED                [100%]
传入的参数分别为:3 - 4


============================== 3 passed in 0.02s ==============================

4.3.2.1 argvalues 来源于Excel

    argvalues 是一个可迭代对象,因此可应用于更复杂的场景中。而在日常测试过程,我们通过这一功能从Excel、数据库中先读取数据,再传递给argvalues,示例代码如下所示:

  • CSV数据
actual,expect
Surpass,Surpass
Kevin,Kevin
Ben,Ben
  • 测试代码:
# @IDE:      PyCharm
# @Project:  PyCharmProjects
# @File:     test_parameterize_07.py
# @Time      2025-03-13 23:02
# @Author:   Surpass Lee
# @E-mail:   surpassmebyme@gmail.com
import csv
import pytest

def read_csv():
    data=[]
    with open("parameterize.csv",mode="r",encoding="utf-8") as fo:
        csv_data=csv.reader(fo,delimiter=",")
        for item in csv_data:
            data.append(item)
    return data[1:]

@pytest.mark.parametrize(["actual","expect"],read_csv())
def test_parameterize_01(actual,expect):
    print(f"\n获取到的数据分别为:{actual} - {expect}")
    assert actual == expect

  &emsp 运行结果如下所示:

============================= test session starts =============================
collecting ... collected 3 items

test_parameterize_07.py::test_parameterize_01[Surpass-Surpass] PASSED    [ 33%]
获取到的数据分别为:Surpass - Surpass

test_parameterize_07.py::test_parameterize_01[Kevin-Kevin] PASSED        [ 66%]
获取到的数据分别为:Kevin - Kevin

test_parameterize_07.py::test_parameterize_01[Ben-Ben] PASSED            [100%]
获取到的数据分别为:Ben - Ben


============================== 3 passed in 0.02s ==============================

4.3.2.2 argvalues 来源于 pytest.param

    示例代码如下所示:

# @IDE:      PyCharm
# @Project:  PyCharmProjects
# @File:     test_parameterize_08.py
# @Time      2025-03-13 23:17
# @Author:   Surpass Lee
# @E-mail:   surpassmebyme@gmail.com
import pytest

@pytest.mark.parametrize(["actual","expect"],[(1,1),(2,2),pytest.param(66,69,marks=pytest.mark.xfail,id="xfail")])
def test_parameterize_01(actual,expect):
    print(f"\n获取到的数据分别为:{actual} - {expect}")
    assert actual == expect

    运行结果如下所示:

============================= test session starts =============================
collecting ... collected 3 items

test_parameterize_08.py::test_parameterize_01[1-1] 
test_parameterize_08.py::test_parameterize_01[2-2] 
test_parameterize_08.py::test_parameterize_01[xfail] 

======================== 2 passed, 1 xfailed in 0.05s =========================
PASSED                [ 33%]
获取到的数据分别为:1 - 1
PASSED                [ 66%]
获取到的数据分别为:2 - 2
XFAIL               [100%]
获取到的数据分别为:66 - 69

actual = 66, expect = 69

    @pytest.mark.parametrize(["actual","expect"],[(1,1),(2,2),pytest.param(66,69,marks=pytest.mark.xfail,id="xfail")])
    def test_parameterize_01(actual,expect):
        print(f"\n获取到的数据分别为:{actual} - {expect}")
>       assert actual == expect
E       assert 66 == 69

test_parameterize_08.py:12: AssertionError

Process finished with exit code 0

4.3.3 indirect 参数

    indirect 是一个布尔类型的值或argnames的一个子集,其作用是将指定参数的实参通过 request.param 重定向到和参数同名的fixture中,以此满足更为复杂的场景。indirect默认为false,使用parameterize中的数据,为true时使用fixture中的数据。示例代码如下所示:

# @IDE:      PyCharm
# @Project:  PyCharmProjects
# @File:     test_parameterize_09.py
# @Time      2025-03-13 23:27
# @Author:   Surpass Lee
# @E-mail:   surpassmebyme@gmail.com
from operator import index

import pytest

@pytest.fixture()
def add(request):
    return request.param + 1

@pytest.fixture()
def sub(request):
    return request.param - 1

@pytest.mark.parametrize("add,sub",[(1,2),(3,4),(5,6)])
def test_parameterize_01(add,sub):
    print(f"\n01: indirect=False 获取到的数据分别为:{add} - {sub}")

@pytest.mark.parametrize("add,sub",[(10,20),(30,40),(50,60)],indirect=True)
def test_parameterize_02(add,sub):
    print(f"\n02:indirect=True 获取到的数据分别为:{add} - {sub}")

@pytest.mark.parametrize("add,sub",[(100,20),(300,40),(500,60)],indirect=["add"])
def test_parameterize_03(add,sub):
    print(f"\n03: indirect = ['add'] 获取到的数据分别为:{add} - {sub}")

    运行结果如下所示:

============================= test session starts =============================
collecting ... collected 9 items

test_parameterize_09.py::test_parameterize_01[1-2] PASSED                [ 11%]
01: indirect=False 获取到的数据分别为:1 - 2

test_parameterize_09.py::test_parameterize_01[3-4] PASSED                [ 22%]
01: indirect=False 获取到的数据分别为:3 - 4

test_parameterize_09.py::test_parameterize_01[5-6] PASSED                [ 33%]
01: indirect=False 获取到的数据分别为:5 - 6

test_parameterize_09.py::test_parameterize_02[10-20] PASSED              [ 44%]
02:indirect=True 获取到的数据分别为:11 - 19

test_parameterize_09.py::test_parameterize_02[30-40] PASSED              [ 55%]
02:indirect=True 获取到的数据分别为:31 - 39

test_parameterize_09.py::test_parameterize_02[50-60] PASSED              [ 66%]
02:indirect=True 获取到的数据分别为:51 - 59

test_parameterize_09.py::test_parameterize_03[100-20] PASSED             [ 77%]
03: indirect = ['add'] 获取到的数据分别为:101 - 20

test_parameterize_09.py::test_parameterize_03[300-40] PASSED             [ 88%]
03: indirect = ['add'] 获取到的数据分别为:301 - 40

test_parameterize_09.py::test_parameterize_03[500-60] PASSED             [100%]
03: indirect = ['add'] 获取到的数据分别为:501 - 60


============================== 9 passed in 0.03s ==============================
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容