20221023 Python Pytest 实战训练营进阶

PPT

代码地址

课后调查

https://jinshuju.net/f/HdMcoS

命名规范

https://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/#section-15

yaml 用法回顾

demo.yaml文件

# list yaml表达列表格式 :
# -【空格】列表中的值1
# -【空格】列表中的值2

# [1,2,3] python 列表对象
#- 1
#- 2
#- 3

# yaml 中表达python字典对象
# key1:[空格]value1
# key2:[空格]value2
# {"name": hogwarts,"course":"测试开发25期"}

#name: hogwarts
#course: 测试开发25期

# 定义嵌套,列表嵌套列表 [[1,1,2], [1,2,3], [2,2,4]]
#-
#  - 1
#  - 1
#  - 2
#-
#  - 1
#  - 2
#  - 3
#- [2,2,4] # 流式写法


# 字典嵌套字典 {"add": {"data": 1, "ids":"a"}}
#add:
#  data: 1
#  ids: a

# 字典嵌套字典,字典嵌套列表 {"add": {"data": [[1,1,2],[2,2,4]], "ids":["a","b"]}}
#add:
#  data:
#    -
#      - 1
#      - 1
#      - 2
#    -
#      - 2
#      - 2
#      - 4
#  ids:
#    - a
#    - b

"""
yaml 数据格式(json,csv ,excel) 存储数据
1. 配置
env: dev
host: 192.168.1.1
port: 8888
2. 测试数据
string
int
float
list
dict
"""
# pip install pyyaml
import yaml
def test_yamldemo():
    # safe_load() 可将yaml对象转成python对象
    with open("../datas/demo.yaml",encoding="utf-8") as f:
        result = yaml.safe_load(f)
    print(result)


def test_safe_dump():
    # safe_dump() 可将python对象转成yaml格式
    data =  {"add": {"data": [[1,1,2],[2,2,4]], "ids":["a","b"]}}
    with open("../datas/data.yaml",mode="w",encoding="utf-8") as f:
        yaml.safe_dump(data, f)



实战练习一

pytest 支持中文展示

在测试项目的根目录下,创建一个名字为conftest.py的文件,在文件中添加下面一段代码

def pytest_collection_modifyitems(items):
    """
    测试用例收集完成时,将收集到的用例名name和用例标识nodeid的中文信息显示在控制台上
    """
    for i in items:
        i.name=i.name.encode("utf-8").decode("unicode_escape")
        i._nodeid=i.nodeid.encode("utf-8").decode("unicode_escape")

读取测试数据关键代码


def get_datas(level):
    """
    读取yaml文件中的数据
    :param level: 通过 级别获取数据,datas 和ids
    :return:
    """
    with open("../datas/calculator.yaml", encoding="utf-8") as f:
        datas = yaml.safe_load(f)
    p0_datas = datas.get('add').get(level).get('datas')
    p0_ids = datas.get('add').get(level).get('ids')
    return [p0_datas,p0_ids]

注意:读取出来的类型如果需要转换成 对应的类别 ,需要使用eval()方法

实战练习二

allure 生成测试报告

  • pip install allure-pytest
  • allure工具
  • Java

# 生成allure 报告
pytest test_add_yaml.py -vs  --alluredir ./result

# 解析allure 报告 
allure serve ./result  

demo.yaml

add:
  P0:
    datas:
      - [1, 1, 2]
      - [-0.01, 0.02, 0.01]
      - [10, 0.02, 10.02]
    ids:
      - 2个整数
      - 2个浮点数
      - 整数+浮点数
  P1_1:
    datas:
      - [98.99, 99, 197.99]
      - [99, 98.99, 197.99]
      - [-98.99, -99, -197.99]
      - [-99, -98.99, -197.99]
      - [99.01, 0, "参数大小超出范围"]
      - [-99.01, -1, "参数大小超出范围"]
      - [2, 99.01, "参数大小超出范围"]
      - [1, -99.01, "参数大小超出范围"]
    ids:
      - 有效边界值相加【98.99,99】
      - 有效边界值相加【99,98.99】
      - 有效边界值相加【-98.99,-99】
      - 有效边界值相加【-99,-98.99】
      - 无效边界值相加【99.01,0】
      - 无效边界值相加【-99.01,-1】
      - 无效边界值相加【2,99.01】
      - 无效边界值相加【1,-99.01】
  P1_2:
    datas:
      - [文, 9.3, "TypeError"]
      - [4, 字, "TypeError"]
      - [nu, 0.2, "TypeError"]
      - [30, t, "TypeError"]
      - ["*&", 0.2, "TypeError"]
      - [21.45, "@", "TypeError"]
    ids:
      - 第一个值是汉字
      - 第二个值是汉字
      - 第一个值是英文字母
      - 第二个值是英文字母
      - 第一个值是特殊字符*&
      - 第二个值是特殊字符@
  P2:
    datas:
      - [None, 20.93, "TypeError"]
      - [-3, None, "TypeError"]
      - [" ", 3.14, "TypeError"]
      - [-90, " ", "TypeError"]
    ids:
      - 第一个值None
      - 第二个值None
      - 第一个值空格
      - 第二个值空格

test_add.py

import pytest
import yaml
import time
from CalculatorProject.script.calculator import Calculator
from CalculatorProject.test.base.Base import Base


def get_datas(fun, level):
    with open("../datas/calculator.yaml", encoding="utf-8") as f:
        datas = yaml.safe_load(f)
        print(datas)
    return {
        "datas": datas[fun][level]["datas"],
        "ids": datas[fun][level]["ids"]
    }

class TestAdd(Base):
    @pytest.mark.add
    @pytest.mark.P0
    @pytest.mark.parametrize("x,y,excepted",
                             get_datas("add", "P0")["datas"],
                             ids=get_datas("add", "P0")["ids"])
    def test_add1(self, x, y, excepted):
        result = self.calc.add(x, y)
        print(f"{x} + {y},预期结果为:{excepted},实际结果为:{result}")
        # 断言
        assert result == excepted

    @pytest.mark.add
    @pytest.mark.P1_1
    @pytest.mark.parametrize("x,y,excepted",
                             get_datas("add", "P1_1")["datas"],
                             ids=get_datas("add", "P1_1")["ids"])
    def test_add4(self, x, y, excepted):
        result = self.calc.add(x, y)
        print(f"{x} + {y},预期结果为:{excepted},实际结果为:{result}")
        # 断言
        assert result == excepted

    @pytest.mark.add
    @pytest.mark.P1_2
    @pytest.mark.parametrize("x,y,excepted",
                             get_datas("add", "P1_2")["datas"],
                             ids=get_datas("add", "P1_2")["ids"])
    def test_add12(self, x, y, excepted):
        # 异常处理两种方式
        # try:
        #     result = self.calc.add(4, "字")
        # except TypeError as e:
        #     print(f"输入类型为中文,类型错误:{e}")

        # with pytest.raises(TypeError) as e:
        #     result = self.calc.add(4, "字")
        # print(f"类型错误:{e}")
        # assert e.type == TypeError

        try:
            result = self.calc.add(x, y)
        except eval(excepted) as e:
            print(f"输入类型错误:{e}")

    @pytest.mark.add
    @pytest.mark.P2
    @pytest.mark.parametrize("x,y,excepted",
                             get_datas("add", "P2")["datas"],
                             ids=get_datas("add", "P2")["ids"])
    def test_add13(self, x, y, excepted):
        try:
            result = self.calc.add(x, y)
        except eval(excepted) as e:
            print(f"输入类型错误:{e}")

data.yaml

add:
  P0:
    data:
    -
      - 1
      - 2
      - 3
    -
      - 0.01
      - 0.02
      - 0.03
    -
      - 10
      - 0.02
      - 10.02
    ids:
      ["int add","float add","double add"]

  P1:
    data:
      - - 98.99
        - 99
        - 197.99

      - - 99
        - 98.99
        - 197.99

      - - -98.99
        - -99
        - -197.99

    ids:
      [ "负整数数浮点数","负整数","负整数浮点数" ]



在此处键入或粘贴代码

test_add.py

import pytest

from CalculatorProject.test.base.Base import Base
from CalculatorProject.test.data.read_yaml import read_yaml

datas = read_yaml("../data/data.yaml")

class TestAdd(Base):

    @pytest.mark.parametrize("x,y,expected", datas['P0']['data'], ids=datas['P0']['ids'])
    @pytest.mark.add
    @pytest.mark.P0
    def test_add_PO(self, x, y, expected):
        result = self.calc.add(x, y)
        print(f"实际结果为:{result}")
        # 断言
        assert result == expected

    @pytest.mark.parametrize("x,y,expected",
                             datas['P1']['data'], ids=datas['P1']['ids'])
    @pytest.mark.add
    @pytest.mark.P1
    def test_add_P1(self, x, y, expected):
        result = self.calc.add(x, y)
        print(f"实际结果为:{result}")
        # 断言
        assert result == expected
在此处键入或粘贴代码

read_yaml.py

import yaml

def read_yaml(f_path):
    with open(f_path, "r", encoding="utf-8") as f:
        data = yaml.safe_load(f)
    return data['add']

在此处键入或粘贴代码

Base.py

from CalculatorProject.script.calculator import Calculator
from CalculatorProject.test.utils.log_util import logger


class Base:

    def setup_class(self):
        # 调用calculator.py 中的add()方法
        # 实例化一个Calculator()实例
        self.calc = Calculator()

    def setup(self):
        # 测试装置,数据的准备工作
        print("开始计算")

    def teardown(self):
        print("结束计算")

    def teardown_class(self):
        # 整个类只执行一次
        logger.info("清理所有的测试数据")
        print("清理所有的测试数据")

在此处键入或粘贴代码
1 个赞
import yaml

def read_data(level):
    with open('../../datas/yaml.yml','r',encoding='utf-8') as f:
        test_data=yaml.safe_load(f)
        data=test_data['add'][level]['datas']
        id=test_data['add'][level]['ids']
        return data,id
import allure

from CalculatorProject.test.cases.until.read_yaml import read_data

import allure
import pytest

from CalculatorProject.test.cases.bases import Base
from CalculatorProject.test.cases.log_util import logger


@allure.feature('加法功能')
class Testcase(Base):

    @allure.story("正向用例")
    @pytest.mark.P0
    @pytest.mark.parametrize('x,y,excepted', read_data('P0')[0],ids=read_data('P0')[1])
    def test_add_001(self, x, y, excepted):
        logger.info(f'实际结果:{self.calculator.add(x, y)}=预期结果:{excepted}')
        assert self.calculator.add(x, y) == excepted

    @allure.story("有效边界")
    @pytest.mark.P1_1
    @pytest.mark.parametrize('x,y,excepted', read_data('P1_1')[0], ids=read_data('P1_1')[1])
    def test_add_002(self, x, y, excepted):
        logger.info(f'实际结果:{self.calculator.add(x, y)}=预期结果:{excepted}')
        assert self.calculator.add(x, y) == excepted

    @pytest.mark.parametrize('x,y,excepted',read_data('P1_2')[0],ids=read_data('P1_2')[1])
    def test_add_003(self, x, y, excepted):
        # try:
        #     result = self.calculator.add(x, y)
        # except TypeError as e:
        #     print(f"输入类型为字符串,类型错误:{e}")
        #     logger.info(f'异常处理:{e}')
        with pytest.raises(TypeError) as f:
            result = self.calculator.add(x, y)
        assert f.type==TypeError

    @pytest.mark.parametrize('x,y,excepted', read_data('P2')[0], ids=read_data('P2')[1])
    def test_add_004(self, x, y, excepted):
        # try:
        #     result = self.calculator.add(x, y)
        # except TypeError as e:
        #     print(f"输入类型为字符串,类型错误:{e}")
        #     logger.info(f'异常处理:{e}')
        with pytest.raises(TypeError) as f:
            result = self.calculator.add(x, y)
        assert f.type == TypeError


1 个赞

read_yaml.py

import config
import yaml

def read_add_data(p,v):
    '''
    :param p: 传用例级别,P0,P1_1,P1_2,P2
    :param v: datas或ids
    :return:
    '''
    with open(f"{config.ROOT_DIR}/test/datas/add.yaml",encoding="utf-8") as f:
        add_data = yaml.safe_load(f)
        # print(add_data)
        # p0_datas = add_data["add"]["P0"]["datas"]
        # print(p0_datas)
        # p0_ids = add_data["add"]["P0"]["ids"]
        # print(p0_ids)
        # p1_1_datas = add_data["add"]["P1_1"]["datas"]
        # p1_1_ids = add_data["add"]["P1_1"]["ids"]
        # p1_2_datas = add_data["add"]["P1_2"]["datas"]
        # p1_2_ids = add_data["add"]["P1_2"]["ids"]
        # p2_datas = add_data["add"]["P2"]["datas"]
        # p2_ids = add_data["add"]["P2"]["ids"]
        return add_data["add"][p][v]

test_add_params.py

@pytest.mark.add
    @pytest.mark.P0
    @pytest.mark.parametrize("a,b,expect", read_yaml.read_add_data("P0","datas"),ids=read_yaml.read_add_data("P0","ids"))
    # 参数化的每一个测试数据,都对应的是单独的一条用例 def test_name()
    # 在执行每一条测试数据之前都会调用setup(),执行之后都会调用teardown()
    def test_add1(self,a,b,expect):
        logger.info(f"a={a}, b= {b}, expect={expect}")
        result = self.calc.add(a, b)
        logger.info(f"实际结果为:{result}")
        # 断言
        assert result == expect

Base.py

    def teardown_class(self):
        # 整个类只执行一次
        print("====>>>清理所有的测试数据")
        del self.calc
1 个赞
class TestAdd(Base):
    with open("D:/cekai_24/CalculatorProject/test/cases/test.yaml", encoding="utf-8") as f:
        data = yaml.safe_load(f)

    @pytest.mark.addP0
    @pytest.mark.parametrize("x,y,expected", data["add"]["P0"]["datas"], ids=data["add"]["P0"]["ids"])
    def test_add_P0(self, x, y, expected):
        logger.info(f"x为{x},y为{y},结果为{expected}")
        assert x + y == expected


    @pytest.mark.addP1_1
    @pytest.mark.parametrize("x,y,expected", data["add"]["P1_1"]["datas"], ids=data["add"]["P1_1"]["ids"])
    def test_add_P1_1(self, x, y, expected):
        logger.info(f"x为{x},y为{y},结果为{expected}")
        try:
            assert x + y == expected
        except AssertionError:
            print("等值错误")

    @pytest.mark.addP1_2
    @pytest.mark.parametrize("x,y,expected", data["add"]["P1_2"]["datas"], ids=data["add"]["P1_2"]["ids"])
    def test_add_P1_2(self, x, y, expected):
        logger.info(f"x为{x},y为{y},结果为{expected}")
        try:
            assert x + y == expected
        except TypeError:
            print("类型错误")

read_yaml.py

import yaml


def read_yaml(f_paths):
    with open(f_paths, "r", encoding="utf-8") as f:
        data = yaml.safe_load(f)
    return data['add']

base.py

from CalculatorProject.script.calculator import Calculator
from CalculatorProject.test.utils.logger import logger


class Base:


    def setup_class(self):
        self.calc = Calculator()

    def setup(self):
        print("开始计算")

    def teardown(self):
        print("结束计算")

test_add_params.py

import pytest

from CalculatorProject.test.base.base import Base
from CalculatorProject.test.datas.read_yaml import read_yaml

datas = read_yaml("../datas/demo.yaml")

print(datas)


class TestAdd(Base):

    @pytest.mark.parametrize("x,y,expected", datas['P0']['data'], ids=datas['P0']['ids'])
    @pytest.mark.add
    @pytest.mark.P0
    def test_add_PO(self, x, y, expected):
        result = self.calc.add(x, y)
        print(f"实际结果为:{result}")
        assert result == expected

    @pytest.mark.parametrize("x,y,expected",
                             datas['P1']['data'], ids=datas['P1']['ids'])
    @pytest.mark.add
    @pytest.mark.P1
    def test_add_P1(self, x, y, expected):
        result = self.calc.add(x, y)
        print(f"实际结果为:{result}")
        assert result == expected

image

class yaml_methods():
    @classmethod
    def yaml_read(self, file_path='../data/data2.yaml', algorithm='add', leave='P0'):
        with open(file_path, encoding="utf-8") as f:
            result = yaml.safe_load(f)
            data1 = result.get(algorithm).get(leave).get('datas')
            ids1 = result.get(algorithm).get(leave).get('ids')

        return data1, ids1
import pytest
from CalculatorProject.test_whj.lib.TestBase import TestBase
from CalculatorProject.test_whj.lib.execl_read import execl_methods
from CalculatorProject.test_whj.lib.yaml_method import yaml_methods


class Test_add(TestBase):
    P0_data, P0_ids = yaml_methods.yaml_read(algorithm='add', leave='P0')

    @pytest.mark.P0
    @pytest.mark.parametrize('a,b,execpt', P0_data, ids=P0_ids)
    def test_add_2(self, a, b, execpt):
        res = self.calculator.add(a, b)
        print(f'a: {a}, b: {b}, c: {execpt} ,res: {res}')
        assert res == execpt