侧边栏壁纸
博主头像
一朵云的博客博主等级

拥抱生活,向阳而生。

  • 累计撰写 108 篇文章
  • 累计创建 28 个标签
  • 累计收到 7 条评论

目 录CONTENT

文章目录

pytest -- allure-pytest插件的介绍和使用

一朵云
2023-12-18 / 0 评论 / 0 点赞 / 1017 阅读 / 22954 字

pytest -- allure-pytest插件的介绍和使用

简介:

allure-pytest 插件能将 pytest 测试框架生成的结果转换为 Allure Report 框架所需的原始数据文件,进而生成美观、详细、交互性强的测试报告。

特点:

  1. 交互式可视化 – 美观的动态图表,支持测试趋势分析。
  2. 步骤分层展示 – 清晰展示测试步骤、子步骤和附件(日志/截图)。
  3. 丰富上下文 – 支持环境信息、分类标签、优先级标记。
  4. 需配合Allure工具 – 需额外安装命令行工具生成HTML报告。
  5. 适合复杂项目 – 提供企业级详细报告,但配置略重。

准备:

安装插件

# 安装 Python 插件
pip install allure-pytest

安装 Allure 命令行工具 (必须!)

  • Windows: 使用 Scoop 或 Chocolatey (scoop install allure)
  • macOS: 使用 Homebrew (brew install allure)
  • Linux: 使用 SDKMAN! (sdk install allure)

windows安装:

1、临时允许脚本运行(仅当前会话,避免安装scoop失败)

Set-ExecutionPolicy Bypass -Scope Process -Force

2、安装 Scoop

iex (new-object net.webclient).downloadstring('https://get.scoop.sh')

😕 要翻墙!否则一直downloading......

image-pzbk.png

3、安装 Allure 命令行工具

scoop install allure

image-vpcf.png

4、验证:

allure --version

装饰器(注解):

(1)测试用例描述类

注解 作用 示例
@allure.title 自定义测试用例标题 @allure.title("用户登录:测试无效密码")
@allure.description 添加详细描述(支持Markdown) @allure.description(""\"验证当密码为空时,系统返回400错误""\")
@allure.description_html 添加HTML格式描述 @allure.description_html("<h3>测试前端组件</h3>")

(2)测试分层结构类

注解 作用 示例
@allure.epic 定义宏观业务模块(顶层分类) @allure.epic("电商平台")
@allure.feature 定义功能模块 @allure.feature("购物车")
@allure.story 定义用户故事/子功能 @allure.story("添加商品到购物车")
@allure.parent_suite/@allure.suite/@allure.sub_suite 自定义测试套件层级 @allure.suite("API测试")

(3)测试步骤类

注解 作用 示例
@allure.step 定义测试步骤(可嵌套) 见下方详细示例
allure.dynamic.step 动态生成步骤(运行时确定名称) allure.dynamic.step(f"Step {i}")

(4)其他增强类

注解 作用 示例
@allure.severity 标记用例优先级 @allure.severity(allure.severity_level.CRITICAL)
@allure.tag/@allure.label 添加标签/自定义标签 @allure.tag("smoke", "API")
@allure.link/@allure.issue/@allure.testcase 添加链接/问题追踪 @allure.link(url="https://example.com", name="需求文档")

allure.dynamic 方法

allure.dynamic 提供了动态设置 Allure 特性的能力,可以在测试执行期间根据实际运行情况来调整报告中的信息,而装饰器通常只用于配置固定不变的内容。

常用方法:

1、动态设置测试用例的标题

allure.dynamic.title("根据条件生成的测试标题")

2、动态设置测试用例的描述信息

allure.dynamic.description("这是一个详细的描述,解释了测试的具体内容。")

3、动态设置测试用例的严重性级别

allure.dynamic.severity(allure.severity_level.CRITICAL)

Allure 提供了 5 个内置的严重性等级,按从高到低排序如下:

级别 中文 说明
BLOCKER 阻塞 阻塞性别:最高优先级,通常指导致系统无法继续使用的缺陷
CRITICAL 严重 关键级别:核心功能、主流程、必须通过的测试(如登录、支付)
NORMAL 一般 普通级别:常规功能测试,重要但不致命
MINOR 较小 轻微级别:次要功能、UI 问题、不影响主流程
TRIVIAL 轻微 琐碎级别:非常轻微的问题,如拼写错误、提示语优化

4、动态创建一个步骤,并在其内部执行代码块

with allure.step("执行某个操作"):
    # 执行具体操作
    pass

5、动态添加链接到报告中

allure.dynamic.link("http://example.com", "相关文档", link_type=allure.link_type.LINK)

6、动态关联外部测试用例管理系统中的用例

allure.dynamic.testcase("http://example.com/testcase/123")

allure.attach() 方法

allure.attach()允许我们在测试报告中附加任意内容,比如文本、图片、日志、请求/响应数据等,从而让报告更加丰富、直观和具有可读性。

语法:

allure.attach(
    body,           # 要附加的内容
    name=None,      # 显示名称(可选)
    attachment_type=None,  # 附件类型(如文本、图片、JSON 等)
    extension=None   # 文件扩展名(可选)
)

示例:

import allure
import json

data = {
    "url": "https://api.example.com/login",
    "method": "POST",
    "headers": {"Content-Type": "application/json"},
    "body": {"username": "admin", "password": "123456"}
}

allure.attach(
    json.dumps(data, indent=2, ensure_ascii=False),
    "请求信息",
    allure.attachment_type.JSON
)

environment.properties

environment.properties 是Allure 报告中用于展示测试环境信息的一个特殊文件,它可以让 Allure 报告首页显示当前测试运行的环境详情。

方式1:手动生成

import os
from datetime import datetime
from pathlib import Path


def generate_environment_properties():
    env_data = {
        "Environment": os.getenv("TEST_ENV", "Local"),  # 从系统环境变量读取
        "Browser": "Chrome",
        "Browser.Version": "125.0.6422.78",
        "OS": os.name,
        "Test.Start.Time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "Python.Version": os.sys.version.split()[0],
        "Allure.Version": "2.24.0"
    }

    # 确保结果目录存在
    results_dir = Path(__file__).parent.parent / "reports" / "results"
    os.makedirs(results_dir, exist_ok=True)
    file_path = os.path.join(results_dir, "environment.properties")
    with open(file_path, 'w', encoding='utf-8') as f:
        for key, value in env_data.items():
            f.write(f"{key}={value}\n")

    print(f"Generated environment.properties at {file_path}")


if __name__ == "__main__":
    generate_environment_properties()

方式2:结合jenkins设置变量

,后续补充。

🚀️ 使用:

1、yml文件示例:

login_tests:
  - id: "login_smoke"
    description: "冒烟测试——账密登录"
    severity: 5
    request_data:
      username: zhangsan
      password: zs123456
      packageName: com.test.a
    expected_response:
      code: 200
      message: 登录成功
      data:
        userId: 10001
        token: 1234567890
        nike_name: 张三

  - id: "login_password_error"
    description: "异常验证——密码错误"
    severity: 4
    request_data:
      username: zhangsan
      password: zs1234566666
      packageName: com.test.a
    expected_response:
      code: 101
      message: 密码校验错误

2、编写allure工具类

# allure_utils.py   Allure 报告工具

import allure
import json
from typing import Any


class AllureUtils:
    @staticmethod
    def attach_screenshot(name: str, png_data: bytes):
        allure.attach(png_data, name, allure.attachment_type.PNG)

    @staticmethod
    def attach_json(name: str, data: Any):
        allure.attach(
            json.dumps(data, indent=2, ensure_ascii=False),  # 格式化为标准 JSON
            name,
            allure.attachment_type.JSON
        )

    # 将请求参数、预期响应、实际响应封装一起
    @staticmethod
    def attach_request_expect_actual(request_data: dict, expected_result: dict, actual_result: dict):
        """
        附加请求参数、预期结果、实际结果
        :param request_data: 请求参数
        :param expected_result: 预期结果
        :param actual_result: 实际结果
        """
        allure.attach(
            json.dumps(request_data, indent=2, ensure_ascii=False),
            "请求参数",
            allure.attachment_type.JSON
        )
        allure.attach(
            json.dumps(expected_result, indent=2, ensure_ascii=False),
            "预期结果",
            allure.attachment_type.JSON
        )
        allure.attach(
            json.dumps(actual_result, indent=2, ensure_ascii=False),
            "实际结果",
            allure.attachment_type.JSON
        )

    @staticmethod
    def attach_api_metadata(url: str, method: str, headers: dict):
        """
        附加API元数据:包括接口地址、请求方式、请求头
        :param url: 接口地址
        :param method: 请求方式(GET, POST等)
        :param headers: 请求头
        """
        metadata = {
            "URL": url,
            "Method": method,
            "Headers": headers
        }

        # 使用json.dumps()美化输出,并确保中文字符不乱码
        metadata_str = json.dumps(metadata, indent=2, ensure_ascii=False)

        allure.attach(
            metadata_str,
            "API 元数据",
            allure.attachment_type.JSON
        )

    @staticmethod
    def attach_api_time(start_time: float, end_time: float):
        """
        将接口请求的开始时间、结束时间、耗时展示出来
        :param start_time: 开始时间(time.time()值)
        :param end_time: 结束时间(time.time()值)
        """
        # 转为本地时间字符串
        start_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(start_time))
        end_time_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(end_time))
        data = {
            "请求开始时间": start_time_str,
            "请求结束时间": end_time_str,
            "总计耗时": f"{end_time - start_time: .3f}秒"
        }
        allure.attach(
            json.dumps(data, indent=2, ensure_ascii=False),
            "接口耗时分析",
            allure.attachment_type.JSON
        )

    @staticmethod
    def attach_title_description_severity(title: str, description: str, severity: int):
        """
        附加标题、描述、严重级别
        :param title: 标题
        :param description: 描述
        :param severity: 严重级别,支持 1~5 的整数
                         1=trivial, 2=minor, 3=normal, 4=critical, 5=blocker
        """
        # 数字到 Allure severity 的映射
        severity_map = {
            1: allure.severity_level.TRIVIAL,
            2: allure.severity_level.MINOR,
            3: allure.severity_level.NORMAL,
            4: allure.severity_level.CRITICAL,
            5: allure.severity_level.BLOCKER,
        }
        allure.dynamic.title(title)
        allure.dynamic.description(description)
        allure.dynamic.severity(severity_map[severity])

3、编写全局fixture

# conftest.py

import httpx
import pytest

from utils.allure_utils import AllureUtils


@pytest.fixture()
def allure_report_supplement():
    """
    目的:封装一些allure报告的内容,利用fixture减少测试用例中的冗余代码
    嵌套函数:外层函数(fixture)返回一个内层函数(_report)
    主要是为了“延迟执行”,简单地说就是等待被调用后才执行!
    避免上下文缺失,如当前这个函数就要等client.sync_request()后,有了response参数再调用!
    :return: _report函数
    """
    def _report(
            case_id: str,
            case_description: str,
            severity_level: str,
            request_data: dict,
            expected_response: dict,
            response: httpx.Response,
            actual_response: dict,
    ):
        """
        封装 Allure 报告附加逻辑
        """
        # 1. 标题、描述、严重级别
        AllureUtils.add_title_description_severity(case_id, case_description, severity_level)

        # 2. API 元数据(URL、Method、Headers)
        AllureUtils.attach_api_metadata(
            str(response.request.url),
            response.request.method,
            dict(response.request.headers)
        )

        # 3、请求开始时间、请求结束时间
        AllureUtils.attach_api_time(response.request.start_time, response.end_time)

        # 4. 请求、预期、实际响应
        AllureUtils.attach_request_expect_actual(request_data, expected_response, actual_response)

    return _report

4、HttpClient封装

# HTTP 请求封装
import time
import httpx
from httpx import Response


async def async_start_time(request):
    request.start_time = time.time()


async def async_end_time(response):
    response.end_time = time.time()


def sync_start_time(request):
    request.start_time = time.time()


def sync_end_time(response):
    response.end_time = time.time()


class HttpClient:
    def __init__(self, base_url: str = "", timeout: int = 10):
        self.base_url = base_url
        self.timeout = timeout
        # 初始化异步客户端
        self._async_client = httpx.AsyncClient(
            base_url=self.base_url,
            timeout=self.timeout,
            event_hooks={
                "request": [async_start_time],
                "response": [async_end_time]
            }
        )
        # 初始化同步客户端
        self._sync_client = httpx.Client(
            base_url=self.base_url,
            timeout=self.timeout,
            event_hooks={
                "request": [sync_start_time],
                "response": [sync_end_time]
            }
        )

    async def async_request(self, method: str, path: str, **kwargs) -> Response:
        return await self._async_client.request(method, path, **kwargs)

    def sync_request(self, method: str, path: str, **kwargs) -> Response:
        return self._sync_client.request(method, path, **kwargs)

    async def aclose(self):
        await self._async_client.aclose()

    def close(self):
        self._sync_client.close()

    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.aclose()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

5、在测试用例中使用

import allure
import pytest

from utils.config_loader import ConfigLoader
from utils.http_client import HttpClient


@allure.epic("SDK3")
@allure.feature("sdk登录接口3")
class TestLogin:
    # 提前加载测试数据
    config_loader = ConfigLoader("data/test_sdk_login.yml")
    login_tests = config_loader.get("login_tests", [])

    @allure.story("用户登录3")
    @pytest.mark.parametrize("test_case", login_tests, ids=[tc.get("id") for tc in login_tests])
    def test_login_single(self, test_case, allure_report_supplement):
        case_id = test_case.get("id", "unknown")
        case_description = test_case.get("description", "unknown")
        request_data = test_case["request_data"]
        expected = test_case["expected_response"]
        severity_level = test_case["severity"]

        client = HttpClient("http://127.0.0.1:8080")
        response = client.sync_request("POST", "/login", json=request_data)
        json_response = response.json()

        # 为 allure 报告补充内容
        allure_report_supplement(
            case_id=case_id,
            case_description=case_description,
            severity_level=severity_level,
            request_data=request_data,
            expected_response=expected,
            response=response,
            actual_response=json_response,
        )

        with allure.step("断言1:判断状态码是否相同"):
            assert json_response["code"] == expected.get("code")
        with allure.step("断言2:当状态码为200时,判断data不为空"):
            if json_response["code"] == 200:
                assert json_response["data"] is not None
        with allure.step("断言3:判断message是否相同 或 message是否在details中存在"):
            assert json_response.get("message") == expected.get("message") or expected.get("message") in str(
                json_response.get("details"))

运行指令:

方式1:

①、运行测试并生成结果

pytest --alluredir=./reports/results [其他参数]

这会执行测试并在 ./reports/results 目录生成原始数据。

参数 说明
--alluredir=PATH 指定 Allure 结果文件的输出目录(生成 .json 文件)
--clean-alluredir 清空 --alluredir 指定的目录(避免旧结果干扰),建议和 --alluredir 一起使用
--allure-no-severity 不生成 severity 标签
--allure-features=FEAT 按 feature 过滤测试用例(执行指定 feature 的用例)
--allure-stories=STORY 按 story 过滤
--allure-epics=EPIC 按 epic 过滤
--allure-ids=IDS 按 allure ID 过滤
--allure-link-type=TYPE 自定义 link 类型

②、生成HTML报告

allure generate ./reports/results -o ./reports/html --clean
  • -o 指定报告输出到 ./reports/html目录
  • --clean 清空已有报告目录

③、打开报告查看

allure open ./reports/html

这会启动本地Web服务器并自动打开浏览器

方式2(调试):

①、运行测试并生成结果

pytest --alluredir=./reports/serve

②、生成实时报告

allure serve ./reports/serve

实时从 ./reports/serve 目录查找数据并生成报告展示

参数补充:

选项 说明
--port 指定端口(默认:4040)
--host 绑定主机(默认:0.0.0.0)
--clean 清除历史结果

解释:

  • 一步完成:自动完成 generate + open 两个操作
  • 临时服务:启动一个本地 Web 服务器(默认端口:4040)
  • 自动清理:生成的报告是临时的(存放在系统临时目录)

何时使用?

  1. 本地开发调试:快速查看最新测试结果
  2. 演示效果:即时展示无需保存报告
  3. 临时分析:不需要保留历史记录时

方式3(推荐)

编写 .sh脚本来运行

#!/bin/bash

# 配置路径
REPORT_DIR="./reports"
RESULTS_DIR="$REPORT_DIR/results"
HTML_DIR="$REPORT_DIR/html"
CONFIG_DIR="./config"

# 确保目录存在
mkdir -p $RESULTS_DIR $HTML_DIR

# 1. 生成环境变量文件
echo "Generating environment properties..."
python $CONFIG_DIR/generate_env.py

# 2. 复制历史数据(如果存在)
if [ -d "$HTML_DIR/history" ]; then
    echo "Copying historical data..."
    cp -r $HTML_DIR/history $RESULTS_DIR/
fi

# 3. 运行测试
echo "Running tests..."
pytest --alluredir=$RESULTS_DIR "$@"
TEST_EXIT_CODE=$?

# 4. 生成报告
echo "Generating Allure report..."
allure generate $RESULTS_DIR -o $HTML_DIR --clean

# 5. 显示报告信息
echo -e "\nAllure report generated at: file://$(pwd)/$HTML_DIR/index.html"
echo "To view report run:"
echo "allure open $HTML_DIR"

# 仅在交互式终端中等待,最多等 30 秒(专门给windows本地调试用,在linux上可以去掉)
if [ -t 0 ] && [ -z "$CI" ]; then
    echo "Press Enter to continue..." >&2
    read -r -t 30 || true  # 30秒后自动继续,不报错
fi

# 6. 根据测试结果退出
exit $TEST_EXIT_CODE

报告效果:

image-mbmu.png

总结:

pytest-html插件的报告静态简洁,属于“快速出结果”类型;allure-pytest插件的报告更强大但更复杂,属于“专业级分析报告”类型。

基于allure的特性封装个工具类,并且基于pytest的fixture装饰器减少用例中的allure代码冗余,搞测试报告还是比较好用的😄

0

评论区