契约测试的要点
1. 契约测试的核心原理是通过定义接口的契约(Contract),即接口的预期行为,然后在测试中验证实际的接口行为是否符合契约的要求。
2. 契约是具体验证什么?契约可包括接口的输入参数、输出结果、异常情况等方面的规定。通过定义一份"合同",我们可以明确接口应该如何被正确使用
,以及它应该返回哪些数据和状态
。
3. 举例说明:
在实际测试中,我们会编写针对每个接口的测试用例,并使用具体的请求数据调用接口,并获取其返回结果。然后,我们会对返回结果进行断言,验证其与契约定义的预期结果是否一致。如果断言失败,就意味着接口的行为与契约不符,测试将失败。
一个用户注册的接口的例子:
对于用户注册接口,首先契约应规定必须提供用户名和密码
作为输入,调用成功后返回一个唯一用户ID
。那么在测试中,我们会编写一个测试用例,传入合法的用户名和密码,调用接口并获得返回结果。接着,我们会对返回结果进行断言,验证是否包含预期的用户ID。如果断言失败,就表示接口的实际行为不符合契约,测试将失败。
通过验证接口行为是否符合契约的原理,契约测试能够有效地保证接口的正确性和稳定性。它提供了一种可靠的测试方法
,既可以在开发过程中验证接口的正确性,也可以在持续集成流程中作为一项重要的测试环节。帮助我们更有信心地保证接口在不同场景下的行为都是符合预期,提高系统的质量和可靠性。
代码展示
-
安装pytest和pytest-contract:使用pip命令安装pytest和pytest-contract两个库:
pip install pytest pip install pytest-contract
-
编写契约文件:根据接口的定义,编写契约文件,其中包括接口的输入参数和输出结果的类型定义和验证逻辑。契约文件一般采用YAML或JSON格式,例如:
# 注册用户接口的契约文件 post /register: request: name: str age: int response: success: bool message: str
-
编写契约测试代码:根据契约文件中的定义,编写契约测试代码,包括对请求参数和响应结果的类型和值的验证。pytest-contract提供了一个
contract()
装饰器,可以方便地编写契约测试,例如:# 注册用户接口的契约测试代码 from pytest_contract import contract from faker import Faker fake = Faker() @contract('register.yml') def test_register(requests): # 生成测试数据 name = fake.name() age = fake.random_int(min=18, max=60) # 发送接口请求 response = requests.post('/register', json={'name': name, 'age': age}) # 验证响应结果 assert response.status_code == 200 assert response.json()['success'] == True
-
运行契约测试:在终端执行pytest命令,可以运行契约测试,例如:
pytest test_register.py
-
查看测试报告:pytest-contract会自动生成测试报告,可查看到各个接口的测试结果和失败原因。
丰富基于pytest-contract的契约测试框架
-
增加参数化测试:
import pytest @pytest.mark.parametrize('name, age', [ ('Alice', 25), ('Bob', 30), ('Charlie', 35) ]) def test_register(requests, name, age): response = requests.post('/register', json={'name': name, 'age': age}) assert response.status_code == 200 assert response.json()['success'] == True
-
增加高级验证逻辑:
def validate_date_in_range(date_str, start_date, end_date): date = datetime.datetime.strptime(date_str, '%Y-%m-%d') return start_date <= date <= end_date @contract('schedule.yml') def test_schedule_event(requests): start_date = '2023-01-01' end_date = '2023-12-31' response = requests.get('/schedule') for event in response.json(): assert validate_date_in_range(event['date'], start_date, end_date)
-
增加数据生成规则:
from faker.providers import BaseProvider class CustomProvider(BaseProvider): def phone_number(self): return '555-1234' # 自定义生成手机号码的规则 fake.add_provider(CustomProvider) def test_generate_phone_number(): number = fake.phone_number() assert number == '555-1234'
-
加入re匹配断言:
import re def test_search_result(requests): response = requests.get('/search?q=test') assert response.status_code == 200 assert 'test' in response.text pattern = re.compile(r'\d+ results found') assert pattern.match(response.text)
-
错误处理和报告:pytest-contract会自动捕获契约验证失败的异常,并提供详细的错误信息和报告。可以直接运行pytest命令查看输出结果。
@pytest.mark.contract def test_contract_validation(requests): response = requests.get('/user') assert response.status_code == 200 assert 'username' in response.json() assert 'email' in response.json()
在执行上述测试用例时,如果契约验证失败,pytest-contract会捕获异常,并提供详细的错误信息和报告,例如指出具体哪个断言失败,方便开发人员定位问题。
-
数据库和外部服务的集成,可使用mock库模拟数据库和外部服务的交互,确保接口在不同情况下的正确性:
import pytest from unittest import mock def get_user_from_db(user_id): # 从数据库中获取用户信息的实际逻辑 pass @pytest.mark.contract def test_get_user(requests): user_id = 1 with mock.patch('app.get_user_from_db', return_value={'id': user_id, 'name': 'Alice'}): response = requests.get(f'/users/{user_id}') assert response.status_code == 200 assert response.json() == {'id': user_id, 'name': 'Alice'}
在上述测试用例中,通过使用mock.patch装饰器,我们可以模拟get_user_from_db函数的行为,使其返回我们指定的数据,从而在不依赖实际数据库的情况下进行测试。
-
集成到持续集成流程:结合持续集成工具,例如Jenkins、GitLab CI等,在项目的CI配置中添加契约测试的步骤,这里使用Jenkins作为持续集成工具,在项目的CI配置中添加契约测试步骤。Jenkinsfile配置如下:
pipeline { agent any stages { stage('Build') { steps { // 构建步骤 } } stage('Test') { steps { // 运行单元测试、集成测试等其他测试步骤 // 执行契约测试 sh 'pytest --contract' } } stage('Deploy') { steps { // 部署步骤 } } } }
上述Jenkinsfile中的Test阶段添加了执行契约测试的步骤,通过运行pytest命令并指定
--contract
参数,可以只运行契约测试,并生成相应的报告和统计信息。