2022.4.17 Pytest实战训练营-进阶

PPT

代码地址

# conftest.py
import pytest
from .pythonCode.calculator import Calculator


@pytest.fixture(scope="class")
def getCalcu():
    print("开始测试")
    calcu = Calculator()
    yield calcu
    print("结束测试")


@pytest.fixture(autouse=True)
def setup_teardown_methond():
    logging.info("开始计算")
    print("开始计算")
    yield
    logging.info("开始计算")
    print("结束计算")

# test_calcu测试文件
@allure.feature("计算器")
class TestCalculator:
    add_p0_datas = readYaml("P0")[0]
    add_p0_ids = readYaml("P0")[1]
    add_P1_1_datas = readYaml("P1_1")[0]
    add_P1_1_ids = readYaml("P1_1")[1]
    add_P1_2_datas = readYaml("P1_2")[0]
    add_P1_2_ids = readYaml("P1_2")[1]

    @pytest.mark.parametrize("first, second, excepted", add_p0_datas, ids=add_p0_ids)
    @pytest.mark.P0
    @allure.title("P0级别的计算器测试用例")
    @pytest.mark.run(order=3)
    def test_add1(self, first, second, excepted, getCalcu):
        assert getCalcu.add(first, second) == excepted

    @pytest.mark.parametrize("first, second, excepted", add_P1_1_datas, ids=add_P1_1_ids)
    @pytest.mark.P1_1
    @allure.title("P1_1级别的计算器测试用例")
    @pytest.mark.run(order=2)
    def test_add2(self, first, second, excepted, getCalcu):
        assert getCalcu.add(first, second) == excepted

    @pytest.mark.parametrize("first, second, excepted", add_P1_2_datas, ids=add_P1_2_ids)
    @pytest.mark.P1_2
    @allure.title("P1_2级别的计算器测试用例")
    @pytest.mark.run(order=1)
    def test_add3(self, first, second, excepted, getCalcu):
        with pytest.raises(eval(excepted)):
            result = getCalcu.add(first, second)
   @pytest.fixture(scope="class", autouse=True)
    def fixture_class(self):
        print("实例化calculator对象")
        self.calc = Calculator()
        yield
        print("结束测试")

    @pytest.fixture(autouse=True)
    def start(self):
        print("开始计算")
        yield
        print("结束计算")

conftest.py

import os
import time
import logging

import pytest

def pytest_collection_modifyitems(items):
    """
    测试用例收集完成时,将收集到的item的name和nodeid的中文显示
    :return:
    """
    for item in items:
        item.name = item.name.encode("utf-8").decode("unicode_escape")
        item._nodeid = item.nodeid.encode("utf-8").decode("unicode_escape")


@pytest.fixture(scope="class", autouse=True)
def get_calc():
    yield Calculator()


@pytest.fixture(scope="class", autouse=True)
def setup_function():
    logging.info("开始计算")
    yield
    logging.info("结束计算")


@pytest.fixture(scope="module", autouse=True)
def setup_function():
    yield
    logging.info("测试结束")


# 日志文件名
def pytest_configure(config):
    now = time.strftime('%Y%m%d_%H%M%S')  # 以日期时间做报告文件名,'%Y%m%d_%H%M%S'
    logfile_name = f'{now}.log'  # 日志文件名
    config.option.log_file = os.path.join(config.rootdir, 'log', logfile_name)

pytest.ini

[pytest]
log_cli = true
log_cli_level = info
addopts = --capture=no -vs
log_cli_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
log_cli_date_format = %Y-%m-%d %H:%M:%S
log_file = ./log/test.log
log_file_level = info
log_file_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
log_file_date_format = %Y-%m-%d %H:%M:%S
markers=P0
        P1

Author: Jakie King

#!/Library/Frameworks/Python.framework/Versions/3.6/bin/python3

from hogwarts_py23_testcode.pytest01.pythoncode.Calculator import Calculator
import pytest

class TestCalculator:

@pytest.fixture(scope="class")
def setup_cal(self):
	cal = Calculator()
	return cal

@pytest.fixture(scope="function", autouse=True)
def counting(self):
	print("开始计算-----")
	yield
	print("结束计算-------")

@pytest.fixture(scope="class", autouse=True)
def ending_test(self):
	yield
	print("测试结束-------")

@pytest.mark.case_p0
@pytest.mark.parametrize('a,b,expected', [(1,1,2), (-0.01,0.02,0.01), (10,0.02,10.02), (-99,98.99, -0.01), (99,98.99, 197.99)])
def test_add_p0_01(self, setup_cal, a, b, expected):
	result = setup_cal.add(a, b)
	print("result---", result)

	# 实际结果 与 预期结果 对比
	assert result == expected
import allure
import pytest
import yaml,logging
from pythoncode.calculator import Calculator


@pytest.fixture(scope='function',autouse=True)
def calculate():
    print('开始计算')
    yield
    print('结束计算')

@pytest.fixture(scope='function')
def calc():
    return Calculator

def get_logger():
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    file_hander=logging.FileHandler('my.log', encoding='utf-8')
    streamHandler=logging.StreamHandler()
    fmt = logging.Formatter('%(asctime)s [%(levelname)s %(message)s (%(filename)s:%(lineno)s)',
                            datefmt='%m/%d/%Y_%I:%M:%S %p')
    file_hander.setFormatter(fmt)
    streamHandler.setFormatter(fmt)
    logger.addHandler(file_hander)
    logger.addHandler(streamHandler)
    return logger


@allure.feature('测试加算器功能的加法运算')
class TestCalculator():
    def setup(self):

        self.logger=get_logger()

    def teardown(self):
        pass

    def teardown_class(self):
        print('结束测试')

    @allure.story('测试正常的加法运算')
    # @pytest.mark.run(order=2)
    @pytest.mark.p0_data
    @pytest.mark.parametrize('x,y,excepted',yaml.safe_load(open('../data/data.yaml',encoding='utf-8'))['data'])
    def test_add0(self,x,y,excepted,calc):
        # 测试相加方法
        # calc = Calculator()
        with allure.step('先进行加法运算'):
            result = calc().add(x,y)
            allure.attach.file('./1.png',name='测试结果的图片',attachment_type=allure.attachment_type.PNG)
            self.logger.info(f'结果是{result}')
            # print(result)
        with allure.step('实际结果 对比 预期结果'):
            # 实际结果 对比 预期结果
            assert result ==excepted

    # @pytest.mark.run(order=1)
    @allure.story('测试异常的加法运算')
    @pytest.mark.p0_data1
    @pytest.mark.parametrize('x,y,exceptederror', yaml.safe_load(open('../data/data.yaml', encoding='utf-8'))['data1'])
    def test_add1(self, x, y, exceptederror,calc):
        # 测试相加方法
        with allure.step('先进行设置异常的捕获'):
            with pytest.raises(eval(exceptederror))as e:
                # calc = Calculator()
                result =calc().add(x, y)
                self.logger.info(f'捕获的异常类型是{e.type}')
                # print(e.type)
        with allure.step('实际异常类型结果 对比 预期异常类型结果'):
            # 实际结果 对比 预期结果
            assert e.type == eval(exceptederror)
#conftest.py
@pytest.fixture(autouse=True)
def fixture_calc():
    print("开始计算")
    yield
    print("结束计算")

@pytest.fixture(scope="class", autouse=True)
def fixture_test():
    testcalc = TestCalc()
    yield testcalc
    print("结束测试")

#test_calc.py
  @pytest.mark.run(order=2)
  @pytest.mark.P0
  @pytest.mark.parametrize('value1, value2, result', CaseData().useData('P0')[0], ids=CaseData().useData('P0')[1])
  def test_P0(self, value1, value2, result):
      try:
          logging.info(f"加法{value1}+{value2}")
          assert self.calc.add(value1, value2) == result
      except Exception as e:
          print(f"异常信息为:{e}")

日志
;日志开关 true false
log_cli = true
;日志级别
log_cli_level = info
;打印详细日志,相当于命令行加 -vs
addopts = --capture=no
;日志格式
log_cli_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
;日志时间格式
log_cli_date_format = %Y-%m-%d %H:%M:%S
;日志文件位置
log_file = ./log/test.log
;日志文件等级
log_file_level = info
;日志文件格式
log_file_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
;日志文件日期格式
log_file_date_format = %Y-%m-%d %H:%M:%S

建议:

  1. 建议fixture名字,不要与系统名字过于相似
  2. 需要添加日志

conftest.py

@pytest.fixture()
def calc():
    print("实例化calculator对象")
    calc = Calculator()
    print("【计算开始】")
    yield calc
    print("【计算结束】")


@pytest.fixture()
def logout():
    log_path = os.path.join(os.path.dirname(os.getcwd()), "log")
    if not os.path.exists(log_path):
        os.makedirs(log_path)
    log_path = os.path.join(log_path, "test_log.txt")
    logger = logging.getLogger(__name__)
    logger.setLevel(level=logging.DEBUG)
    handler = logging.FileHandler(log_path)
    logger.addHandler(handler)
    return logger

test_calc.py

class TestCalculator:
    def teardown_class(self):
        print("结束测试")

    # 冒烟测试用例
    @pytest.mark.P0
    # 需要至少两个参数,第一参数是字符串- 里面存放要传递的参数,以逗号隔开
    # 第二参数,就是我们要传递的数据序列(可以列表,可以元组),每个序列里存放一组数据
    @pytest.mark.parametrize('a,b, expect',
                             [[1, 1, 2], [-0.01, 0.02, 0.01], [10, 0.02, 10.02]],
                             ids=['整型', '浮点型', '整型+浮点型'])
    def test_add0(self, calc, logout, a, b, expect):
        result = calc.add(a, b)
        logout.info(f"结果是{result}")
        assert result == expect

pytest.ini

[pytest]

addopts = -vs

log_cli = true
log_cli_level = info
log_cli_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
log_file_level = info
log_file_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
log_file_date_format = %Y-%m-%d %H:%M:%S

test_calc.py

import pytest
import time
import logging
import yaml
import allure
import Calculator


@pytest.fixture(autouse=True, scope="session")
def init_project(request):
    # 创建日志文件名称
    now = time.strftime("%Y-%m-%d %H-%M-%S")
    log_name = 'log/' + now + '.log'
    request.config.pluginmanager.get_plugin("logging-plugin").set_log_path(log_name)

    logging.info("project init")
    yield Calculator()
    logging.info("project end")


@pytest.fixture(autouse=True, scope="function")
def init_testcase():
    logging.info("testcase init")
    yield
    logging.info("testcase end")


def get_datas(level):
    # safe_load: 把yaml 格式 转成python对象
    # safe_dump: 把python对象 转成yaml格式
    with open("./datas.yml", encoding="utf-8") as f:
        result = yaml.safe_load(f)

        add_datas = result.get("add").get(level).get('datas')
        add_ids = result.get("add").get(level).get('ids')

        print(f"{level} 级别的datas {add_datas}")
        print(f"{level} 级别的ids {add_ids}")
    # P0 级别的datas [[1, 1, 2], [-0.01, 0.02, 0.01], [10, 0.02, 10.02]]
    # P0 级别的ids ['2个整数', '2个浮点数', '整数+浮点数']
    return [add_datas, add_ids]


class TestCalculator:
    add_P0_datas = get_datas("P0")[0]
    add_P0_ids = get_datas("P0")[1]
    add_P1_1_datas = get_datas("P1_1")[0]
    add_P1_1_ids = get_datas("P1_1")[1]
    add_P1_2_datas = get_datas("P1_2")[0]
    add_P1_2_ids = get_datas("P1_2")[1]
    add_P2_datas = get_datas("P2")[0]
    add_P2_ids = get_datas("P2")[1]

    @allure.feature("加法")
    @allure.story("正向测试")
    @pytest.mark.P0
    @pytest.mark.parametrize("num1, num2, expect", add_P0_datas, ids=add_P0_ids)
    def test_add_1(self, num1, num2, expect, init_project):
        result = init_project.add(num1, num2)
        logging.info("testcase: {} + {}".format(num1, num2))
        logging.info("expect: {}".format(expect))
        logging.info("result: {}".format(result))
        assert result == expect

    @allure.feature("加法")
    @allure.story("边界值测试")
    @pytest.mark.P0
    @pytest.mark.parametrize("num1, num2, expect", add_P1_1_datas, ids=add_P1_1_ids)
    def test_add3(self, num1, num2, expect, init_project):
        result = init_project.add(num1, num2)
        logging.info("testcase: {} + {}".format(num1, num2))
        logging.info("expect: {}".format(expect))
        logging.info("result: {}".format(result))
        assert result == expect

    @pytest.mark.run(order=3)
    @pytest.mark.P0
    # 需要至少两个参数,第一参数是字符串- 里面存放要传递的参数,以逗号隔开
    # 第二参数,就是我们要传递的数据序列(可以列表,可以元组),每个序列里存放一组数据
    @pytest.mark.parametrize('a,b, expect',
                             add_P0_datas,
                             ids=add_P0_ids)
    @allure.severity(severity_level=allure.severity_level.BLOCKER)
    def test_add0(self, a, b, expect):
        with allure.step("1"):
            time.sleep(1)
        # 测试相加方法
        with allure.step("2"):
            result = self.calc.add(a, b)
        with allure.step("3"):
            logging.info(result)
        # 实际结果 对比 预期结果
        allure.attach('./test.png', name="测试", attachment_type=allure.attachment_type.PNG, extension='.png')
        assert result == expect
[pytest]
addopts = -vs -n auto --alluredir=./result

@allure.feature("计算器加法测试")
class TestCalculator:
    add_P0_datas = get_datas("P0")[0]
    add_P0_ids = get_datas("P0")[1]
    add_P1_1_datas = get_datas("P1_1")[0]
    add_P1_1_ids = get_datas("P1_1")[1]
    add_P1_2_datas = get_datas("P1_2")[0]
    add_P1_2_ids = get_datas("P1_2")[1]
    add_P2_datas = get_datas("P2")[0]
    add_P2_ids = get_datas("P2")[1]

    # 冒烟测试用例
    @allure.story("计算器加法冒烟测试用例")
    @pytest.mark.run(order=3)
    @pytest.mark.P0
    # 需要至少两个参数,第一参数是字符串- 里面存放要传递的参数,以逗号隔开
    # 第二参数,就是我们要传递的数据序列(可以列表,可以元组),每个序列里存放一组数据
    @pytest.mark.parametrize('a,b, expect',
                             add_P0_datas,
                             ids=add_P0_ids)
    def test_add0(self, a, b, expect, get_calc):
        # 测试相加方法
        with allure.step("读取测试数据,执行测试结果"):
            result = get_calc.add(a, b)
        logging.info(result)
        allure.attach.file(source="../file/1.jpg", attachment_type=allure.attachment_type.JPG)
        # 实际结果 对比 预期结果
        with allure.step("断言实际结果与预期结果"):
            assert result == expect

    @pytest.mark.run(order=3)
    @pytest.mark.P0
    # 需要至少两个参数,第一参数是字符串- 里面存放要传递的参数,以逗号隔开
    # 第二参数,就是我们要传递的数据序列(可以列表,可以元组),每个序列里存放一组数据
    @pytest.mark.parametrize('a,b, expect',
                             add_P0_datas,
                             ids=add_P0_ids)
    @allure.story('这是第一个')
    def test_add0(self, a, b, expect,bigan):
        # 测试相加方法
        with  allure.step('测试步骤一'):
            result = self.calc.add(a, b)
        with allure.step('第二步截图'):
            allure.attach.file(r'C:\Users\Administrator\Pictures\728220.png',name='截图',attachment_type=allure.attachment_type.PNG)
        sleep(1)
        with allure.step('第三步打印日志加输出'):
            logging.info(f'输入数据:{a},{b},预期结果:{result}')
            print(result)
        # 实际结果 对比 预期结果
        assert result == expect

命令:
pytest -n auto .\test_calc_params.py --alluredir .\result\ --clean-alluredir
allure serve .\result\

练习二

运行命令

pytest -n 4 --alluredir=./result
allure generate ./result -o ./report

allure报告

pytest.ini

[pytest]

addopts = -vs

log_cli = true
log_cli_level = info
log_cli_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
log_file_level = info
log_file_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
log_file_date_format = %Y-%m-%d %H:%M:%S

test_calc.py

"""
__author__ = '霍格沃兹测试开发学社'
__desc__ = '更多测试开发技术探讨,请访问:https://ceshiren.com/t/topic/15860'
"""


import pytest
import time
import logging
import yaml
import allure
from ..pythoncode.calculator import Calculator


@pytest.fixture(autouse=True, scope="session")
def init_project(request):
    # 创建日志文件名称
    now = time.strftime("%Y-%m-%d %H-%M-%S")
    log_name = 'log/' + now + '.log'
    request.config.pluginmanager.get_plugin("logging-plugin").set_log_path(log_name)

    logging.info("project init")
    yield Calculator()
    logging.info("project end")


@pytest.fixture(autouse=True, scope="function")
def init_testcase():
    logging.info("testcase init")
    yield
    logging.info("testcase end")


def get_datas(level):
    # safe_load: 把yaml 格式 转成python对象
    # safe_dump: 把python对象 转成yaml格式
    with open("resource/datas.yml", encoding="utf-8") as f:
        result = yaml.safe_load(f)

        add_datas = result.get("add").get(level).get('datas')
        add_ids = result.get("add").get(level).get('ids')

        print(f"{level} 级别的datas {add_datas}")
        print(f"{level} 级别的ids {add_ids}")
    # P0 级别的datas [[1, 1, 2], [-0.01, 0.02, 0.01], [10, 0.02, 10.02]]
    # P0 级别的ids ['2个整数', '2个浮点数', '整数+浮点数']
    return [add_datas, add_ids]


class TestCalculator:
    add_P0_datas = get_datas("P0")[0]
    add_P0_ids = get_datas("P0")[1]
    add_P1_1_datas = get_datas("P1_1")[0]
    add_P1_1_ids = get_datas("P1_1")[1]
    add_P1_2_datas = get_datas("P1_2")[0]
    add_P1_2_ids = get_datas("P1_2")[1]
    add_P2_datas = get_datas("P2")[0]
    add_P2_ids = get_datas("P2")[1]

    @allure.feature("加法")
    @allure.story("正向测试")
    @pytest.mark.P0
    @pytest.mark.parametrize("num1, num2, expect", add_P0_datas, ids=add_P0_ids)
    def test_add_1(self, num1, num2, expect, init_project):
        time.sleep(1)

        allure.step('调用加法计算')
        result = init_project.add(num1, num2)

        logging.info("testcase: {} + {}".format(num1, num2))
        logging.info("expect: {}".format(expect))
        logging.info("result: {}".format(result))
        allure.attach.file("./resource/photo.png", name="图片", attachment_type=allure.attachment_type.PNG)

        assert result == expect

    @allure.feature("加法")
    @allure.story("边界值测试")
    @pytest.mark.P0
    @pytest.mark.parametrize("num1, num2, expect", add_P1_1_datas, ids=add_P1_1_ids)
    def test_add3(self, num1, num2, expect, init_project):
        time.sleep(1)

        allure.step('调用加法计算')
        result = init_project.add(num1, num2)

        logging.info("testcase: {} + {}".format(num1, num2))
        logging.info("expect: {}".format(expect))
        logging.info("result: {}".format(result))
        allure.attach.file("./resource/photo.png", name="图片", attachment_type=allure.attachment_type.PNG)

        assert result == expect

实战一 使用 Fixture

  • 使用 fixture 提供 calc 对象
  • 使用 fixture 实现:用例执行之前打印【开始计算】,之后【结束计算】
  • 当前模块所有用例执行完成之后,打印【测试结束】
  • 每条用例添加测试日志,并将日志打印输出到 logs/ <日期_时间>.log 文件中
    思路:
  1. 定义fixture方法,这个可以随自己喜好
    这里定义两个:
    【1】完成:* 使用 fixture 提供 calc 对象、* 当前模块所有用例执行完成之后,打印【测试结束】
    【2】完成:* 使用 fixture 实现:用例执行之前打印【开始计算】,之后【结束计算】
    conftest.py
@pytest.fixture(scope="class")
def make_cal():
    cal = Calculator()
    yield cal
    print("测试结束")

@pytest.fixture(autouse=True)  #因为每个用例都要打印,所以设置为autouse
def print_end():
    print("开始计算")
    print("结束计算")

2.添加日志
pytest支持在ini文件更改log设置
pytest.ini
下面配置了命令行和文件输出的格式和等级和时间显示格式

log_cli = true
log_cli_level = info
log_cli_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
log_cli_date_format = %Y-%m-%d %H:%M:%S
log_file_level = info
log_file_format = %(asctime)s [%(levelname)s] %(message)s (%(filename)s:%(lineno)s)
log_file_date_format = %Y-%m-%d %H:%M:%S

如果想要自定义log文件名称,需添加下面这个fixture
conftest.py

@pytest.fixture(scope="session", autouse=True)
def manage_logs(request):
    """Set log file name same as test name"""
    now = time.strftime("%Y-%m-%d %H-%M-%S")
    log_name = './log/' + now + '.logs'

    request.config.pluginmanager.get_plugin("logging-plugin") \
        .set_log_path(log_name)

实战二

  • 假设每条用例执行需要 1 秒,加速执行用例(速度提升一倍以上)

思路:
加速执行可以使用 xdist
pip install pytest-xdist
命令行输入:
pytest -n auto test_cal.py
//意思为 自动选择核数加快运行py 文件,也可以auto改为自己的CPU核数

  • 生成测试报告(添加用例分类,添加测试步骤,添加图像<本地任意图片>)
    思路:使用allure来实现
    摘取一段代码展示:
    @pytest.mark.P1
    # @pytest.mark.run(order=2)
    @allure.story("无效边界值相加") #添加用例分类
    @pytest.mark.parametrize("x,y,expected",test_func.get_data("P1-2")[0],
                             ids=test_func.get_data("P1-2")[1])
    def test_Ca_add_P1_2(self,make_cal,x,y,expected):
        sleep(1)
        try:
            logging.info(f"参数:{x,y},正在运行P1-2 case")
            with allure.step("参数相加"): #添加用例步骤
                result = make_cal.add(x,y)
            with allure.step("判断是否和期望结果一致"): #添加用例步骤

                assert result == expected
                allure.attach("E:\Life\20200818204615.jpg",    #使用allure.attach附上截图
                          name="截图",
                          attachment_type = allure.attachment_type.JPG,
                          extension=".jpg") #添加用例截图
        except TypeError as e:
            print(f"输入有问题,输入值为{x,y},抛出异常:{e}")
1 个赞