Taoの小窝
首页项目博客照片墙音乐技能栈说说杂谈友链关于
封面

Python + Pytest + Requests 接口自动化测试框架:从零搭建可维护的分层式测试工程

写作时间:2025-12-20 12:00:00
# 软件测试
# 接口测试
# Python
# pytest
# 自动化测试

项目背景

在面试或实际工作中,接口自动化测试能力几乎是测试工程师和开发工程师的必备技能。但很多人只停留在"会用 Postman 点点点"的阶段,缺乏工程化的框架思维。本项目基于 Python + pytest + requests 搭建了一套分层式 API 自动化测试框架,覆盖 GET / POST / PUT / DELETE 与参数化测试,目标是建立可维护、可扩展的接口自动化测试工程。

项目目标

  • 掌握 Python + pytest + requests 做接口测试的完整流程
  • 实践分层架构:配置层、数据层、接口封装层、用例层、工具层分离
  • 使用 YAML 管理配置与测试数据
  • 集成日志、异常处理、HTML 报告、Allure 报告
  • 为简历提供可直接使用的项目描述

被测对象

JSONPlaceholder:免费的 REST API 练习平台,无需注册和 Token,支持标准的 CRUD 操作,非常适合接口自动化测试学习与教学。

技术栈

技术 说明
Python 3.10+ 编程语言
pytest 测试框架
requests HTTP 请求库
PyYAML YAML 配置与数据解析
pytest-html HTML 测试报告
allure-pytest Allure 测试报告
pytest-xdist 并行执行(可选)

项目结构

api-automation-framework/
├── api/                    # 接口封装层
│   ├── base_api.py         # 基础请求封装
│   ├── users_api.py        # /users 资源接口
│   └── posts_api.py        # /posts 资源接口
├── testcases/              # 测试用例
│   ├── test_users.py       # 用户接口 CRUD 测试
│   ├── test_posts.py       # 帖子接口 CRUD 测试
│   ├── test_parametrize.py # 参数化测试
│   └── test_exceptions.py  # 异常场景测试
├── config/
│   └── config.yaml         # 基础 URL、超时时间等
├── data/                   # YAML 测试数据
│   ├── users.yaml
│   └── posts.yaml
├── utils/                  # 工具类
│   ├── config_loader.py    # 配置加载
│   ├── data_loader.py      # 测试数据加载
│   ├── logger.py           # 日志工具
│   └── assertions.py       # 断言辅助函数
├── reports/                # 报告输出目录
├── logs/                   # 运行日志
├── conftest.py             # pytest fixture
├── pytest.ini              # pytest 配置
├── requirements.txt
├── run_tests.py            # 一键执行脚本
└── README.md

分层设计思想

如果把框架比作一栋楼:

  • config/ 是地基配置(URL、超时时间)
  • utils/ 是工具房(锤子、螺丝刀)
  • api/ 是水电管线(接口请求)
  • data/ 是建材仓库(测试数据)
  • testcases/ 是房间装修(具体测试用例)

分层的好处:

  1. 好维护:改 URL 只改 config.yaml,不用改几十条用例。
  2. 好复用:用户接口和帖子接口共用 base_api.py 的请求逻辑。
  3. 好扩展:新增资源(如 comments),只需新增 comments_api.py 和 test_comments.py。

核心实现

1. 配置文件 config.yaml

base_url: https://jsonplaceholder.typicode.com
timeout: 5
log_level: INFO
response_time_threshold: 5.0

所有环境相关的配置集中管理,便于切换测试环境和生产环境。

2. 配置加载器 utils/config_loader.py

from pathlib import Path
import yaml

CONFIG_PATH = Path(__file__).resolve().parent.parent / "config" / "config.yaml"

class ConfigLoader:
    _cache = {}

    @classmethod
    def load_config(cls, key=None, default=None):
        if "config" not in cls._cache:
            with open(CONFIG_PATH, "r", encoding="utf-8") as f:
                cls._cache["config"] = yaml.safe_load(f)

        config = cls._cache["config"]
        if key is None:
            return config
        return config.get(key, default)

使用缓存机制避免重复读取 YAML 文件,通过 Path 定位确保无论在哪里运行测试都能找到配置文件。

3. 基础 API 封装 api/base_api.py

import requests
from requests.exceptions import RequestException

from utils.config_loader import load_config
from utils.logger import get_logger


class BaseAPI:
    def __init__(self):
        self.base_url = load_config("base_url")
        self.timeout = load_config("timeout", 5)
        self.logger = get_logger(self.__class__.__name__)

    def request(self, method: str, endpoint: str, **kwargs):
        url = f"{self.base_url}{endpoint}"
        self.logger.info(f"[{method}] {url} 开始请求")

        try:
            response = requests.request(
                method=method.upper(),
                url=url,
                timeout=self.timeout,
                **kwargs,
            )
            self.logger.info(
                f"[{method}] {url} 请求完成,状态码:{response.status_code},"
                f"响应时间:{response.elapsed.total_seconds():.3f}s"
            )
            return response
        except RequestException as exc:
            self.logger.error(f"[{method}] {url} 请求异常:{exc}")
            raise

    def get(self, endpoint: str, **kwargs):
        return self.request("GET", endpoint, **kwargs)

    def post(self, endpoint: str, **kwargs):
        return self.request("POST", endpoint, **kwargs)

    def put(self, endpoint: str, **kwargs):
        return self.request("PUT", endpoint, **kwargs)

    def delete(self, endpoint: str, **kwargs):
        return self.request("DELETE", endpoint, **kwargs)

BaseAPI 是所有业务接口的基类,集中处理:

  • URL 拼接
  • 请求日志记录
  • 异常捕获与记录
  • 响应时间记录

这样测试用例不需要关心底层请求细节,只关注业务断言。

4. 业务接口封装 api/users_api.py

from api.base_api import BaseAPI


class UsersAPI(BaseAPI):
    def get_user(self, user_id: int):
        return self.get(f"/users/{user_id}")

    def list_users(self):
        return self.get("/users")

    def create_user(self, payload: dict):
        return self.post("/users", json=payload)

    def update_user(self, user_id: int, payload: dict):
        return self.put(f"/users/{user_id}", json=payload)

    def delete_user(self, user_id: int):
        return self.delete(f"/users/{user_id}")

每个方法对应一个接口,业务语义清晰。新增资源时,只需复制一份并修改名称和路径。

5. pytest Fixture conftest.py

import pytest
from api.users_api import UsersAPI
from api.posts_api import PostsAPI
from utils.config_loader import load_config


@pytest.fixture(scope="session")
def config():
    return load_config()


@pytest.fixture(scope="function")
def users_api():
    return UsersAPI()


@pytest.fixture(scope="function")
def posts_api():
    return PostsAPI()


@pytest.fixture(scope="session")
def response_time_threshold(config):
    return config.get("response_time_threshold", 2.0)
  • scope="session":整个测试会话共享,如 config。
  • scope="function":每个测试函数新建实例,避免状态互相影响,如 users_api。

6. 测试用例 testcases/test_users.py

import pytest
import allure
from utils.data_loader import load_data
from utils.assertions import (
    assert_status_code,
    assert_response_time,
    assert_json_contains,
    assert_field_equals,
)


@allure.feature("用户接口")
@allure.story("查询用户")
class TestGetUser:

    @pytest.mark.smoke
    @pytest.mark.users
    @allure.title("查询单个用户")
    def test_get_user_success(self, users_api, response_time_threshold):
        response = users_api.get_user(1)

        assert_status_code(response, 200)
        assert_response_time(response, response_time_threshold)
        assert_json_contains(response, "id")
        assert_json_contains(response, "name")
        assert_json_contains(response, "email")
        assert_field_equals(response, "id", 1)

每个用例基本都会断言三个维度:

  1. 状态码:200、201、404 等
  2. 响应时间:接口不能慢得离谱
  3. 关键字段:确认返回了预期的数据

7. 参数化测试 testcases/test_parametrize.py

import pytest
from utils.data_loader import load_data
from utils.assertions import assert_status_code, assert_field_equals


@pytest.mark.parametrize(
    "case",
    load_data("users.yaml", "parametrize_users"),
    ids=[f"user_{c['user_id']}" for c in load_data("users.yaml", "parametrize_users")],
)
def test_get_user_with_parametrize(self, users_api, response_time_threshold, case):
    response = users_api.get_user(case["user_id"])

    assert_status_code(response, 200)
    assert_field_equals(response, "name", case["expected_name"])
    assert_field_equals(response, "username", case["expected_username"])

@pytest.mark.parametrize 会自动把 YAML 中的多组数据跑一遍。YAML 里有 3 组数据,这条用例就会生成 3 个测试。

8. 异常场景测试 testcases/test_exceptions.py

import pytest
from requests.exceptions import Timeout
from utils.data_loader import load_data
from utils.assertions import assert_status_code


def test_get_nonexistent_user(self, users_api):
    invalid_id = load_data("users.yaml", "invalid_user_id")
    response = users_api.get_user(invalid_id)
    assert_status_code(response, 404)


def test_request_timeout(self, users_api):
    users_api.timeout = 0.001
    with pytest.raises(Timeout):
        users_api.get_user(1)

异常场景测试验证了接口的容错能力和框架的异常处理机制。

9. YAML 测试数据 data/users.yaml

create_user:
  name: Alice Chen
  username: alice_dev
  email: alice.dev@example.com
  phone: 010-12345678
  website: https://alice.dev

parametrize_users:
  - user_id: 1
    expected_name: Leanne Graham
    expected_username: Bret
  - user_id: 2
    expected_name: Ervin Howell
    expected_username: Antonette

测试数据与代码分离的好处:

  • 改数据不用改代码
  • 非技术同事也能看懂和修改
  • 便于参数化和数据驱动

10. 日志工具 utils/logger.py

import logging
from pathlib import Path
from utils.config_loader import load_config

LOG_FILE = Path(__file__).resolve().parent.parent / "logs" / "api_test.log"


def get_logger(name: str = "api_test") -> logging.Logger:
    logger = logging.getLogger(name)
    if logger.handlers:
        return logger

    level = load_config("log_level", "INFO")
    logger.setLevel(getattr(logging, level.upper(), logging.INFO))

    formatter = logging.Formatter(
        "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )

    console_handler = logging.StreamHandler()
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)

    file_handler = logging.FileHandler(LOG_FILE, encoding="utf-8")
    file_handler.setFormatter(formatter)
    logger.addHandler(file_handler)

    return logger

日志同时输出到控制台和 logs/api_test.log 文件,便于排查问题和追溯历史。

11. 断言工具 utils/assertions.py

def assert_status_code(response, expected: int):
    assert response.status_code == expected, (
        f"状态码断言失败:期望 {expected},实际 {response.status_code}"
    )


def assert_response_time(response, threshold: float):
    elapsed = response.elapsed.total_seconds()
    assert elapsed < threshold, (
        f"响应时间断言失败:期望小于 {threshold}s,实际 {elapsed:.3f}s"
    )


def assert_json_contains(response, field: str):
    data = response.json()
    assert field in data, f"响应 JSON 中缺少字段:{field}"


def assert_field_equals(response, field: str, expected):
    data = response.json()
    assert data.get(field) == expected, (
        f"字段 {field} 断言失败:期望 {expected},实际 {data.get(field)}"
    )

断言失败时给出清晰的中文错误信息,方便快速定位问题。

运行方式

运行全部测试

pytest

按标记运行

# 只跑冒烟用例
pytest -m smoke

# 只跑用户接口
pytest -m users

# 只跑帖子接口
pytest -m posts

生成 Allure 报告

pytest --alluredir=reports/allure
allure generate reports/allure -o reports/allure/html --clean

一键执行

python run_tests.py

该脚本会自动清空旧报告、运行全部用例、生成 HTML 和 Allure 报告。

测试覆盖

接口类型 覆盖内容
GET 查询单个用户、查询用户列表、查询单个帖子、查询帖子列表
POST 创建用户、创建帖子
PUT 更新用户、更新帖子
DELETE 删除用户、删除帖子
参数化测试 多组用户 ID / 帖子 ID 数据验证
异常处理 查询不存在资源返回 404、网络超时异常捕获

项目成果

  • 建立了完整的分层式接口自动化测试框架。
  • 覆盖 JSONPlaceholder 的 users/posts 资源 CRUD 操作。
  • 实现配置、数据、接口、用例、工具完全解耦。
  • 集成日志、异常处理、响应时间断言、HTML/Allure 报告。
  • 支持按 smoke/users/posts 等自定义标记运行测试。

简历写法参考

API 接口自动化测试框架

  • 基于 Python + pytest + requests 搭建分层式接口自动化测试框架,覆盖 JSONPlaceholder 的 users/posts 资源。
  • 使用 YAML 管理接口配置与测试数据,通过 conftest.py 统一管理 fixture,实现用例与数据解耦。
  • 封装 BaseAPI 统一处理 HTTP 请求、日志记录与异常捕获,提供状态码、响应时间、关键字段等多维度断言。
  • 集成 pytest-html 与 Allure 生成可视化测试报告,支持冒烟、用户、帖子、参数化等自定义标记运行。

写在最后

这个项目虽小,但五脏俱全。它覆盖了接口自动化测试的核心能力:接口请求、数据管理、断言设计、日志记录、异常处理、报告输出。掌握这套框架后,完全可以自信地在简历中写"具备接口自动化测试框架搭建能力",并在面试中从容展开。

avatar

Tao

在数据与代码间探索的普通人 / 数据科学与大数据技术专业大三学生,坐标陕西西安。热爱技术实践与数据分析,正在找测试、数据分析方向的实习。

RECOMMENDED

数据分析入门:从数据清洗到可视化

2025-09-15 10:00:00

从零开始的数据分析学习项目:清洗、探索、可视化与机器学习入门

2025-08-15 10:00:00

电商订单数据质量测试:用 Python + Pandas 构建自动化数据校验体系

2026-01-15 13:00:00

Table of Contents