01.pytest的用例发现规则

Pytest 用例发现规则详解

1. 什么是用例发现

1.1 基本概念

用例发现(Test Discovery) 是 pytest 自动查找和识别测试用例的过程。当你运行 pytest 命令时,pytest 会自动扫描指定的目录,找到所有符合规则的测试文件,然后在这些文件中查找所有符合规则的测试函数和测试类。

1.2 为什么需要用例发现

  • 自动化:不需要手动指定每个测试文件
  • 高效:一次命令运行所有测试
  • 规范:统一的命名规则让测试更易管理
  • 灵活:可以运行整个项目或指定目录的测试

1.3 发现流程

运行 pytest 命令
    ↓
扫描指定目录(默认当前目录)
    ↓
查找符合命名规则的文件
    ↓
在文件中查找符合命名规则的类和函数
    ↓
收集所有测试用例
    ↓
执行测试用例

2. Pytest 发现机制原理

2.1 默认发现规则

Pytest 使用以下默认规则来发现测试用例:

  1. 文件规则:文件名必须以 test_ 开头或以 _test 结尾
  2. 函数规则:函数名必须以 test_ 开头
  3. 类规则:类名必须以 Test 开头(不能有 __init__ 方法)
  4. 目录规则:目录名不能是 Python 保留字,不能以 . 开头

2.2 发现顺序

Pytest 按照以下顺序发现测试:

  1. 首先扫描当前目录(或指定目录)
  2. 递归扫描子目录
  3. 按照文件名的字母顺序处理文件
  4. 在文件中按照代码顺序处理类和函数

2.3 示例:基本发现过程

假设你有以下目录结构:

project/
├── test_login.py
├── test_user.py
└── api/
    └── test_api.py

运行 pytest 时:

  1. 发现 test_login.py
  2. 发现 test_user.py
  3. 发现 api/test_api.py
  4. 在每个文件中查找 test_ 开头的函数
  5. 收集所有测试用例并执行

3. 文件命名规则

3.1 规则说明

Pytest 会识别以下两种命名格式的测试文件:

规则 1:以 test_ 开头

  • test_login.py
  • test_user_management.py
  • test_api_v1.py
  • login_test.py(不符合此规则)
  • test.py(不符合,必须是 test_ 开头)

规则 2:以 _test 结尾

  • login_test.py
  • user_management_test.py
  • api_v1_test.py
  • testlogin.py(不符合此规则)
  • _test.py(不符合,必须有前缀)

3.2 详细示例

示例 1:正确的文件命名

# 文件名:test_login.py
def test_user_login():
    assert 1 + 1 == 2

def test_admin_login():
    assert 2 + 2 == 4

运行结果:

$ pytest test_login.py
# 会找到 2 个测试用例

示例 2:另一种正确的命名方式

# 文件名:login_test.py
def test_user_login():
    assert 1 + 1 == 2

运行结果:

$ pytest login_test.py
# 会找到 1 个测试用例

示例 3:错误的文件命名

# 文件名:login.py(不符合规则)
def test_user_login():
    assert 1 + 1 == 2

运行结果:

$ pytest login.py
# 不会发现任何测试用例,除非使用 pytest login.py::test_user_login 明确指定

3.3 文件命名最佳实践

推荐方式:使用 test_ 前缀

# ✅ 推荐
test_login.py
test_user_management.py
test_api_v1.py
test_integration.py

# ⚠️ 可用但不推荐
login_test.py
user_management_test.py

原因

  • test_ 前缀更直观,一眼就能看出是测试文件
  • 在文件浏览器中,测试文件会聚集在一起(按字母排序)
  • 符合大多数 Python 项目的约定

4. 测试函数命名规则

4.1 基本规则

函数名必须以 test_ 开头

这是 pytest 识别测试函数的核心规则。

4.2 正确示例

# test_example.py

# ✅ 正确:以 test_ 开头
def test_login():
    assert True

def test_user_registration():
    assert True

def test_api_get_user():
    assert True

def test_计算器加法():  # 支持中文
    assert 1 + 1 == 2

def test_login_with_valid_credentials():
    assert True

def test_123():  # 数字也可以
    assert True

4.3 错误示例

# test_example.py

# ❌ 错误:不以 test_ 开头
def login():
    assert True  # pytest 不会发现这个函数

def check_user():
    assert True  # pytest 不会发现这个函数

# ⚠️ 注意:testlogin() 在某些 pytest 版本中可能被发现
# 但这不是标准规则,不推荐使用
def testlogin():  # 缺少下划线,不符合标准规则
    assert True  # 某些版本的 pytest 可能会发现,但不推荐

# ❌ 错误:在类中但类名不符合规则
class UserTest:  # 类名应该是 TestUser
    def test_login(self):
        assert True  # 不会被执行

重要说明:关于 testlogin() 的发现规则

虽然 pytest 的官方标准规则要求函数名必须以 test_ 开头(带下划线),但在实际使用中:

  1. 某些 pytest 版本或配置可能会发现 test 开头的函数(不带下划线)

    • 这可能是因为 pytest 使用了更宽松的匹配规则
    • 或者项目中有自定义的 pytest.ini 配置
  2. 不推荐使用 testlogin() 这种命名方式,因为:

    • 不符合官方规范(官方要求 test_ 开头)
    • 可能在不同版本或配置下行为不一致
    • 可读性较差,容易与 test_login() 混淆
    • 可能导致团队协作时的困惑
  3. 如何验证你的环境

    # 查看 pytest 的配置
    pytest --collect-only
    
    # 查看 pytest 版本
    pytest --version
    
    # 查看 pytest.ini 配置(如果有)
    cat pytest.ini

推荐做法:始终使用 test_ 开头(带下划线),这是最可靠和标准的做法。

实际测试示例

# test_demo.py
def test_user_login():  # ✅ 标准写法,肯定会被发现
    assert 1 + 1 == 2

def testlogin():  # ⚠️ 非标准写法,可能被发现,但不推荐
    assert 2 + 2 == 4

运行 pytest 后,如果两个函数都被发现,说明你的环境支持更宽松的规则,但为了代码的可移植性和规范性,建议统一使用 test_ 开头的命名方式。

4.4 函数命名最佳实践

实践 1:描述性命名

# ✅ 好的命名:清晰描述测试内容
def test_user_login_with_valid_credentials():
    """测试用户使用有效凭据登录"""
    pass

def test_user_login_with_invalid_password():
    """测试用户使用无效密码登录"""
    pass

def test_user_login_with_empty_username():
    """测试用户名为空时的登录"""
    pass

# ❌ 不好的命名:不够描述性
def test_login1():
    pass

def test_login2():
    pass

def test_1():
    pass

实践 2:使用下划线分隔

# ✅ 推荐:使用下划线
def test_user_can_login():
    pass

def test_admin_can_delete_user():
    pass

# ⚠️ 可用但不推荐:驼峰命名
def testUserCanLogin():
    pass  # 虽然能运行,但不符合 Python 命名规范

实践 3:组织相关测试

# test_user.py

def test_user_login_success():
    """测试登录成功"""
    pass

def test_user_login_failure():
    """测试登录失败"""
    pass

def test_user_logout():
    """测试登出"""
    pass

def test_user_profile_update():
    """测试更新用户资料"""
    pass

4.5 参数化测试函数

参数化测试函数也需要以 test_ 开头:

import pytest

# ✅ 正确:参数化函数也要以 test_ 开头
@pytest.mark.parametrize("username,password", [
    ("admin", "123456"),
    ("user", "password"),
])
def test_login_with_different_users(username, password):
    assert username is not None
    assert password is not None

5. 测试类命名规则

5.1 基本规则

类名必须以 Test 开头,且类中不能有 __init__ 方法

重要说明

  • 如果测试类中有 __init__ 方法,pytest 无法收集该测试类
  • 运行 pytest 时会显示 PytestCollectionWarning 警告
  • 最终会收集到 0 个测试项
  • 需要使用 setup_methodsetup_classfixture 来替代 __init__ 进行初始化

5.2 正确示例

# test_example.py

# ✅ 正确:类名以 Test 开头
class TestLogin:
    def test_user_login(self):
        assert True

    def test_admin_login(self):
        assert True

# ✅ 正确:Test 后面可以有其他字符
class TestUserManagement:
    def test_create_user(self):
        assert True

    def test_delete_user(self):
        assert True

# ✅ 正确:可以有多个测试类
class TestAPI:
    def test_get_request(self):
        assert True

class TestDatabase:
    def test_connection(self):
        assert True

5.3 错误示例

# test_example.py

# ❌ 错误:类名不以 Test 开头
class LoginTest:  # 应该是 TestLogin
    def test_user_login(self):
        assert True  # pytest 不会发现这个测试

# ❌ 错误:类名是 test(小写)
class testLogin:  # 应该是 TestLogin
    def test_user_login(self):
        assert True  # pytest 不会发现这个测试

# ❌ 错误:类中有 __init__ 方法
class TestLogin:
    def __init__(self):  # 不能有 __init__
        self.username = "admin"

    def test_user_login(self):
        assert True  # pytest 不会发现这个测试
        # 运行 pytest 时会显示警告:
        # PytestCollectionWarning: cannot collect test class 'TestLogin' 
        # because it has a __init__ constructor
        # collected 0 items

# ❌ 错误:类名不以 Test 开头,即使函数名正确
class UserTest:
    def test_login(self):
        assert True  # pytest 不会发现这个测试

5.4 类中方法的命名规则

重要:类中的测试方法也必须以 test_ 开头!

class TestLogin:
    # ✅ 正确:方法名以 test_ 开头
    def test_user_login(self):
        assert True

    # ✅ 正确:可以有多个测试方法
    def test_admin_login(self):
        assert True

    # ❌ 错误:方法名不以 test_ 开头
    def user_login(self):
        assert True  # pytest 不会发现这个方法

5.5 类的继承

测试类可以继承其他类,但必须遵循命名规则:

# ✅ 正确:继承的基类可以是普通类
class BaseTest:
    def setup(self):
        pass

class TestLogin(BaseTest):
    def test_user_login(self):
        assert True

# ✅ 正确:可以继承其他测试类
class TestBase:
    def setup(self):
        pass

class TestLogin(TestBase):
    def test_user_login(self):
        assert True

5.6 类命名最佳实践

实践 1:类名应该描述测试范围

# ✅ 好的命名
class TestUserLogin:
    """测试用户登录相关功能"""
    def test_login_success(self):
        pass

    def test_login_failure(self):
        pass

class TestUserRegistration:
    """测试用户注册相关功能"""
    def test_register_success(self):
        pass

# ❌ 不好的命名
class Test1:
    def test_login(self):
        pass

class TestA:
    def test_register(self):
        pass

实践 2:一个类组织相关的测试

# test_user.py

class TestUserLogin:
    """所有登录相关的测试放在一个类中"""
    def test_login_with_valid_credentials(self):
        pass

    def test_login_with_invalid_password(self):
        pass

    def test_login_with_empty_username(self):
        pass

class TestUserProfile:
    """所有用户资料相关的测试放在一个类中"""
    def test_update_profile(self):
        pass

    def test_get_profile(self):
        pass

6. 目录结构规则

6.1 基本规则

  1. 目录名不能是 Python 保留字
  2. 目录名不能以 . 开头(隐藏目录)
  3. 目录中的文件仍需遵循文件命名规则

6.2 目录结构示例

示例 1:扁平结构

project/
├── test_login.py
├── test_user.py
├── test_api.py
└── conftest.py

示例 2:按模块组织

project/
├── tests/
│   ├── test_login.py
│   ├── test_user.py
│   └── api/
│       ├── test_user_api.py
│       └── test_order_api.py
└── conftest.py

示例 3:按功能组织

project/
├── tests/
│   ├── unit/
│   │   ├── test_user_model.py
│   │   └── test_order_model.py
│   ├── integration/
│   │   ├── test_user_flow.py
│   │   └── test_order_flow.py
│   └── api/
│       ├── test_user_api.py
│       └── test_order_api.py
└── conftest.py

6.3 目录中的文件命名

重要:无论文件在哪个目录,都必须遵循文件命名规则!

# ✅ 正确:tests/api/test_user_api.py
def test_get_user():
    assert True

# ✅ 正确:tests/unit/test_user_model.py
def test_user_creation():
    assert True

# ❌ 错误:tests/api/user_api.py(不符合命名规则)
def test_get_user():
    assert True  # pytest 不会发现这个文件

6.4 conftest.py 文件

conftest.py 是一个特殊的文件,用于存放 pytest 的配置和共享的 fixture。

规则

  • 文件名必须是 conftest.py(固定名称)
  • 可以放在任何目录中
  • 该目录及其子目录的测试都可以使用其中的 fixture

示例

project/
├── conftest.py          # 项目级别的 fixture
├── tests/
│   ├── conftest.py      # tests 目录级别的 fixture
│   ├── test_login.py
│   └── api/
│       ├── conftest.py  # api 目录级别的 fixture
│       └── test_api.py

6.5 运行指定目录的测试

# 运行 tests 目录下的所有测试
pytest tests/

# 运行 tests/api 目录下的所有测试
pytest tests/api/

# 运行 tests/unit 目录下的所有测试
pytest tests/unit/

7. 配置文件 pytest.ini

7.1 什么是 pytest.ini

pytest.ini 是 pytest 的配置文件,可以自定义测试发现规则和其他配置。

重要格式要求

  • pytest.ini 文件必须[pytest] 节头开始
  • 所有配置项都必须在 [pytest] 节下
  • 如果没有 [pytest] 节头,会报错:ERROR: no section header defined

7.2 基本配置示例

✅ 正确格式

[pytest]
# 指定测试文件匹配模式
python_files = test_*.py *_test.py

# 指定测试类匹配模式
python_classes = Test*

# 指定测试函数匹配模式
python_functions = test_*

# 指定测试目录
testpaths = tests

# 其他配置
addopts = -v --tb=short

7.3 自定义文件命名规则

示例 1:只接受 test_ 开头的文件

[pytest]
python_files = test_*.py

效果:

  • test_login.py – 会被发现
  • login_test.py – 不会被发现

示例 2:自定义文件命名模式

[pytest]
python_files = test_*.py check_*.py

效果:

  • test_login.py – 会被发现
  • check_login.py – 会被发现
  • login_test.py – 不会被发现

7.4 自定义类命名规则

[pytest]
python_classes = Test* Check*

效果:

# ✅ 会被发现
class TestLogin:
    def test_login(self):
        pass

class CheckUser:
    def test_user(self):
        pass

# ❌ 不会被发现
class LoginTest:
    def test_login(self):
        pass

7.5 自定义函数命名规则

⚠️ 重要提醒:配置项必须[pytest] 节头下,否则会报错!

错误示例(会导致报错):

# ❌ 错误:缺少 [pytest] 节头
python_functions = test_* login_*
# 运行 pytest 时会报错:
# ERROR: pytest.ini:1: no section header defined

正确示例

# ✅ 正确:必须有 [pytest] 节头
[pytest]
python_functions = test_* login_*

效果:

# ✅ 会被发现
def test_login():
    pass

def login_user():
    pass

# ❌ 不会被发现
def check_user():
    pass

更多示例

[pytest]
# 允许 test_ 和 check_ 开头的函数
python_functions = test_* check_*

效果:

# ✅ 会被发现
def test_login():
    pass

def check_user():
    pass

# ❌ 不会被发现
def login_test():
    pass

7.6 指定测试目录

[pytest]
testpaths = tests unit_tests

效果:pytest 会优先在这些目录中查找测试文件。

7.7 完整配置示例

[pytest]
# 测试文件匹配模式
python_files = test_*.py

# 测试类匹配模式
python_classes = Test*

# 测试函数匹配模式
python_functions = test_*

# 测试目录
testpaths = tests

# 命令行选项
addopts = 
    -v
    --tb=short
    --strict-markers
    --disable-warnings

# 标记定义
markers =
    slow: 标记为慢速测试
    integration: 标记为集成测试
    smoke: 标记为冒烟测试

8. 实际示例演示

8.1 示例 1:基本测试文件

创建文件 test_basic.py

# test_basic.py

def test_addition():
    """测试加法"""
    assert 1 + 1 == 2

def test_subtraction():
    """测试减法"""
    assert 5 - 3 == 2

def test_multiplication():
    """测试乘法"""
    assert 2 * 3 == 6

运行:

$ pytest test_basic.py

输出:

test_basic.py::test_addition PASSED
test_basic.py::test_subtraction PASSED
test_basic.py::test_multiplication PASSED

发现结果:pytest 发现了 3 个测试用例。

8.2 示例 2:测试类

创建文件 test_user.py

# test_user.py

class TestUser:
    def test_create_user(self):
        """测试创建用户"""
        assert True

    def test_delete_user(self):
        """测试删除用户"""
        assert True

class TestUserLogin:
    def test_login_success(self):
        """测试登录成功"""
        assert True

    def test_login_failure(self):
        """测试登录失败"""
        assert True

运行:

$ pytest test_user.py -v

输出:

test_user.py::TestUser::test_create_user PASSED
test_user.py::TestUser::test_delete_user PASSED
test_user.py::TestUserLogin::test_login_success PASSED
test_user.py::TestUserLogin::test_login_failure PASSED

发现结果:pytest 发现了 2 个测试类,共 4 个测试用例。

8.3 示例 3:混合情况

创建文件 test_mixed.py

# test_mixed.py

# 独立的测试函数
def test_function1():
    assert True

def test_function2():
    assert True

# 测试类
class TestClass1:
    def test_method1(self):
        assert True

    def test_method2(self):
        assert True

# 普通函数(不会被发现)
def helper_function():
    return "helper"

# 普通类(不会被发现)
class HelperClass:
    def helper_method(self):
        return "helper"

# 另一个测试类
class TestClass2:
    def test_method3(self):
        assert True

运行:

$ pytest test_mixed.py -v

输出:

test_mixed.py::test_function1 PASSED
test_mixed.py::test_function2 PASSED
test_mixed.py::TestClass1::test_method1 PASSED
test_mixed.py::TestClass1::test_method2 PASSED
test_mixed.py::TestClass2::test_method3 PASSED

发现结果

  • ✅ 发现了 2 个独立测试函数
  • ✅ 发现了 2 个测试类中的 3 个测试方法
  • ❌ 忽略了 helper_functionHelperClass(不符合命名规则)

8.4 示例 4:目录结构

创建以下目录结构:

project/
├── test_root.py
├── tests/
│   ├── test_module1.py
│   ├── test_module2.py
│   └── subdirectory/
│       └── test_sub.py

文件内容:

# test_root.py
def test_root():
    assert True

# tests/test_module1.py
def test_module1():
    assert True

# tests/test_module2.py
def test_module2():
    assert True

# tests/subdirectory/test_sub.py
def test_sub():
    assert True

运行:

$ pytest

输出:

test_root.py::test_root PASSED
tests/test_module1.py::test_module1 PASSED
tests/test_module2.py::test_module2 PASSED
tests/subdirectory/test_sub.py::test_sub PASSED

发现结果:pytest 递归扫描了所有目录,发现了 4 个测试用例。

8.5 示例 5:不符合规则的示例

创建文件 wrong_naming.py

# wrong_naming.py(文件名不符合规则)

def test_function():
    """这个函数符合规则,但文件不符合"""
    assert True

class TestClass:
    def test_method(self):
        """这个方法符合规则,但文件不符合"""
        assert True

def login_test():
    """函数名不符合规则"""
    assert True

class LoginTest:
    """类名不符合规则"""
    def test_method(self):
        assert True

运行:

$ pytest wrong_naming.py

输出:

# 不会发现任何测试用例,因为文件名不符合规则

解决方法 1:重命名文件

$ mv wrong_naming.py test_wrong_naming.py
$ pytest test_wrong_naming.py

解决方法 2:明确指定测试

$ pytest wrong_naming.py::test_function
$ pytest wrong_naming.py::TestClass::test_method

8.6 示例 6:使用 pytest.ini 自定义规则

创建 pytest.ini

[pytest]
python_files = test_*.py check_*.py
python_classes = Test* Check*
python_functions = test_* check_*

创建文件 check_custom.py

# check_custom.py(使用自定义规则)

def check_function():
    """使用自定义函数命名规则"""
    assert True

class CheckClass:
    def check_method(self):
        """使用自定义类和方法命名规则"""
        assert True

运行:

$ pytest check_custom.py -v

输出:

check_custom.py::check_function PASSED
check_custom.py::CheckClass::check_method PASSED

发现结果:由于配置了自定义规则,pytest 发现了这些测试。


9. 常见问题与解决方案

9.1 问题 1:pytest 找不到我的测试用例

症状

$ pytest
# 输出:no tests ran

可能原因和解决方案

原因 1:文件名不符合规则

# ❌ 错误:login.py
def test_login():
    assert True

解决:重命名文件

# ✅ 正确:test_login.py
def test_login():
    assert True

原因 2:函数名不符合规则

# test_login.py
# ❌ 错误
def login():
    assert True

解决:重命名函数

# ✅ 正确
def test_login():
    assert True

原因 3:类名不符合规则

# test_login.py
# ❌ 错误
class LoginTest:
    def test_login(self):
        assert True

解决:重命名类

# ✅ 正确
class TestLogin:
    def test_login(self):
        assert True

9.2 问题 2:类中的测试方法没有被发现

症状

class TestLogin:
    def login(self):  # 方法名不以 test_ 开头
        assert True

解决

class TestLogin:
    def test_login(self):  # 方法名必须以 test_ 开头
        assert True

9.3 问题 3:类中有 init 方法导致测试不被发现

症状

class TestLogin:
    def __init__(self):
        self.username = "admin"

    def test_login(self):
        assert True  # 这个测试不会被发现

运行 pytest 时的表现

$ pytest
collected 0 items

=== 1 warning in 0.01s ===
PytestCollectionWarning: cannot collect test class 'TestLogin' because it has a __init__ constructor

原因说明

  • pytest 无法收集带有 __init__ 方法的测试类
  • 会显示 PytestCollectionWarning 警告
  • 最终收集到 0 个测试项

解决:使用 fixture 或 setup_method

方法 1:使用 fixture(推荐)

import pytest

class TestLogin:
    @pytest.fixture(autouse=True)
    def setup(self):
        """自动执行的 fixture,用于初始化"""
        self.username = "admin"
        yield  # 可选:清理代码可以放在 yield 之后
        # 清理代码(如果有)

    def test_login(self):
        assert self.username == "admin"

方法 2:使用 setup_method(简单场景)

class TestLogin:
    def setup_method(self):
        """pytest 会在每个测试方法执行前自动调用这个方法"""
        self.username = "admin"

    def test_login(self):
        assert self.username == "admin"

方法 3:使用 setup_class(类级别初始化)

class TestLogin:
    @classmethod
    def setup_class(cls):
        """pytest 会在类中所有测试执行前调用一次"""
        cls.shared_data = "admin"

    def test_login(self):
        assert self.shared_data == "admin"

对比说明

  • __init__:❌ pytest 无法收集测试类
  • setup_method:✅ 每个测试方法执行前调用
  • setup_class:✅ 整个类执行前调用一次
  • fixture:✅ 最灵活,可以控制作用域和自动执行

9.4 问题 4:子目录中的测试没有被发现

症状

project/
├── tests/
│   └── api/
│       └── test_api.py  # 这个文件没有被发现

可能原因:目录中有 __init__.py 文件,但 pytest 配置不正确。

解决:确保 pytest 能扫描到子目录

# 明确指定目录
pytest tests/

# 或者配置 pytest.ini
[pytest]
testpaths = tests

9.5 问题 5:如何查看 pytest 发现了哪些测试

方法 1:使用 --collect-only 参数

$ pytest --collect-only

输出示例:

<Module test_login.py>
  <Function test_user_login>
  <Function test_admin_login>
<Module test_user.py>
  <Class TestUser>
    <Function test_create_user>
    <Function test_delete_user>

方法 2:使用 -v 参数查看详细信息

$ pytest -v

9.6 问题 6:如何只运行特定的测试

运行单个文件

$ pytest test_login.py

运行单个测试函数

$ pytest test_login.py::test_user_login

运行单个测试类

$ pytest test_user.py::TestUser

运行类中的单个方法

$ pytest test_user.py::TestUser::test_create_user

使用模式匹配

# 运行所有包含 "login" 的测试
$ pytest -k login

# 运行所有包含 "user" 但不包含 "admin" 的测试
$ pytest -k "user and not admin"

9.7 问题 7:pytest.ini 配置不生效或报错

错误 1:缺少 [pytest] 节头

症状

$ pytest
ERROR: D:pythonpytest.ini:1: no section header defined

原因pytest.ini 文件缺少必需的 [pytest] 节头。

错误示例

# ❌ 错误:缺少 [pytest] 节头
python_functions = test_* login_*

解决方法

# ✅ 正确:必须有 [pytest] 节头
[pytest]
python_functions = test_* login_*

错误 2:配置不生效

可能原因

  1. 文件位置不对(应该在项目根目录)
  2. 文件格式错误(缺少 [pytest] 节头)
  3. 配置项拼写错误
  4. 配置项缩进错误(不应该有缩进)

检查方法

# 查看 pytest 的配置
$ pytest --collect-only -v

# 查看 pytest 版本
$ pytest --version

正确的位置和格式

project/
├── pytest.ini  # 应该在项目根目录
├── tests/
│   └── test_example.py

正确的格式

[pytest]  # 必须有这个节头
python_files = test_*.py
python_classes = Test*
python_functions = test_*

常见格式错误

# ❌ 错误1:缺少节头
python_functions = test_*

# ❌ 错误2:节头拼写错误
[pytest]  # 正确
[pyest]   # 错误

# ❌ 错误3:配置项有缩进
[pytest]
    python_functions = test_*  # 错误:不应该有缩进

# ✅ 正确:配置项没有缩进
[pytest]
python_functions = test_*  # 正确

10. 最佳实践建议

10.1 文件命名建议

  1. 统一使用 test_ 前缀

    # ✅ 推荐
    test_login.py
    test_user_management.py
    test_api_v1.py
  2. 文件名应该描述测试内容

    # ✅ 好的命名
    test_user_login.py
    test_order_creation.py
    test_api_authentication.py
    
    # ❌ 不好的命名
    test1.py
    test_a.py
    test.py
  3. 使用下划线分隔单词

    # ✅ 推荐
    test_user_login.py
    test_api_v1.py
    
    # ⚠️ 不推荐
    testUserLogin.py
    test-api-v1.py

10.2 函数命名建议

  1. 使用描述性的名称

    # ✅ 好的命名
    def test_user_login_with_valid_credentials():
       pass
    
    def test_user_login_with_invalid_password():
       pass
    
    # ❌ 不好的命名
    def test_login1():
       pass
    
    def test_login2():
       pass
  2. 遵循测试命名模式:test_

    # 模式:test_<对象>_<场景>_<结果>
    def test_user_login_with_valid_credentials_should_succeed():
       pass
    
    def test_user_login_with_invalid_password_should_fail():
       pass
  3. 使用下划线,不要使用驼峰命名

    # ✅ 推荐
    def test_user_can_login():
       pass
    
    # ⚠️ 不推荐
    def testUserCanLogin():
       pass

10.3 类命名建议

  1. 类名应该描述测试范围

    # ✅ 好的命名
    class TestUserLogin:
       pass
    
    class TestOrderManagement:
       pass
    
    # ❌ 不好的命名
    class Test1:
       pass
    
    class TestA:
       pass
  2. 一个类组织相关的测试

    class TestUserLogin:
       """所有登录相关的测试"""
       def test_login_success(self):
           pass
    
       def test_login_failure(self):
           pass
    
       def test_login_with_empty_username(self):
           pass

10.4 目录结构建议

  1. 按功能模块组织

    tests/
    ├── test_user.py
    ├── test_order.py
    ├── test_payment.py
    └── api/
       ├── test_user_api.py
       └── test_order_api.py
  2. 按测试类型组织

    tests/
    ├── unit/
    │   ├── test_user_model.py
    │   └── test_order_model.py
    ├── integration/
    │   ├── test_user_flow.py
    │   └── test_order_flow.py
    └── api/
       ├── test_user_api.py
       └── test_order_api.py
  3. 使用 conftest.py 共享 fixture

    project/
    ├── conftest.py          # 项目级别的 fixture
    ├── tests/
    │   ├── conftest.py      # tests 目录的 fixture
    │   └── api/
    │       ├── conftest.py  # api 目录的 fixture
    │       └── test_api.py

10.5 配置文件建议

  1. 创建 pytest.ini 统一配置

    [pytest]
    python_files = test_*.py
    python_classes = Test*
    python_functions = test_*
    testpaths = tests
    addopts = -v --tb=short
  2. 定义测试标记

    [pytest]
    markers =
       slow: 标记为慢速测试
       integration: 标记为集成测试
       smoke: 标记为冒烟测试
       api: 标记为 API 测试

10.6 验证测试发现

定期运行以下命令验证测试发现是否正常:

# 查看所有发现的测试
pytest --collect-only

# 查看详细信息
pytest --collect-only -v

# 运行所有测试
pytest

# 运行并显示详细信息
pytest -v

10.7 总结检查清单

在创建测试时,检查以下事项:

  • [ ] 文件名以 test_ 开头或以 _test 结尾
  • [ ] 测试函数名以 test_ 开头
  • [ ] 测试类名以 Test 开头
  • [ ] 测试类中没有 __init__ 方法
  • [ ] 类中的测试方法名以 test_ 开头
  • [ ] 文件、函数、类名使用描述性名称
  • [ ] 使用下划线分隔单词
  • [ ] 目录结构清晰合理
  • [ ] 有 pytest.ini 配置文件(如需要)

附录:快速参考表

文件命名规则

格式 示例 是否会被发现
test_*.py test_login.py ✅ 是
*_test.py login_test.py ✅ 是
*.py login.py ❌ 否

函数命名规则

格式 示例 是否会被发现 说明
test_* test_login() ✅ 是 标准规则,推荐使用
* login() ❌ 否 不符合规则
test* testlogin() ⚠️ 可能 某些版本可能发现,但不推荐,应使用 test_*

类命名规则

格式 示例 是否会被发现
Test* TestLogin ✅ 是
*Test LoginTest ❌ 否
test* testLogin ❌ 否

类方法命名规则

格式 示例 是否会被发现
test_* test_login() ✅ 是
* login() ❌ 否

特殊文件

文件名 作用 是否会被发现
conftest.py 存放 fixture ❌ 否(特殊文件)
pytest.ini 配置文件 ❌ 否(配置文件)

结语

通过本文档,你应该已经全面了解了 pytest 的用例发现规则。记住核心规则:

  1. 文件test_*.py*_test.py
  2. 函数test_*
  3. Test*(不能有 __init__
  4. 类方法test_*

遵循这些规则,pytest 就能自动发现并运行你的测试用例。如果遇到问题,使用 pytest --collect-only 命令查看 pytest 发现了哪些测试,这将帮助你快速定位问题。

发表评论