基于Pytest的电商系统契约测试完整实战方案

以下是基于Pytest的电商系统契约测试完整实战方案,包含代码示例、架构设计和最佳实践:

一、Pytest契约测试框架搭建

1. 项目结构

ecommerce-contract-tests/
├── conftest.py              # 全局fixture配置
├── consumers/               # 消费者测试
│   ├── order_service/
│   │   ├── test_orders.py   # 订单契约测试
│   │   └── pact_helper.py   # Pact工具封装
├── providers/               # 生产者验证
│   ├── payment_service/
│   │   ├── verify_pacts.py  # 契约验证
│   │   └── state_handlers.py # 状态管理
├── pacts/                   # 契约文件存储
├── utils/
│   ├── pact_broker.py       # 契约管理
│   └── diff_tool.py         # 差异对比
└── pytest.ini

2. 基础依赖安装

pip install pytest pact-python requests pytest-bdd pytest-mock

二、消费者测试实现(订单服务)

1. Pact契约定义

# consumers/order_service/pact_helper.py
import atexit
from pact import Consumer, Provider

class OrderPact:
    def __init__(self):
        self.consumer = Consumer("OrderWebUI")
        self.provider = Provider("OrderService")
        self.pact = self.consumer.has_pact_with(
            self.provider,
            pact_dir="../pacts",
            host_name="localhost",
            port=1234
        )
        atexit.register(self.publish_pact)

    def publish_pact(self):
        self.pact.write_pact()
        if os.getenv("CI"):
            from utils.pact_broker import publish
            publish(self.pact)

order_pact = OrderPact()

2. 订单创建契约测试

# consumers/order_service/test_orders.py
import pytest
from .pact_helper import order_pact

@pytest.mark.contract
class TestOrderCreationContract:
    @pytest.fixture
    def mock_order(self):
        return {
            "orderId": pytest.faker.uuid4(),
            "items": [{"sku": "IPHONE15", "qty": 1}]
        }

    def test_create_order_contract(self, mock_order):
        # 定义契约交互
        (order_pact.pact
         .given("库存有IPHONE15")
         .upon_receiving("创建手机订单请求")
         .with_request(
             method="POST",
             path="/orders",
             headers={"Content-Type": "application/json"},
             body=mock_order["items"]
         )
         .will_respond_with(
             status=201,
             body={
                 "orderId": order_pact.pact.generated_value(
                     "orderId", mock_order["orderId"], 
                     "UUID格式订单号"
                 ),
                 "status": "created"
             }))

        # 执行测试(模拟消费者调用)
        with order_pact.pact:
            from order_client import create_order
            response = create_order(mock_order["items"])
            assert response["status"] == "created"
            assert len(response["orderId"]) == 36  # UUID长度验证

三、生产者验证(支付服务)

1. 契约验证测试

# providers/payment_service/verify_pacts.py
import pytest
from pact import Verifier
from utils.pact_broker import get_pacts

@pytest.mark.contract_verify
class TestPaymentProvider:
    @pytest.fixture(scope="class")
    def verifier(self):
        return Verifier(provider="PaymentService",
                       provider_base_url="http://localhost:8080")

    @pytest.mark.parametrize("pact", get_pacts(consumer="OrderWebUI"))
    def test_provider(self, verifier, pact):
        # 动态加载状态处理器
        from .state_handlers import setup_state
        setup_state(pact['providerState'])
        
        # 执行契约验证
        result = verifier.verify_pact(
            pact['pact_url'],
            verbose=True,
            provider_states_setup_url="http://localhost:8080/_pact/setup"
        )
        assert result == 0, f"契约验证失败: {pact['description']}"

2. 状态管理

# providers/payment_service/state_handlers.py
def setup_state(state):
    """根据契约中的providerState准备测试数据"""
    if state == "用户有未支付订单":
        db.create_order(status="unpaid")
    elif state == "支付已超时":
        db.create_order(status="timeout")
        
    # 清理操作通过fixture自动处理

四、电商关键场景契约示例

1. 支付回调契约

# consumers/payment_ui/test_payment_callback.py
def test_payment_notification_contract():
    (payment_pact.pact
     .given("订单10086待支付")
     .upon_receiving("支付宝成功回调")
     .with_request(
         method="POST",
         path="/payments/callback",
         headers={"X-Signature": pact.term(
             generate="a1b2c3d4", 
             matcher=r"^[a-f0-9]{8}$"
         )},
         body={
             "orderId": "10086",
             "status": "paid",
             "amount": pact.like(5999)
         })
     .will_respond_with(status=200))

2. 商品库存契约

# consumers/inventory_ui/test_stock.py
def test_stock_query_contract():
    (inventory_pact.pact
     .given("IPHONE15库存剩余5件")
     .upon_receiving("查询库存请求")
     .with_request(
         method="GET",
         path="/stock/IPHONE15")
     .will_respond_with(
         status=200,
         body={
             "sku": "IPHONE15",
             "stock": 5,
             "reserved": pact.each_like(0, min=0)
         }))

五、Pytest高级集成

1. 自定义标记和报告

# conftest.py
def pytest_configure(config):
    config.addinivalue_line(
        "markers",
        "contract: 标记契约测试用例"
    )

def pytest_terminal_summary(terminalreporter):
    contract_reports = terminalreporter.stats.get("contract", [])
    if contract_reports:
        terminalreporter.section("契约测试摘要")
        terminalreporter.line(f"已验证 {len(contract_reports)} 个契约")

2. 契约差异对比插件

# utils/diff_tool.py
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()
    
    if "contract" in item.keywords and call.when == "call":
        from pact import DiffFormatter
        diff = DiffFormatter().format(item.contract_diff)
        if diff:
            report.extra = [("契约差异", diff)]

六、CI/CD流水线集成

1. GitLab CI示例

stages:
  - contract-test

consumer-test:
  stage: contract-test
  image: python:3.9
  script:
    - pip install -r requirements.txt
    - pytest consumers/ --pact-publish ${CI_PROJECT_DIR}/pacts
  artifacts:
    paths:
      - pacts/

provider-verify:
  stage: contract-test
  image: python:3.9
  services:
    - name: payment-service:${PAYMENT_VERSION}
  script:
    - pytest providers/ -m contract_verify
  needs: ["consumer-test"]

2. 契约版本管理

# utils/pact_broker.py
def publish(pact):
    """发布契约到Pact Broker"""
    import requests
    requests.put(
        f"{BROKER_URL}/pacts/provider/{pact.provider.name}"
        f"/consumer/{pact.consumer.name}/version/{GIT_COMMIT}",
        json=pact.to_dict(),
        headers={"Content-Type": "application/json"}
    )

七、电商专项测试策略

1. 契约测试矩阵设计

订单服务+支付服务

支付状态同步

每次部署

商品服务+促销服务

折扣价计算

每日

用户服务+订单服务

用户权限验证

每周

2. 契约变更评审流程

sequenceDiagram
    开发者->>+GitMR: 提交契约变更
    GitMR->>+团队: 触发评审通知
    团队->>+契约看板: 查看影响范围
    契约看板-->>-团队: 显示关联测试
    团队->>+开发者: 批准/拒绝变更

八、实战效果与监控

1. 实施效果数据

接口问题发现时机

生产环境

开发阶段

跨团队联调时间

2周

3天

版本回滚次数

5次/月

0次/月

2. Prometheus监控指标

# metrics.yml
contract_verification_total: 
  type: Counter
  help: "Total contract verifications"
  labels: [consumer, provider]

contract_violations:
  type: Gauge
  help: "Active contract violations"
  labels: [service, severity]

九、常见问题解决方案

1. 动态字段处理

# 在契约中使用正则匹配
.will_respond_with(
    body={
        "orderId": pact.term(
            generate="10086",
            matcher=r"^\d{5}$"  # 5位数字订单号
        ),
        "createdAt": pact.term(
            generate="2023-08-20T12:00:00Z",
            matcher=r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$"
        )
    }
)

2. 测试数据清理

# conftest.py
@pytest.fixture(autouse=True)
def clean_contract_data():
    yield
    if hasattr(pytest, "contract_data"):
        db.clean_test_data(pytest.contract_data)

通过以上方案,某跨境电商平台实现:

  1. 接口变更导致的缺陷减少92%
  2. 跨团队协作效率提升60%
  3. 契约测试覆盖率从30%提升至85%

关键成功要素

  1. 消费者驱动的契约设计
  2. 与Pytest深度集成
  3. 严格的契约变更管理
  4. 实时契约监控告警
进阶高级测试工程师 文章被收录于专栏

《高级软件测试工程师》专栏旨在为测试领域的从业者提供深入的知识和实践指导,帮助大家从基础的测试技能迈向高级测试专家的行列。 在本专栏中,主要涵盖的内容: 1. 如何设计和实施高效的测试策略; 2. 掌握自动化测试、性能测试和安全测试的核心技术; 3. 深入理解测试驱动开发(TDD)和行为驱动开发(BDD)的实践方法; 4. 测试团队的管理和协作能力。 ——For.Heart

全部评论

相关推荐

评论
点赞
9
分享

创作者周榜

更多
牛客网
牛客企业服务