20220626 Python Pytest 实战训练营

PPT

代码地址

课后调查表

https://jinshuju.net/f/emiy7L

实战一 实现数据驱动

  • 创建数据文件:保存测试数据
  • 创建解析文件方法:解析 yaml 中的数据
  • 用例中使用解析之后的数据
  • 执行完所有的用例,打印日志:清理所有的测试数据

练习20分钟

实战二 灵活使用第三方插件完成需求

  • 假设每条用例执行需要 5 秒,加速执行用例(速度提升一倍以上) — pytest-xdist
  • 调整执行用例的顺序,先执行 add P0 级别和 div P0 级别,再执行 add P1 级别和 divP1 级别(addP0>divP0>addP1>divP1)
    pytest-ordering
  • 如果 P0 级别的用例执行失败,则重新尝试执行最多 3 次,如果执行成功则只执行一次 -pytest-rerunfailures
  • 生成测试报告(添加用例分类,添加测试步骤,添加图像<本地任意图片>) allure-pytest
import pytest
import yaml

class TestData:
    def setup_class(self):
        print("准备数据完成")

    def teardown_class(self):
        print("清理所有数据")

    @pytest.mark.parametrize("data",yaml.safe_load(open("data.yml")))
    def test_add(self,data):
        c = data['a'] + data['b']
        assert c == data['expect']
-
  a : 1
  b : 1
  expect : 2
-
  a : 2
  b : 1
  expect : 3
import pytest
from test.base.base import Base
import yaml
import allure
from loguru import logger
import os


def read_yaml(name):
    path = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + os.path.sep + "../..")
    with open(path + "/data/add_calc_data.yml", encoding="utf-8") as f:
        data = yaml.safe_load(f)[name]
        return data


@allure.feature("计算器测试加法")
class TestAdd(Base):

    @pytest.mark.smoke
    @pytest.mark.add
    @pytest.mark.P0
    @allure.severity(allure.severity_level.BLOCKER)
    @allure.story("加法的正向用例")
    @allure.title("加法正向用例:{a}+{b} == {expect}")
    @pytest.mark.parametrize(**read_yaml("add_sucess_p0"))
    def test_add1(self, a, b, expect):
        reslut = self.cal.add(a, b)
        assert reslut == expected

    @pytest.mark.smoke
    @pytest.mark.add
    @allure.severity(allure.severity_level.BLOCKER)
    @pytest.mark.P1
    @allure.story("加法的正向用例")
    @allure.title("加法正向用例:{a}+{b} == {expect}")
    @pytest.mark.parametrize(**read_yaml("add_sucess_p1"))
    def test_add2(self, a, b, expect):
        reslut = self.cal.add(a, b)
        assert reslut == expected

    @pytest.mark.parametrize(**read_yaml("add_faild"))
    @pytest.mark.P1
    def test_add_faild(self, a, b, error_type):
        with pytest.raises(eval(error_type)) as e:
            raise e(f"{a} + {b}, 发生预期的{error_type}异常")
            result = self.cal.add(a, b)
1 个赞

完成的非常好,功能都实现了

改进点:

  • 打开文件要记得关闭文件

from pytest_test_data.data import Calculator
import pytest
import logging
import allure
import yaml

print(yaml.safe_load(open(’./data.yaml’ ,encoding=‘utf-8’)))

from pytest_test_data.logger import

def get_yaml(feilpath):
with open(feilpath,encoding=‘utf-8’) as f:
datas = yaml.safe_load(f)
return datas

@allure.feature(“加法模块”)
class TestAdd:
def setup(self):
print(‘开始执行add’)
def teardown(self):
print(‘结束执行add’)
# @pytest.mark.P0
@allure.story(“加法用例”)
@pytest.mark.parametrize(‘result1,result2,expect’,get_yaml("./datatest.yaml")[“test_data”] )
def test_add1(self,result1,result2,expect):
self.cal = Calculator()
# result 实际
try:
result = self.cal.add(result1,result2)
except Exception as e:
# logging.debug(“debug message”)
print(‘出现异常’)
else:
# logging.debug(“debug message”)
assert result == expect

yaml文件

calculator:
  test_add:
    - - 1
      - 1
      - 2
    - - -0.01
      - 0.02
      - 0.01
    - - 99.01
      - 0
      - "参数大小超出范围"
  test_add_expect_fail:
    - - 文
      - 9.3
      - TypeError
    - - 4
      - 字
      - TypeError

conftest.py

from config import conf as cf
import yaml


# 配置参数化
def pytest_generate_tests(metafunc):
    # 获取当前运行py文件层级
    file_list = metafunc.module.__name__.split(".")
    func_name = metafunc.function.__name__

    # 拼接yaml文件路径
    yaml_path = f"{cf.CASE_DIR}/yml/{file_list[-1]}.yml"

    with open(yaml_path) as f:
        yaml_data = yaml.safe_load(f)

    datas = yaml_data.get("calculator").get(func_name)
    ids = [data.pop(0) for data in datas]
    
    if not datas:
        raise ValueError(f"{file_list}-{func_name}数据集为空或不存在")

    # 配置参数化
    if "params" in metafunc.fixturenames:
        metafunc.parametrize("params", datas, ids=ids)


def pytest_collection_modifyitems(items):
    for item in items:
        item.name = item.name.encode('utf-8').decode('unicode-escape')
        # 用例路径
        item._nodeid = item.nodeid.encode("utf-8").decode("unicode-escape")

case.py

@allure.feature("计算器")
class TestAdd(Base):

    @allure.story("add正向功能")
    @pytest.mark.P0
    def test_add(self, params):
        with allure.step(f"step1: 本次计算 {params[0]} + {params[1]},预期结果:{params[2]}"):
            result = self.cal.add(params[0], params[1])
        with allure.step(f"step2: 校验结果"):
            assert result == params[2]
        allure.attach.file("/Users/soner/Pictures/logo/QA.jpg")

数据:
data.yml

success:
    "正整数相加": [1,1,2]
    "正负2位小数相加": [ -0.01,0.02,0.01 ]
    "正整数小数相加": [ 10,0.02,10.02 ]
    "边界值99先98.99": [ 99,98.99,197.99 ]
    "边界值99后98.99": [ 98.99,99,197.99 ]
    "边界值-99先-98.99": [ -99,-98.99,-197.99 ]
    "边界值-99后-98.99": [ -98.99,-99,-197.99 ]
tips:
    "边界值99.01先": [99.01,0,"参数大小超出范围"]
    "边界值99.01后": [1,99.01,"参数大小超出范围"]
    "边界值-99.01先": [-99.01,-10,"参数大小超出范围"]
    "边界值-99.01后": [-2,-99.01,"参数大小超出范围"]
error:
    "中文先": ["中文先",2,"TypeError"]
    "中文后": [4,"中文后","TypeError"]

工具:
util.py

import os

import yaml

def getdata(filepath):
    """
    当前目录的上一级目录(test目录),拼接test目录下的文件路径
    :param filepath: test目录下的文件路径
    :return: 返回嵌套数据字典
    """
    dirpath = os.path.dirname(os.path.abspath(__file__))
    finalpath = os.path.join(dirpath,"..",filepath)
    print(finalpath)
    with open(finalpath, "r", encoding="utf-8") as f:
        add_data = yaml.safe_load(f)
    return add_data

用例:
test_add.py

import logging

import allure
import pytest
from test.base.Base import Base
from test.utils.util import getdata


@allure.feature("计算器模块")
@allure.story("加法功能")
class TestAdd(Base):
    logging.info("读取yaml数据:字典类型")
    # 获取 yaml 的嵌套数据字典
    add_data = getdata(r"datas\adddata.yml")

    @allure.title("加法:成功")
    @pytest.mark.success
    @pytest.mark.run(order=1)
    @pytest.mark.flaky(reruns=3,reruns_delay=2)
    @pytest.mark.parametrize("add1,add2,expect",
                             add_data["success"].values(),
                             ids=add_data["success"].keys())
    def test_add_success(self,add1,add2,expect):
        logging.info(f"输入数据:{add1},{add2},期望结果:{expect}")
        allure.attach.file(r"C:\workplace\PycharmProject\PytestCalculator\test\img\计算器.PNG",
                           name="计算器",attachment_type=allure.attachment_type.JPG)
        with allure.step("step1:相加操作"):
            result = self.cal.add(add1,add2)
        logging.info(f"断言实际结果{result}=={expect}")
        with allure.step("step2:断言"):
            assert result == expect

    @allure.title("加法:入参提示")
    @pytest.mark.tips
    @pytest.mark.run(order=2)
    @pytest.mark.parametrize("add1,add2,expect",
                             add_data["tips"].values(),
                             ids=add_data["tips"].keys())
    def test_add_tips(self, add1, add2, expect):
        logging.info(f"输入数据:{add1},{add2},期望结果:{expect}")
        with allure.step("step1:相加操作"):
            result = self.cal.add(add1, add2)
        logging.info(f"断言实际结果{result}=={expect}")
        with allure.step("step2:断言"):
            assert result == expect

    @allure.title("加法:入参错误")
    @pytest.mark.error
    @pytest.mark.run(order=3)
    @pytest.mark.parametrize("add1,add2,expect",
                             add_data["error"].values(),
                             ids=add_data["error"].keys())
    def test_add_error(self, add1, add2, expect):
        logging.info(f"输入数据:{add1},{add2},期望结果:{expect}")
        with pytest.raises(TypeError):
            with allure.step("step1:相加操作"):
                result = self.cal.add(add1, add2)
            logging.info(f"断言实际结果{result}=={expect}")
            with allure.step("step2:断言"):
                assert result == expect