封装 add, div 测试步骤到 yaml 文件,因为测试数据不一样,怎么利用pytest.mark.parametrize 一次性传参呢?

问题描述:

  • 环境:Pycharm
  • 问题复述:
    在计算器的pytest练习中,老师要求封装 add, div 测试步骤到 yaml 文件, 可加法里面的测试数据的期望结果,和除法的不一样,如何通过pytest.mark.parametrize 一次性传参呢?

相关代码

calc.py 相关代码:

from decimal import Decimal

# 声明一个Calc类
class Calc:
    # 定义类方法add,传入参数a和b, 方法的返回值是 a+b
    def add(self, a, b):
        return Decimal(str(a)) + Decimal(str(b))

    # 定义类方法div,传入参数a和b, 方法的返回值是 a/b
    def div(self, a, b):
        return Decimal(str(a)) / Decimal(str(b))

test_calc.py 相关代码

# 定义一个get_data方法
    def get_data():
        # 定义一个data变量,将data.yml里面的数据读出来
        data = yaml.safe_load(open("data/data.yml", encoding='UTF-8'))
        # 方法返回 data
        return data
@pytest.mark.parametrize('a, b, expect', get_data()["mix"])
    def test_mix(self, a, b, expect, initialization):
        self.get_steps(a, b, expect, initialization)
# 定义一个获取步骤文件内容的方法
    def get_steps_file(self):
        with open('steps/mix_steps.yml') as f:
            return yaml.safe_load(f)

    # 分析步骤文件里的内容
    # 取出mix里面的add和div对应的测试数据
    def get_steps(self, a, b, expect, initialization):
        # 调用读取步骤文件的方法
        steps = self.get_steps_file()
        # 循环分析每一个步骤
        for step in steps:
            print(f"step=={step}")
            # 若步骤等于add,则执行add的测试用例
            if 'add' == step:
                assert initialization.add(a,b) == Decimal(str(expect))
            # 若步骤等于div,则执行div的测试用例
            if 'div' == step:
                assert initialization.div(a,b) == Decimal(str(expect))

mix_steps.yml 相关代码

- add
- div

data.yml 相关代码

mix:
  - [1, 1, 2]
  - [2, 3, 5]

报错信息:

因为加法和减法共用了一套数据,加法可以pass,但是除法会fail,因为case先执行的是加法步骤,再执行的除法步骤,在执行除法之后,整个case都fail掉了

解决办法:

我用了两套数据去分别执行加法和减法,虽然成功了,但是用了6个参数,而且后面如果增加减法和乘法,还需要额外6个,感觉太麻烦了,而且在结果树上(用例里面都是6个参数)看起来也很奇怪。。。所以求助一下老师和同学,怎么做更好?

test_calc.py 相关代码,修改了:

# 取出mix里面的add和div对应的测试数据
    @pytest.mark.parametrize('a1, b1, expect1', get_data()["mix"]["add1"])
    @pytest.mark.parametrize('a2, b2, expect2', get_data()["mix"]["div1"])
    # 调用分析测试步骤的方法
    def calc_mix(self, a1, b1, expect1, a2, b2, expect2, initialization):
        self.get_steps(a1, b1, expect1, a2, b2, expect2, initialization)
# 分析步骤文件里的内容
    def get_steps(self, a1, b1, expect1, a2, b2, expect2, initialization):
        # 调用读取步骤文件的方法
        steps = self.get_steps_file()
        # 循环分析每一个步骤
        for step in steps:
            print(f"step=={step}")
            # 若步骤等于add,则执行add的测试用例
            if 'add' == step:
                assert initialization.add(a1,b1) == expect1
            # 若步骤等于div,则执行div的测试用例
            if 'div' == step:
                assert initialization.div(a2,b2) == expect2

data.yml 相关代码,修改了:

mix:
  add1:
    - [1, 1, 2]
    - [2, 3, 5]
    - [1, 3, 4]

  div1:
    - [2, 1, 2]
    - [6, 2, 3]

你这个写的有点混杂在一起了,你这样就算能跑通,测试报告你也根本看不懂,只有一堆数据你都不知道验证的是哪个方法
建议:
如果想要用参数化加上一个通用方法来执行测试,那么就提前写一个方法读取和处理一下数据,将每条测试数据变为都带上被测方法名的数据,比如[1, 1, 2,‘add1’],这种方法应该很好写,你这样将原本的数据处理之后,就可以用参数化通过pytest来逐条执行,也不会导致看不懂测试报告。另外你get_steps和calc_mix两个方法完全一样,根本没必要写两个,或者你把功能分拆开,get_steps就负责获取对应被测方法的步骤数据,然后再到calc_mix中去根据获取的步骤和测试数据进行测试

老师,我按照您说的思路,做了以下修改,请问是这样写吗?

data.yml 相关代码

mix:
  - [1, 1, 2, add1]
  - [2, 3, 5, add1]
  - [1, 3, 4, add1]
  - [2, 1, 2, div1]
  - [6, 2, 3, div1]

test_calc.py 相关代码

get_steps 代码删掉了

calc_mix 代码改成了:

# 取出mix里面的add和div对应的测试数据
    @pytest.mark.parametrize('a, b, expect, mark', get_data()["mix"])
    # 定义一个方法用于按步骤测试
    def calc_mix(self, a, b, expect, mark, initialization):
        steps = self.get_steps_file()
        # print(steps)
        # 循环分析每一个步骤
        for step in steps:
            print(f"step=={step}")
            # 若步骤等于add,则执行add的测试用例
            if step == 'add':
                if mark == 'add1':
                    result = initialization.add(a, b)
                    assert result == expect
                    print("result is {}, expect is {}".format(result, expect))
            # 若步骤等于div,则执行div的测试用例
            if step == 'div':
                if mark == 'div1':
                    result = initialization.div(a, b)
                    assert result == expect
                    print("result is {}, expect is {}".format(result, expect))

建议:

  • 你不要直接修改数据文件,而是写一个函数来把你之前的数据文件读取出来之后转化为你需要的数据
  • 测试步骤处理的时候,不要每次都把全部测试步骤文件读取出来,因为你在参数里已经输入了被测功能的名称,你可以根据名称直接取出对应的部分运行就可以了

逐步优化,不用很着急,后面还有很多机会能做测试步骤的数据驱动练习,细节都会讲到的,现在只是个简单的入门。

老师,我把数据文件改回原来的了,用test_steps_data()把数据读出来转化成了我需要的数据。
但是您提的第二个建议,还是不太理解:
测试步骤处理的时候,不要每次都把全部测试步骤文件读取出来,因为你在参数里已经输入了被测功能的名称,你可以根据名称直接取出对应的部分运行就可以了

data.yml 相关代码

mix:
  add:
    - [1, 1, 2]
    - [2, 3, 5]
    - [1, 3, 4]

  div:
    - [2, 1, 2]
    - [6, 2, 3]

test_calc.py 相关代码

    def test_steps_data():
        data = yaml.safe_load(open("D:\py_workspace\demo\calc_testing1\data\data.yml"))
        list_mix= []
        for i in range(len(data["mix"]["add"])):
            temp = data["mix"]["add"][i]
            temp.append("add")
            # print(temp)
            list_mix.append(temp)
        for i in range(len(data["mix"]["div"])):
            temp = data["mix"]["div"][i]
            temp.append("div")
            # print(temp)
            list_mix.append(temp)
        print(list_mix)
        return list_mix

展开讲就太多了,后面课程会详细讲到这些内容的,大概先给你写一个demo你看一下思路吧

import pytest
import yaml

"""
data.yaml:

add:
    - [1, 1, 2]
    - [2, 3, 5]
    - [1, 3, 4]

div:
    - [2, 1, 2]
    - [6, 2, 3]
"""


def read_data(file_path):
    with open(file_path, encoding='utf-8') as f:
        data = yaml.safe_load(f)
    result_data = []
    for key, value in data.items():
        for v in value:
            result_data.append(v + [key])
    return result_data


"""
steps.yaml:

add: '+'
div: '/'
"""


def get_step(file_path, method):
    with open(file_path, encoding='utf-8') as f:
        return yaml.safe_load(f)[method]


def add(a, b):
    return a + b


def div(a, b):
    return a / b


@pytest.mark.parametrize('a, b, expect, method', read_data('data.yaml'))
def test_run(a, b, expect, method):
    steps = get_step('steps.yaml', method)
    for step in steps:
        if step == '+':
            assert add(a, b) == expect
        if step == '/':
            assert div(a, b) == expect

好的,谢谢了,老师的demo里面的把数据读出来转化成需要的数据,这个方法比我的清爽太多了,我好好学习下,再次感谢,老师辛苦了!