Help us learn about your current experience with the documentation. Take the survey.

Python 风格指南

测试

概述

GitLab 的测试,包括 Python 代码库中的测试,是核心优先事项,而不是事后补充。因此,从一开始就应考虑测试设计质量与功能设计同等重要。

使用 Pytest 进行 Python 测试。

推荐阅读

测试层次

在编写测试之前,了解不同的测试层次,并确定适合您更改的测试层次。

了解更多关于不同测试层次的信息,以及如何决定您的更改应该在哪个层次进行测试。

建议

测试文件名应与被测试文件名相同

对于单元测试,使用 test_{被测试文件}.py 命名测试文件,并将其放在相同的目录结构中,有助于后续测试的发现。这也可以避免同名文件但不同模块之间的混淆。

File: /foo/bar/cool_feature.py

# Bad

Test file: /tests/my_cool_feature.py

# Good

Test file: /tests/foo/bar/test_cool_feature.py

使用 NamedTuples 定义参数化测试用例

Pytest 参数化测试 有效减少了代码重复,但它们依赖元组来定义测试用例,不像 Ruby 那样有更易读的语法。随着参数或测试用例的增加,这些基于元组的测试变得越来越难以理解和维护。

通过使用 Python NamedTuples,您可以:

  • 通过命名字段强制执行更清晰的组织。
  • 使测试更具自文档性。
  • 轻松定义参数的默认值。
  • 在复杂的测试场景中提高可读性。
# Good: Short examples, with small numbers of arguments. Easy to map what each value maps to each argument
# 良好:简短的示例,参数数量少。容易映射每个值到每个参数

@pytest.mark.parametrize(
    (
        "argument1",
        "argument2",
        "expected_result",
    ),
    [
        # description of case 1,
        # 用例 1 的描述,
        ("value1", "value2", 200),
        # description of case 2,
        # 用例 2 的描述,
        ...,
    ],
)
def test_get_product_price(argument1, argument2, expected_result):
    assert get_product_price(value1, value2) == expected_cost

# Bad: difficult to map a value to an argument, and to add or remove arguments when updating test cases
# 不良:难以将值映射到参数,并且在更新测试用例时难以添加或删除参数

@pytest.mark.parametrize(
    (
        "argument1",
        "argument2",
        "argument3",
        "expected_response",
    ),
    [
      # Test case 1:
      # 测试用例 1:
      (
        "value1",
        {
          ...
        },
        ...
      ),
      # Test case 2:
      # 测试用例 2:
      ...
    ]
)

def test_my_function(argument1, argument2, argument3, expected_response):
   ...

# Good: NamedTuples improve readability for larger test scenarios.
# 良好:NamedTuples 提高了大型测试场景的可读性。

from typing import NamedTuple

class TestMyFunction:
  class Case(NamedTuple):
      argument1: str
      argument2: int = 3
      argument3: dict
      expected_response: int

  TEST_CASE_1 = Case(
      argument1="my argument",
      argument3={
          "key": "value"
      },
      expected_response=2
  )

  TEST_CASE_2 = Case(
      ...
  )
  @pytest.mark.parametrize(
      "test_case", [TEST_CASE_1, TEST_CASE_2]
  )
  def test_my_function(test_case):
      assert my_function(case.argument1, case.argument2, case.argument3) == case.expected_response

模拟

  • 使用 unittest.mock 库。
  • 在适当的级别进行模拟,例如在方法调用边界处。
  • 模拟外部服务和 API。

代码风格

建议使用自动化工具来确保代码质量和安全。 考虑在 CI 管道和本地运行以下工具:

格式化工具

  • Black:强制执行一致风格的代码格式化工具
  • isort:排序和组织导入语句

代码检查工具

  • flake8:检查 PEP-8 合规性和常见错误
  • pylint:更全面的代码质量检查工具
  • mypy:Python 静态类型检查器

测试工具

  • pytest:带有覆盖率报告的测试框架

安全工具

  • 依赖扫描:检查依赖项中的漏洞
  • 秘密检测:确保没有秘密被提交到仓库
  • SAST (semgrep):静态应用程序安全测试