pytest接口自动化测试框架 - 插件二次开发实战

视频来源:B站《冒死上传!pytest接口自动化测试框架(基础理论到项目实战及二次开发)教学视频【软件测试】》

一边学习一边整理老师的课程内容及试验笔记,并与大家分享,侵权即删,谢谢支持!


一、HOOK函数详解

HOOK函数的定义

HOOK=钩子

钩子函数:

1、是个函数,在系统消息触发时别系统调用

2、不是用户自己触发的

3、使用时直接编写函数体

4、钩子函数的名称是确定,当系统消息触发,自动会调用

比如:pytest_runntest_makereport


插件与hook函数关系:

插件就是用1个或者多个hook函数,也就是钩子函数构成的。如果想要编写新的插件,或者是仅仅改进现有的插件,都必须通过这个hook函数进行。所以想掌握pytest插件二次开发,必须搞定hook函数


【HOOK函数简单实践-修改pytest-html报告】

conftest.py

from datetime import datetime
from py.xml import html
import pytest

"""
    使用测试函数docstring添加描述(Description)列,添加可排序时间(Time)列
    并删除链接Link列
"""
# 添加表头
def pytest_html_results_table_header(cells):
    # 添加Description列
    cells.insert(2, html.th("Description"))
    # 添加Time列
    cells.insert(1, html.th("Time", class_="sortable time", col="time"))
    cells.pop() # 删除最后link列的内容

# 添加表格内容
def pytest_html_results_table_row(report, cells):
    cells.insert(2, html.td(report.description))
    cells.insert(1, html.td(datetime.utcnow(), class_="col-time"))
    cells.pop()

# 获取用例的注释信息
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()
    report.description = str(item.function.__doc__)

main.py

import pytest


if __name__ == '__main__':
    pytest.main(['-s', '-v', '--capture=sys', '--html=./report/report.html'])

test_case01.py

import pytest

def test_case01():
    """
        用例1标题
        用例1步骤
        用例1预期结果
    """
    assert 1==1

def test_case02():
    """
        用例2标题
        用例2步骤
        用例2预期结果
    """
    assert 1==2

def test_case03():
    """
        用例3标题
        用例3步骤
        用例3预期结果
    """
    assert 1==3

运行结果:

pytest接口自动化测试框架 | 插件二次开发实战

【装饰器@pytest.hookimple(hookwrapper=True)】

@pytest.hookimple(hookwrapper=True)装饰的钩子函数,有以下两个作用:

(1)可以获取到测试用例不同执行阶段的结果(setup all teardown)

(2)可以获取钩子方法的调用结果(yield返回一个result对象)和调用结果的测试报告(返回一个report对象)

【HOOK函数排序/调用示例】

官方解释:https://docs.pytest.org/en/latest/how-to/writing_hook_functions.html

对于任何给定的钩子函数规格,可能存在多个实现,因为我们通常将钩子函数执行视为1:N的函数调用,其中N是已注册函数的数量。有一些方法可以影响钩子函数实现是在其他之前还是之后,即在N-sized函数列表中的位置:

执行顺序:

  1. Plugin3的pytest_collection_modifyitems钩子函数最先被调用运行到yield处,因为它拥有装饰器@pytest.hookimpl(hookwrapper=True)。
  1. Plugin1的pytest_collection_modifyitems钩子函数被调用,是因为它标有tryfirst=True。
  1. Plugin2的pytest_collection_modifyitems钩子函数被调用,是因为它被标记trylast=True(但即使没有这个标记,它也会在Plugin1之后出现)。
  1. Plugin3的pytest_collection_modifyitems钩子函数执行yield之后的代码。yield接收一个Result实例,该实例封装了调用没有装饰器的钩子函数的结果。并且不能修改结果。

以上使用tryfirst,trylast,以及结合hookwrapper=True的示例,它会影响彼此之间hookwrappers的排序


Pytest Hook钩子函数完整API

官方:https://docs.pytest.org/en/latest/reference/reference.html?highlight=hooks#hooks


Hooks函数terminal打印测试结果(pytest_report_teststatus)

Hooks函数之统计测试结果(pytest_terminal_summary)

Hooks函数改变用例执行顺序(pytest_collection_modifyitems)

Hooks函数自定义添加命令行参数

。。。


二、插件操作文档详解

pytest-html官网API:

https://pytest-html.readthedocs.io/en/latest/user_guide.html#creating-a-self-contained-report

添加额外内容


三、使用hook函数进行插件二次开发

  1. 项目结构
  1. 主要代码实现

【错误截图插入HTML格式测试报告】

钩子函数:pytest_runtest_makereport()

主要功能:判断每条用例运行情况

开发思路:

1)将失败或错误的用例截图到本地目录中

当测试用例错误或失败后会调用函数进行截图,并且将测试用例“文件名+类名+方法名”作为截图的名称,保存到image/目录中

2)将截图插入到HTML格式的测试报告中

提问:pytest-html会生成一张HTML格式的测试报告,那么如何将截图插入到HTML格式的测试报告中?

添加标签,通过src属性指定图片的路径

衍生问题:ids中文导致控制台输出乱码


实现本地截图功能

截图方法介绍:

  1. save_screenshoot # 获取截屏png图片,参数是文件名称,截屏必须是.png图片,如果只给文件名,截图会保存在项目的根目录下面
  1. get_screenshoot_as_file # 获取截屏png图片,参数是文件的绝对路径,截屏必须是.png图片。如果只给文件名,截屏会存在项目的根目录下。
  1. get_screenshoot_as_png # 获取二进制数据流
  1. get_screenshoot_as_base64 # 获取截屏的base64编码数据,在HTML界面输出截图时使用


conftest.py

import os
from selenium import webdriver
import pytest

from hook_practice.config import RunConfig


@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    pytest_html = item.config.pluginmanager.getplugin("html")
    outcome = yield
    report = outcome.get_result()
    extra = getattr(report, "extra", [])
    # 只获取用例运行时的结果
    if report.when == "call":
        # # always add url to report
        # extra.append((pytest_html.extras.url("http://www/.example.com/")))
        # 用例失败
        xfail = hasattr(report, "wasxfail")
        if (report.skipped and xfail) or (report.failed and not xfail):
            # 将测试用例的“文件名+类名+方法名”作为截图的名称
            # 获取nodeid,并处理::,替换成_
            case_path = report.nodeid.replace("::", "_") + ".png"
            # 还要处理减号-,叠加参数化ids,需要做判断处理
            if "[" in case_path:
                # 通过分隔符取第一个元素,避免出现-号,参数化叠加使用会出现
                case_name = case_path.split("-")[0] + "].png"
            else:
                case_name = case_path

            # 定义方法,实现本地截图
            capture_screenshots(case_name)
            # case_name,不能有/
            img_path = "image/" + case_name.split("/")[-1]
            # 如果存在img_path,向pytest_html中添加截图网页代码
            if img_path:
                html = 'screenshot

config.py

import os

# 获取代码目录的绝对路径
PRO_PATH = os.path.dirname(os.path.abspath(__file__))

class RunConfig:
    """
    测试配置
    """
    # 运行测试用例的目录或文件
    case_path = os.path.join(PRO_PATH, "test_case", "test_baidu.py")

    # 配置浏览器驱动类型(Chrome/Firefox)
    driver_type = "chrome"

    # 配置运行的URL
    url = "http://www.baidu.com"

    # 失败重跑次数
    rerun = "1"

    # 当达到失败最大数,停止运行
    max_fail = "5"

    # 测试报告存放的根目录
    REPORT_DIR = PRO_PATH + "/test_report/"

    # 浏览器驱动
    driver = None

    # 新报告路径
    NEW_REPORT = None

main.run.py

import os
import time
import pytest

from hook_practice.config import RunConfig


def init_env(new_report):
    """
    初始化报告目录
    """
    os.mkdir(new_report)
    os.mkdir(new_report + "/image")

def run():
    # 获取时间,按年月日时分秒排列
    now_time = time.strftime("%Y_%m_%d_%H_%M_%S")
    # 根目录+时间成为每次生成报告的唯一目录
    RunConfig.NEW_REPORT = os.path.join(RunConfig.REPORT_DIR, now_time)
    init_env(RunConfig.NEW_REPORT)
    # 定义测试报告的绝对路径
    html_report = os.path.join(RunConfig.NEW_REPORT, "report.html")
    # 定义备用的xml格式报告的绝对路径
    xml_report = os.path.join(RunConfig.NEW_REPORT, "junit-xml.html")
    # 指定目录/文件执行用例
    pytest.main(["-s", "-v", RunConfig.case_path,
                "--html=" + html_report,
                "--junit-xml=" + xml_report,
                 # 捕获错误日志输出信息到测试报告
                 "--capture=sys",
                 # 设置最大失败次数
                 "--maxfail", RunConfig.max_fail,
                 # 设置失败重跑次数
                 "--reruns", RunConfig.rerun])

if __name__ == '__main__':
    run()

test_case/test_baidu.py

from time import sleep

# 使用在conftest中初始好了的driver
def test_baidu_case01(browser):
    driver = browser
    driver.get("http://www.baidu.com")
    sleep(1)
    driver.find_element_by_id('kw').send_keys("狗狗币")
    sleep(1)
    driver.find_element_by_id('su').click()
    assert driver.title == "11111"
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章