Python+Requests零基础系统掌握接口自动化测试

Python+Requests零基础系统掌握接口自动化测试---youkeit.xyz/1008/

从Postman到Python+Requests:接口自动化"脚本化"技术跃迁实战指南

引言:接口测试的进化之路

随着敏捷开发和DevOps的普及,接口测试从简单的工具点击操作,逐步演进为可编程的自动化测试体系。本文将带你完成从Postman可视化操作到Python+Requests全脚本化开发的完整跃迁,通过对比展示和代码实战,揭示自动化测试的核心技术升级路径。

一、Postman基础:可视化接口测试

1.1 Postman基本工作流

javascript 复制代码
// Postman的Tests脚本示例
pm.test("状态码应为200", function() {
    pm.response.to.have.status(200);
});

pm.test("响应时间小于200ms", function() {
    pm.expect(pm.response.responseTime).to.be.below(200);
});

pm.test("包含特定字段", function() {
    var jsonData = pm.response.json();
    pm.expect(jsonData.data).to.have.property('username');
});

1.2 Postman局限性分析

  • 难以版本控制:测试用例存储在Postman集合中
  • 有限编程能力:仅支持JavaScript片段
  • 不易集成:与CI/CD流水线集成复杂

二、Python+Requests基础迁移

2.1 基础请求转换

Postman中的GET请求转换为Python:

python 复制代码
import requests

# 对应Postman GET请求
response = requests.get(
    'https://api.example.com/users',
    params={'page': 1, 'limit': 20},
    headers={'Authorization': 'Bearer token123'}
)

# 断言状态码
assert response.status_code == 200
# 断言响应时间
assert response.elapsed.total_seconds() * 1000 < 200  
# 断言JSON字段
assert 'username' in response.json()['data']

2.2 POST请求示例

python 复制代码
# 复杂POST请求示例
auth_payload = {
    'username': 'testuser',
    'password': 'Test@123'
}

login_response = requests.post(
    'https://api.example.com/auth/login',
    json=auth_payload,
    headers={'Content-Type': 'application/json'}
)

# 获取token供后续使用
auth_token = login_response.json()['token']

三、高级封装与设计模式

3.1 请求封装类

python 复制代码
class APIClient:
    def __init__(self, base_url):
        self.base_url = base_url
        self.session = requests.Session()
        self.default_headers = {
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
    
    def _request(self, method, endpoint, **kwargs):
        url = f"{self.base_url}{endpoint}"
        headers = {**self.default_headers, **kwargs.pop('headers', {})}
        
        response = self.session.request(
            method=method,
            url=url,
            headers=headers,
            **kwargs
        )
        
        response.raise_for_status()  # 自动处理4xx/5xx错误
        return response
    
    def get(self, endpoint, params=None, **kwargs):
        return self._request('GET', endpoint, params=params, **kwargs)
    
    def post(self, endpoint, json=None, **kwargs):
        return self._request('POST', endpoint, json=json, **kwargs)
    
    # 可继续添加put/patch/delete等方法

3.2 测试用例组织

python 复制代码
import unittest

class UserAPITests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.client = APIClient('https://api.example.com')
        cls.client.default_headers.update({
            'Authorization': f'Bearer {get_auth_token()}'
        })
    
    def test_get_user_list(self):
        response = self.client.get('/users', params={'page': 1})
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertIsInstance(data['items'], list)
        self.assertLessEqual(len(data['items']), 20)
    
    def test_create_user(self):
        user_data = {
            'username': 'new_user',
            'email': 'new@example.com',
            'password': 'P@ssw0rd'
        }
        response = self.client.post('/users', json=user_data)
        self.assertEqual(response.status_code, 201)
        self.assertIn('id', response.json())

四、Postman到Python的自动化转换

4.1 Postman集合导出

  1. 在Postman中选择集合 → 导出 → 选择v2.1格式
  2. 保存为JSON文件(如postman_collection.json

4.2 自动转换脚本

python 复制代码
import json
from pathlib import Path

def convert_postman_to_python(collection_path, output_dir):
    with open(collection_path) as f:
        collection = json.load(f)
    
    output_dir = Path(output_dir)
    output_dir.mkdir(exist_ok=True)
    
    for item in collection['item']:
        class_name = item['name'].replace(' ', '')
        filename = f"test_{class_name.lower()}.py"
        
        with open(output_dir / filename, 'w') as f:
            f.write(f"import unittest\n")
            f.write(f"from api_client import APIClient\n\n")
            f.write(f"class {class_name}Tests(unittest.TestCase):\n")
            f.write(f"    @classmethod\n    def setUpClass(cls):\n")
            f.write(f"        cls.client = APIClient('{collection.get('variable', [{}])[0].get('value', '')}')\n\n")
            
            for request in item.get('item', []):
                method = request['request']['method'].lower()
                test_name = request['name'].replace(' ', '_').lower()
                url = request['request']['url'].get('raw', '').split('?')[0]
                
                f.write(f"    def test_{test_name}(self):\n")
                if method == 'get':
                    f.write(f"        response = self.client.{method}('{url}')\n")
                else:
                    f.write(f"        payload = {request['request'].get('body', {}).get('raw', '{}')}\n")
                    f.write(f"        response = self.client.{method}('{url}', json=payload)\n")
                
                f.write(f"        self.assertEqual(response.status_code, 200)\n\n")
            
            f.write(f"if __name__ == '__main__':\n    unittest.main()\n")

# 使用示例
convert_postman_to_python('postman_collection.json', 'generated_tests')

五、进阶自动化能力

5.1 数据驱动测试

python 复制代码
import csv
import ddt

@ddt.ddt
class DataDrivenTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.client = APIClient('https://api.example.com')
    
    @ddt.file_data('test_data.json')
    def test_create_user(self, username, email, password):
        user_data = {
            'username': username,
            'email': email,
            'password': password
        }
        response = self.client.post('/users', json=user_data)
        self.assertEqual(response.status_code, 201)
        self.assertTrue(response.json()['id'])

5.2 自动化断言生成

python 复制代码
def generate_assertions(response, prefix=''):
    assertions = []
    if isinstance(response, dict):
        for key, value in response.items():
            new_prefix = f"{prefix}['{key}']" if prefix else f"['{key}']"
            if isinstance(value, (dict, list)):
                assertions.extend(generate_assertions(value, new_prefix))
            else:
                assertions.append(f"self.assertIn('{key}', response.json(){prefix})")
    elif isinstance(response, list) and len(response) > 0:
        assertions.extend(generate_assertions(response[0], f"{prefix}[0]"))
    return assertions

# 使用示例
response = requests.get('https://api.example.com/users/1')
print("\n".join(generate_assertions(response.json())))

六、CI/CD集成实战

6.1 pytest集成示例

python 复制代码
# conftest.py
import pytest

@pytest.fixture(scope="module")
def api_client():
    client = APIClient('https://api.example.com')
    yield client
    client.session.close()

# test_users.py
def test_user_flow(api_client):
    # 创建用户
    create_resp = api_client.post('/users', json={
        'username': 'testuser',
        'password': 'Test@123'
    })
    assert create_resp.status_code == 201
    user_id = create_resp.json()['id']
    
    # 查询用户
    get_resp = api_client.get(f'/users/{user_id}')
    assert get_resp.status_code == 200
    assert get_resp.json()['username'] == 'testuser'
    
    # 删除用户
    del_resp = api_client.delete(f'/users/{user_id}')
    assert del_resp.status_code == 204

6.2 GitHub Actions配置

yaml 复制代码
name: API Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.9'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install requests pytest pytest-cov
        
    - name: Run tests
      run: |
        pytest tests/ --cov=src --cov-report=xml
        
    - name: Upload coverage
      uses: codecov/codecov-action@v1

七、企业级最佳实践

7.1 测试框架扩展

python 复制代码
# base_test.py
class APITestBase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.client = APIClient(os.getenv('API_BASE_URL'))
        cls.test_data = load_test_data()
        
    def assertResponseTime(self, response, max_ms):
        elapsed_ms = response.elapsed.total_seconds() * 1000
        self.assertLessEqual(elapsed_ms, max_ms,
            f"响应时间{elapsed_ms}ms超过限制{max_ms}ms")
    
    def assertSchema(self, response, schema):
        jsonschema.validate(response.json(), schema)

# 继承使用
class ProductTests(APITestBase):
    def test_product_list(self):
        response = self.client.get('/products')
        self.assertEqual(response.status_code, 200)
        self.assertResponseTime(response, 500)
        self.assertSchema(response, PRODUCT_SCHEMA)

7.2 智能Mock服务

python 复制代码
from unittest.mock import patch

class MockTests(unittest.TestCase):
    @patch('requests.Session.request')
    def test_mocked_api(self, mock_request):
        # 配置Mock响应
        mock_response = unittest.mock.Mock()
        mock_response.status_code = 200
        mock_response.json.return_value = {'id': 123, 'name': 'Mocked'}
        mock_request.return_value = mock_response
        
        # 执行测试
        client = APIClient('http://mocked')
        response = client.get('/users/123')
        
        # 验证
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.json()['name'], 'Mocked')
        mock_request.assert_called_with(
            'GET', 'http://mocked/users/123',
            headers={'Accept': 'application/json', 'Content-Type': 'application/json'},
            allow_redirects=True, params=None, timeout=None
        )

结语:技术跃迁的价值提升

从Postman到Python+Requests的转变,不仅仅是工具的更换,更是测试思维和技术能力的全面升级:

  1. 可编程性:完整Python生态支持复杂测试逻辑
  2. 可维护性:代码化用例更易于版本管理和团队协作
  3. 可扩展性:轻松集成到CI/CD流程和企业测试体系
  4. 可复用性:通过封装实现测试资产积累

关键建议:初期可以Postman作为探索性测试工具,待接口稳定后转换为Python自动化脚本,两者互补而非互斥。最终目标是建立"代码即文档、测试即资产"的现代化接口测试体系。

相关推荐
Rust研习社1 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒1 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro2 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax2 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH2 小时前
Koa和Express的区别
后端
MariaH3 小时前
Koa框架的使用
后端
luckdewei4 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某5 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy5 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom5 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github