为每一个Python项目创建独立的虚拟环境是一个被广泛推荐的最佳实践和良好习惯,它有很多好处。还推荐使用 Pycharm ,它挺牛的。

确定项目结构

项目都会有项目结构,那最优的项目结构是啥?答案是没有答案。因为它高度依赖于项目的具体类型和使用的编程语言等因素。不过你可以参考本文中的项目结构。

因为需要集成 pytest 和 allure,项目结构可以如下:

Project/
├── allure/                   # allure 目录,集成 allure 相关文件
├── common/                   # 通用的功能实现可以放在这里
│   ├── __init__.py           # 包初始化文件,可以定义一些变量或执行一些操作。当然里面什么都不写也可以。
│   └── tool.py       		  # 具体功能实现,比如 database.py
├── configs/                  # 配置文件目录,一些配置类型的文件都可以放在这里
├── fixtures/                 # 夹具函数目录
│   ├── __init__.py           # 包初始化文件,可以定义一些变量或执行一些操作。当然里面什么都不写也可以。
│   ├── login_fixture.py      # 登录模块 所需夹具函数
│   └── user_fixture.py       # 用户模块 所需夹具函数
├── tests/                    # 测试用例目录
│   ├── conftest.py           # 包含适用于所有测试的通用配置和 fixture
│   ├── __init__.py           # 包初始化文件
│   └── login/				  # login 模块测试目录
│       ├── conftest.py		  # 适用于 login 模块的通用配置和 fixture
│       └── test_login.py     # 测试 login 模块的测试用例
├── app.py                    # 项目启动文件
├── requirements.txt          # 项目依赖项列表
├── pytest.ini            	  # pytest 相关配置,比如自定义 mark 标记
└── README.md                 # 项目说明文档

安装依赖项

安装 pytest :

pip install -U pytest

安装 allure-pytest :

pip install allure-pytest

Python中,requests 是比较好用的请求库,按需安装 :

pip install requests

编辑启动文件

编辑 app.py ,作为项目的启动文件:

import pytest
import sys
import logging
import argparse
from pathlib import Path
import subprocess



# 自定义日志格式
LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s'
logging.basicConfig(level=logging.INFO, format=LOG_FORMAT)
logger = logging.getLogger(__name__)

# 定义pytest配置文件路径
PYTEST_CONFIG_PATH = "pytest.ini"

# 项目根目录路径
parent_dir = Path(__file__).resolve().parent

# 构建 Allure 命令的绝对路径

allure_bin_path = parent_dir / "allure" / "bin" / "allure.bat"


# 运行测试
def run_tests(test_target: str, testprint: str, tb: str, mark: str) -> int:
    """
    运行测试并生成Allure结果数据
    :param test_target: 测试目标路径(支持文件、目录、节点ID如 test_xxx.py::test_func)
    :param testprint: 控制输出信息
    :param tb: traceback 格式
    :param mark: 测试标记
    :return: 退出码
    """
    # 提取实际文件或目录路径(去除 :: 之后的节点名)
    path_part = test_target.split("::")[0]
    if not Path(path_part).exists():
        logger.error(f"测试目标文件或目录不存在: {path_part}")
        return 1

    # 构建pytest参数
    pytest_args = ["--clean-alluredir", "--alluredir=./result"]

    if (parent_dir / PYTEST_CONFIG_PATH).exists():
        pytest_args.extend(["-c", str(parent_dir / PYTEST_CONFIG_PATH)])
    else:
        logger.warning("未找到配置文件,将使用默认配置")

    # 添加输出控制参数
    if testprint:
        pytest_args.append("-" + testprint)
    pytest_args.extend([test_target, "--tb=" + tb])

    if mark:
        pytest_args.extend(["-m", mark])

    try:
        logger.info(f"开始运行测试,目标: {test_target}")
        exit_code = pytest.main(pytest_args)

        exit_messages = {
            0: "✅ 全部测试用例通过",
            1: "⚠️ 部分测试用例未通过",
            2: "❌ 测试过程中有中断或其他非正常终止",
            3: "❌ 内部错误",
            4: "❌ pytest无法找到任何测试用例",
            5: "❌ 命令行解析错误"
        }

        logger.info(exit_messages.get(exit_code, f"❓ 未知的退出码: {exit_code}"))
        return exit_code

    except Exception as e:
        logger.exception("运行测试时发生致命错误:")
        logger.debug("异常详情:", exc_info=True)
        return 1


# 构建参数解析器
def parse_arguments() -> argparse.Namespace:
    """
    解析命令行参数:
    test_target:测试目标路径
    testprint:控制输出信息,默认为空;-p + 参数
               可输入参数有"", "v", "ra", "rA", "rp", "rx", "rs", "rN", "vra", "vrA", "vrp", "vrx", "vrs", "vrN"
               具体作用参考 pytest 官方文档
    tb:控制输出信息,默认为 auto;-tb + 参数
                可输入参数有"auto", "long", "short", "line", "native", "no"
                具体作用参考 pytest 官方文档
    mark:测试标记,默认为空;-m + 参数;参数为 pytest.ini 中自定义的 mark 标记
    :return: 解析后的参数
    """
    parser = argparse.ArgumentParser(description="使用指定的命令运行 pytest 测试")

    # 指定测试目录、文件、测试组或测试用例,默认为 tests/
    parser.add_argument(
        'test_target',
        nargs='?',
        type=str,
        default="tests/",
        help='指定测试目录文件 (默认: tests/)'
    )

    # 添加 pytest 参数 testprint、 tb
    parser.add_argument(
        '-p', '--testprint',
        nargs='?',
        type=str,
        default="",
        choices=["", "v", "ra", "rA", "rp", "rx", "rs", "rN", "vra", "vrA", "vrp", "vrx", "vrs", "vrN"],
        help='控制测试输出信息(例如:v=详细输出,ra=记录所有测试结果,详细选项参考 pytest 文档)'
    )
    parser.add_argument(
        '-tb', '--tb',
        nargs='?',
        type=str,
        default="auto",
        choices=["auto", "long", "short", "line", "native", "no"],
        help='控制 traceback 输出格式(例如:auto=自动选择,long=详细,short=简短)'
    )
    parser.add_argument(
        '-m', '--mark',
        nargs='?',
        type=str,
        help='指定测试标记(例如:smoke=运行标记为smoke的测试用例)'
    )
    return parser.parse_args()


# 生成测试结果报告
def allure_report():
    """
    使用 Allure 生成测试报告
    """
    if not allure_bin_path.exists():
        logger.error(f"Allure 可执行文件不存在: {allure_bin_path}")
        return

    result_dir = parent_dir / "result"
    if not result_dir.exists():
        logger.error("测试结果目录不存在,无法生成报告")
        return

    try:
        logger.info("开始生成 Allure 测试报告")
        allure_command = [
            str(allure_bin_path),
            "generate",
            str(result_dir),
            "-o", "report",
            "--clean"
        ]
        result = subprocess.run(
            allure_command,
            check=True,
            text=True,
            capture_output=True,
            cwd=str(parent_dir)
        )
        logger.info("Allure 报告成功生成至 report 目录")
        logger.debug(result.stdout)
    except subprocess.CalledProcessError as e:
        logger.error(f"生成 Allure 报告时出错: {e}")
        logger.error(e.stderr)
        logger.debug(e.stdout)


# 主函数
def main():
    args = parse_arguments()
    exit_code = run_tests(args.test_target, args.testprint, args.tb, args.mark)
    allure_report()
    sys.exit(exit_code)


if __name__ == "__main__":
    main()

使用说明:

python .\app.py 测试目标路径 -p 参数 -tb 参数 -m 自定义mark标记

其中:

  • 测试目标路径:默认为 tests/,这个参数可以指定要运行的范围,目录下、测试组下、测试用例。

    比如: python .\app.py tests\test_mod.py::test_list_fail

  • -p:默认为空,这个参数可以控制打印台输出信息的详细程度。

    比如:python .\app.py -p v

  • -tb:默认为 auto,这个参数可以控制打印台输出信息的详细程度。

    比如:python .\app.py -tb short

  • -m:默认为空,这个参数可以运行特定标记的测试用例。

    比如:python .\app.py -m smoke


THEEND



© 转载需要保留原始链接,未经明确许可,禁止商业使用。CC BY-NC-ND 4.0