pytest测试框架搭建难吗?

pytest 是一个非常灵活且强大的测试框架,它支持简单的单元测试到复杂的功能测试。显著特点是其简洁的语法,可以无需继承 TestCase 类直接使用函数来编写测试用例,并通过 assert语句 进行断言。还支持参数化测试、丰富的插件系统

pytest自动化测试框架搭建

本系列探讨一下如何基于 pytest 构建自动化测试套件

PS: 本文基于pytest 8.3.3

安装 pytest

在终端中安装:

pip install -U pytest

测试用例和断言

pytest 的设计思想围绕测试用例的发现、执行、管理和报告展开,其灵活性和可扩展性也主要服务于测试用例的高效编写和维护。

测试用例需要编写在前缀为名称 test_ 或 test 的模块(.py文件)中,作为测试用例的函数的前缀也需为 test 或 _test。每个测试用例中都需要至少一个断言(assert)。比如:

# test_mod.py

list_list = 'hello!'


def test_list_fail():
    assert 'hello1!' == list_list, "两个值不同"


def test_list_pass():
    assert 'hello!' == list_list, "两个值不同"

test_list_fail 和 test_list_pass 皆是测试用例。

assert 后接两个参数,第一个值用于判断,第二个值是字符串。第一个值为 True(真) ,则该测试用例继续进行直至结束;如果为 False(假) ,则测试失败,并且 pytest 会停止执行该测试用例、报告错误并打印第二个值。

第一个值可以是表达式、函数返回值、数值、布尔值等等,第二个值可以自定义信息,精准描述问题。

test_list_fail 和 test_list_pass 两个测试用例的结果如下:

============================= test session starts =============================
collecting ... collected 2 items

test_test.py::test_list_fail FAILED                                      [ 50%]
test_test.py:3 (test_list_fail)
'hello1!' != 'hello!'

预期:'hello!'
实际:'hello1!'
<点击以查看差异>

def test_list_fail():
>       assert 'hello1!' == list_list, "两个值不同"
E       AssertionError: 两个值不同
E       assert 'hello1!' == 'hello!'
E         
E         - hello!
E         + hello1!
E         ?      +

test_test.py:5: AssertionError

test_test.py::test_list_pass PASSED                                      [100%]

========================= 1 failed, 1 passed in 0.05s =========================

test_mod.py::test_list_fail FAILED 表示测试用例 test_list_fail 断言失败,测试用例未通过 。

test_mod.py::test_list_pass PASSED 表示测试用例 test_hanshu、test_list 断言成功,测试用例通过。

做下补充,第一个值可以如下:

  1. 比较表达式
# 等于
assert 10 == 5  # False

# 不等于
assert 10 != 5  # True

# 大于
assert 10 > 5   # True

# 小于
assert 10 < 5   # False

# 大于等于
assert 10 >= 5  # True

# 小于等于
assert 10 <= 5  # False
  1. 逻辑表达式
# 逻辑与
assert (10 > 5) and (5 < 10)  # True

# 逻辑或
assert (10 > 5) or (5 > 10)   # True

# 逻辑非
assert not (10 > 5)           # False
  1. 成员表达式
# in 操作符
assert 5 in [1, 2, 3, 4, 5]  # True

# not in 操作符
assert 6 not in [1, 2, 3, 4, 5]  # True
  1. 身份表达式
a = [1, 2, 3]
b = [1, 2, 3]
c = a

# is 操作符
assert a is c  # True,因为 a 和 c 引用同一个对象

# is not 操作符
assert a is not b  # True,因为 a 和 b 是不同的对象
  1. 布尔值的隐式转换
# 空列表、空字符串、零等会被视为 False,而非空列表、非空字符串、非零等会被视为 True

# 空列表
assert []  # False

# 非空列表
assert [1, 2, 3]  # True

# 空字符串
assert ""  # False

# 非空字符串
assert "Hello"  # True

# 零
assert 0  # False

# 非零
assert 10  # True
  1. 布尔值
# True
assert True

# False
assert False

测试用例组

应用有许多模块,每个模块的测试用例我们都可以写在对应模块文件里。模块又有很多功能,我们又如何对功能进行划分呢?

pytest 框架里有测试组概念,用类(class)表达,可以在类里编写功能的所有测试用例。

# C:\PythonTest\Test\test_mod.py

class AddUser:
    def test_smoke(self):
        x = "this"
        assert "h" in x, "字符串中没有 h"

    def test_error(self):
        x = 1
        y = 2
        assert type(x) == type(y), "x 不等于 y"

运行结果:

========================= 1 failed, 1 passed in 0.05s =========================
PASSED                             [ 50%]FAILED                             [100%]
test_test.py:5 (AddUser.test_error)
1 != 2

预期:2
实际:1
<点击以查看差异>

self = <test_test.AddUser object at 0x000001E3ADA52C10>

    def test_error(self):
        x = 1
        y = 2
>       assert x == y, "x 不等于 y"
E       AssertionError: x 不等于 y
E       assert 1 == 2

test_test.py:9: AssertionError

还可以添加 DeleteUser、EditUser 等等测试组。

运行测试用例

编写测试用例后,可以在命令行中运行测试用例。以 Windows 为例:

  1. 终端中运行 C:\PythonTest\Test\test_mod.py 中所有测试用例:
pytest C:\PythonTest\Test\test_mod.py
  1. 终端中运行 C:\PythonTest\Test\test_mod.py 中的 test_list_fail 测试用例:
pytest C:\PythonTest\Test\test_mod.py::test_list_fail
  1. 在终端中运行 C:\PythonTest\Test\test_mod.py 中的 TestAdd 测试用例组:
pytest C:\PythonTest\Test\test_mod.py::TestClassOne
pytest test_mod.py::TestClassOne
  1. 在终端中运行 C:\PythonTest\Test\test_mod.py 中 TestAdd 中的 test_error 测试用例:***
pytest C:\PythonTest\Test\test_mod.py::TestClassOne::test_error

初步构建测试套件

结合本章内容,我们可以初步构建一个测试套件。目录结构可以如下:

Project/
│
├── Package/                  # 程序目录
│   ├── __init__.py           # 包初始化文件,可以定义一些变量或执行一些操作。当然里面什么都不写也可以。
│   ├── module1.py            # 公共模块,比如连接数据库操作数据,接口请求等操作,推荐按功能封装成类
│   └── module2.py            # 公共模块,比如连接数据库操作数据,接口请求等操作,推荐按功能封装成类
│
├── Test/                     # 测试用例目录
│   ├── __init__.py           # 包初始化文件
│   ├── test_module1.py       # 测试 module1 的测试用例
│   └── test_module2.py       # 测试 module2 的测试用例
├── app.py                    # 项目启动文件
├── requirements.txt          # 项目依赖项列表
└── README.md                 # 项目说明文档

一个完整的套件,需要有个启动文件 app.py ,测试时运行 app.py 文件。

app.py 文件可以这样设计:

import pytest
import sys
import logging
import argparse
import os

# 自定义日志格式
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__)

def run_tests(test_target: str)->int::
    """
    运行pytest测试并返回退出码

    Args:
        test_target (str): 测试目标路径

    Returns:
        int: pytest退出码
    """
    if not os.path.exists(test_target):
        logger.error(f"测试目标路径不存在: {test_target}")
        return 1

    # 构建pytest参数
    pytest_args = ["-v", test_target]


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

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

        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:
    """解析命令行参数"""
    parser = argparse.ArgumentParser(description="使用指定的命令运行 pytest 测试")
    parser.add_argument(
        'test_target', 
        nargs='?', 
        type=str, 
        default="Test/",
        help='指定测试目录/文件 (默认: Test/)'
    )
    return parser.parse_args()

if __name__ == "__main__":
    args = parse_arguments()

    exit_code = run_tests(args.test_target)
    sys.exit(exit_code)

项目根目录下,可以这样运行测试:

  • 运行所有测试:
python app.py
  • 运行特定文件:
python app.py Test/test_module1.py
  • 运行特定模块文件中的特定测试用例:
python app.py Test/test_module1.py::test_case
  • 运行特定文件中的特定测试组:
python app.py Test/test_module1.py::TestClass
  • 运行特定文件中的特定测试组中的特定测试用例:
python app.py Test/test_module1.py::TestClass::test_case

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值