Python+Pytest 接口自动化测试实战 —— 抽奖系统接口测试框架设计与实现

文章目录

    • 一、项目背景
      • [1.1 为什么选择接口自动化测试](#1.1 为什么选择接口自动化测试)
      • [1.2 系统业务模块介绍](#1.2 系统业务模块介绍)
    • 二、技术选型
      • [2.1 为什么选择Pytest](#2.1 为什么选择Pytest)
      • [2.2 为什么选择YAML作为数据存储格式](#2.2 为什么选择YAML作为数据存储格式)
    • 三、核心模块
      • [3.1 HTTP请求封装](#3.1 HTTP请求封装)
      • [3.2 YAML数据管理](#3.2 YAML数据管理)
      • [3.3 JSON Schema响应校验](#3.3 JSON Schema响应校验)
      • [3.4 测试数据生成工具](#3.4 测试数据生成工具)
    • 四、挑选接口
    • 五、项目结构
      • [5.1 Pytest配置文件](#5.1 Pytest配置文件)
    • 六、测试用例设计(带脑图)
      • [6.1 测试用例设计原则](#6.1 测试用例设计原则)
      • [6.2 测试用例示例(脑图)](#6.2 测试用例示例(脑图))
    • 七、编写代码
      • [7.1 用户注册接口](#7.1 用户注册接口)
      • [7.2 登录接口](#7.2 登录接口)
      • [7.3 创建活动接口](#7.3 创建活动接口)
      • [7.4 抽奖接口](#7.4 抽奖接口)
    • 八、数据流转
      • [8.1 数据流转设计思想](#8.1 数据流转设计思想)
      • [8.2 完整数据流转链路](#8.2 完整数据流转链路)
      • [8.3 测试用例执行顺序](#8.3 测试用例执行顺序)
    • 九、测试报告
      • [9.1 测试用例统计](#9.1 测试用例统计)
      • [9.2 异常场景覆盖](#9.2 异常场景覆盖)
      • [9.3 allure 生成测试报告](#9.3 allure 生成测试报告)
    • 十、优化方向
      • [10.1 短期优化](#10.1 短期优化)
      • [10.2 长期扩展](#10.2 长期扩展)
    • 十一、总结

一、项目背景

在企业级应用开发中,抽奖系统是一个常见的业务场景,广泛应用于电商促销、年会活动、用户运营等多种场景。一个完整的抽奖系统涉及用户管理、奖品管理、活动管理、抽奖逻辑等多个模块,系统复杂度较高,接口数量众多。为了保证系统的稳定性和可靠性,我们需要对各个API接口进行全面的自动化测试。

1.1 为什么选择接口自动化测试

相比于UI自动化测试,接口自动化测试具有显著优势:

执行效率更高:接口测试直接与后端服务交互,无需加载前端页面,单次测试执行时间通常在毫秒级别。一个包含上百个测试用例的接口测试套件,通常可以在几分钟内完成执行,而同样的UI测试可能需要数小时。

稳定性更强:接口测试不依赖前端页面的元素定位,不会因为页面结构变化、样式调整、动态加载等因素导致测试失败。只要接口契约不变,测试用例就能稳定运行。

覆盖更全面:接口测试可以覆盖更多业务场景和边界条件,包括正常流程、异常流程、边界值、并发场景等。同时,接口测试更容易实现参数化和数据驱动,可以用较少的代码覆盖更多的测试场景。

投入产出比更高:接口自动化测试框架搭建相对简单,学习曲线平缓,团队成员可以快速上手。维护成本也相对较低,适合长期投入。

1.2 系统业务模块介绍

本抽奖系统主要包含以下核心业务模块:

用户管理模块:支持管理员和普通用户两种角色的注册、登录功能。管理员拥有系统管理权限,可以创建活动和管理奖品;普通用户作为活动参与者,可以参与抽奖活动。系统通过Token机制实现用户认证和权限控制。

奖品管理模块:管理员可以创建、编辑、删除奖品信息。每个奖品包含名称、描述、价格、图片等属性。奖品创建时需要上传奖品图片,支持多种图片格式。奖品可以关联到多个活动中,实现奖品的复用。

活动管理模块:管理员可以创建抽奖活动,关联奖品和参与用户。创建活动时需要设置活动名称、描述、奖品列表(包含奖品等级和中奖人数)、参与用户列表。系统支持多种奖品等级,如一等奖、二等奖、三等奖等。

抽奖执行模块:根据活动配置执行抽奖操作,记录中奖信息。抽奖时需要指定活动ID、奖品ID、奖品等级、中奖时间、中奖用户列表等信息。系统会校验活动的有效性、奖品的可用性、用户的参与资格等。

中奖记录模块:展示中奖信息,支持按活动查询中奖记录,提供活动详情查看功能。用户可以查看自己参与活动中的中奖情况,管理员可以查看所有活动的抽奖结果。

二、技术选型

选择以下技术进行代码编写:

技术 版本 说明
Python 3.12 编程语言,简洁优雅的语法、丰富的第三方库生态、强大的数据处理能力
Pytest 8.3.4 测试框架,提供简洁的用例编写方式、强大的参数化功能、丰富的插件生态、详细的失败信息输出
Requests 2.32.3 HTTP客户端库,API设计简洁直观,支持Session管理、文件上传、超时控制等特性
JSON Schema 4.23.0 响应数据校验,精确描述响应数据的类型、必填字段、取值范围等约束条件,支持复杂的嵌套结构
PyYAML 6.0.2 数据文件管理,YAML格式简洁易读,支持复杂的数据结构,适合作为测试数据配置文件
Pytest-Ordering 0.6 执行顺序控制,在存在数据依赖的场景下,合理的执行顺序是测试成功的关键

2.1 为什么选择Pytest

Pytest是Python生态中最流行的测试框架之一,相比unittest等传统框架,Pytest具有以下优势:

简洁的用例编写 :不需要继承TestCase类,不需要特定的命名规则,只需要以test_开头的函数即可作为测试用例。测试代码更加简洁,可读性更强。

强大的参数化功能 :通过@pytest.mark.parametrize装饰器,可以轻松实现数据驱动测试,用一套代码覆盖多组测试数据。

丰富的插件生态:Pytest拥有丰富的插件生态,如pytest-html(生成HTML报告)、pytest-ordering(控制执行顺序)、pytest-rerunfailures(失败重试)、allure-pytest(生成Allure报告)等。

详细的失败信息:Pytest在测试失败时会输出详细的错误信息,包括断言表达式、实际值、期望值等,便于快速定位问题。

灵活的fixture机制:通过fixture可以实现测试前置条件、测试数据准备、测试后清理等功能,支持fixture之间的依赖和复用。

2.2 为什么选择YAML作为数据存储格式

YAML(YAML Ain't Markup Language)是一种人类可读的数据序列化格式,相比JSON和XML,YAML具有以下优势:

简洁易读:YAML使用缩进表示层级关系,语法简洁,可读性强。对于配置文件和测试数据来说,可读性是非常重要的。

支持注释:YAML支持添加注释,可以在数据文件中添加说明信息,便于理解和维护。

支持复杂数据结构:YAML支持列表、字典、嵌套结构等复杂数据类型,可以表示任意复杂的数据结构。

支持多文档 :一个YAML文件可以包含多个文档,用---分隔,适合存储多个独立的配置项。

三、核心模块

3.1 HTTP请求封装

将Requests库进行二次封装,统一管理请求配置和异常处理。使用Session对象自动管理Cookie和连接池,提高请求效率。

封装的主要功能包括:

Session管理:使用Session对象自动管理Cookie和连接池,避免每次请求都建立新的连接,提高请求效率。同时,Session可以自动处理Cookie,对于需要登录态的接口非常方便。

超时控制:统一设置请求超时时间,避免因网络问题导致测试长时间阻塞。默认超时时间设置为10秒,可以根据实际情况调整。

异常处理 :提供raise_for_status参数控制是否自动抛出HTTP错误。对于正常测试场景,开启此选项可以自动检测HTTP状态码;对于需要测试异常响应的场景,可以关闭此选项手动处理。

统一入口:所有请求都通过封装后的Request类发起,便于统一添加日志、鉴权、重试等通用逻辑。

python 复制代码
import requests

host = "http://****"

class Request:
    def __init__(self):
        self.session = requests.Session()
        self.timeout = 10
    
    def get(self, url, params=None, headers=None, raise_for_status=True, **kwargs):
        full_url = host + url if not url.startswith('http') else url
        response = self.session.get(full_url, params=params, headers=headers, 
                                     timeout=self.timeout, **kwargs)
        if raise_for_status:
            response.raise_for_status()
        return response
    
    def post(self, url, json=None, data=None, headers=None, files=None, 
             raise_for_status=True, **kwargs):
        full_url = host + url if not url.startswith('http') else url
        response = self.session.post(full_url, json=json, data=data, headers=headers,
                                      files=files, timeout=self.timeout, **kwargs)
        if raise_for_status:
            response.raise_for_status()
        return response

3.2 YAML数据管理

使用YAML文件存储和管理测试数据,实现数据的持久化和复用。支持通过点分隔的键路径访问嵌套数据,如register.admin.last.phoneNumber

数据管理的设计思路:

数据持久化:将测试过程中产生的数据(如用户ID、Token、活动ID等)存储到YAML文件中,实现数据的持久化。这样即使测试中断,也可以从上次的状态继续执行。

数据复用:多个测试用例可以共享同一份数据,避免重复创建测试数据。例如,用户注册后保存的用户信息,可以在登录、创建活动等多个测试用例中使用。

数据隔离:通过键路径的方式组织数据,不同模块的数据存储在不同的命名空间下,避免数据冲突。

默认值支持:当数据不存在时,可以返回默认值,避免因数据缺失导致测试失败。

python 复制代码
import yaml
import os

def get_yaml(yaml_path, key, default=None):
    """获取YAML文件中的数据,支持点分隔的键路径"""
    keys = key.split('.')
    if not os.path.exists(yaml_path):
        return default
    with open(yaml_path, 'r', encoding='utf-8') as f:
        data = yaml.safe_load(f) or {}
    for k in keys:
        if data is None or not isinstance(data, dict):
            return default
        data = data.get(k)
    return data if data is not None else default

def set_yaml(yaml_path, key, value):
    """设置YAML文件中的数据,支持点分隔的键路径"""
    keys = key.split('.')
    if os.path.exists(yaml_path):
        with open(yaml_path, 'r', encoding='utf-8') as f:
            data = yaml.safe_load(f) or {}
    else:
        data = {}
    current = data
    for k in keys[:-1]:
        if k not in current:
            current[k] = {}
        current = current[k]
    current[keys[-1]] = value
    with open(yaml_path, 'w', encoding='utf-8') as f:
        yaml.dump(data, f, allow_unicode=True, default_flow_style=False)

3.3 JSON Schema响应校验

使用JSON Schema对API响应进行严格校验,确保响应数据结构正确。通过声明式的方式描述预期结构,任何不符合预期的响应都会被检测出来。

JSON Schema校验的优势:

结构校验:可以校验响应数据的整体结构,包括字段是否存在、字段类型是否正确、必填字段是否完整等。

类型约束:可以精确指定每个字段的类型,如字符串、数字、布尔值、数组、对象等。还可以进一步约束字符串的格式、数字的范围等。

灵活配置 :通过additionalProperties可以控制是否允许额外字段,通过required可以指定必填字段,通过enum可以限制枚举值。

错误定位:当校验失败时,JSON Schema会输出详细的错误信息,包括错误路径、错误类型、期望值和实际值等,便于快速定位问题。

python 复制代码
schema = {
    "type": "object",
    "required": ["code", "data", "msg"],
    "additionalProperties": False,
    "properties": {
        "code": {"type": "number"},
        "data": {"type": ["object", "null"]},
        "msg": {"type": "string"}
    }
}

from jsonschema import validate
validate(response.json(), schema=schema)

3.4 测试数据生成工具

为了保证测试数据的唯一性和有效性,我们封装了一系列测试数据生成工具:

手机号生成:生成符合中国大陆手机号格式的随机号码,确保每次生成的手机号都是唯一的,避免因手机号重复导致注册失败。

邮箱生成:生成符合标准格式的随机邮箱地址,邮箱域名可以配置,支持多种邮箱格式。

昵称生成:基于手机号或其他唯一标识生成用户昵称,确保昵称的唯一性。

python 复制代码
import random
import string

def generate_unique_phone():
    """生成唯一的手机号"""
    prefixes = ['138', '139', '150', '151', '152', '158', '159', '186', '187', '188']
    prefix = random.choice(prefixes)
    suffix = ''.join(random.choices(string.digits, k=8))
    return prefix + suffix

def generate_unique_email(phone):
    """生成唯一的邮箱"""
    return f"test_{phone}@example.com"

def generate_unique_nickname(phone):
    """生成唯一的昵称"""
    return f"测试用户_{phone[-4:]}"

四、挑选接口

根据业务需求,挑选以下核心接口进行自动化测试。选择接口的原则是:优先覆盖核心业务流程、高频使用的接口、容易出错的接口。

接口名称 请求方法 接口路径 说明
用户注册 POST /register 支持管理员和普通用户注册,需要提供手机号、邮箱、密码、昵称等信息
发送验证码 GET /verification-code/send 发送手机验证码,用于手机号验证场景
登录 POST /password/login 账号密码登录,返回Token用于后续接口认证
用户列表 GET /base-user/find-list 查询用户列表,支持分页,可按角色筛选
创建奖品 POST /prize/create 创建奖品(含图片上传),需要管理员权限
奖品列表 GET /prize/find-list 查询奖品列表,支持分页
创建活动 POST /activity/create 创建抽奖活动,需要关联奖品和用户
活动列表 GET /activity/find-list 查询活动列表,支持分页
抽奖 POST /draw-prize 执行抽奖操作,记录中奖信息
中奖记录 POST /winning-records/show 查询中奖记录,按活动ID筛选
活动详情 GET /activity-detail/find 查询活动详情,包含奖品和用户信息

五、项目结构

清晰的项目结构是框架可维护性的基础。采用分层设计的思想,将框架划分为测试用例层、业务逻辑层、接口封装层、工具支持层和数据层。

复制代码
lottery_system_ApiAutoTest/
├── cases/                          # 测试用例目录
│   ├── test_register.py            # 用户注册接口测试(30个用例)
│   ├── test_sendcode.py            # 验证码发送接口测试(5个用例)
│   ├── test_admin_login.py         # 管理员登录接口测试(14个用例)
│   ├── test_user_list.py           # 用户列表查询接口测试(3个用例)
│   ├── test_create_prize.py        # 奖品创建接口测试(4个用例)
│   ├── test_prize_list.py          # 奖品列表查询接口测试(6个用例)
│   ├── test_create_activity.py     # 活动创建接口测试(17个用例)
│   ├── test_activity_list.py       # 活动列表查询接口测试(6个用例)
│   ├── test_draw_prize.py          # 抽奖执行接口测试(8个用例)
│   ├── test_winning_records.py     # 中奖记录查询接口测试(3个用例)
│   └── test_activity_detail.py     # 活动详情查询接口测试(2个用例)
├── data/                           # 数据目录
│   ├── data.yaml                   # 测试数据存储文件
│   └── images/                     # 图片资源目录(用于奖品图片上传)
├── utils/                          # 工具类目录
│   ├── request_util.py             # HTTP请求封装
│   ├── yaml_util.py                # YAML文件操作封装
│   ├── phone_util.py               # 手机号生成工具
│   ├── mail_util.py                # 邮箱生成工具
│   └── name_util.py                # 昵称生成工具
└── pytest.ini                      # Pytest配置文件

5.1 Pytest配置文件

pytest.ini文件用于配置Pytest的运行行为:

ini 复制代码
[pytest]
markers =
    order: mark test to run in a specific order
testpaths = cases
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short

六、测试用例设计(带脑图)

针对每个接口,从以下维度设计测试用例,确保测试覆盖的全面性和有效性:

分类 说明 示例
正向测试 正常业务流程,验证接口在正常输入下的行为 正常登录、正常创建活动、正常抽奖
未登录测试 无Token访问需要认证的接口,验证权限控制 期望返回401或相应错误信息
参数缺失测试 必填参数为空,验证参数校验逻辑 用户名为空、密码为空、活动ID为空
参数格式测试 参数格式错误,验证格式校验逻辑 手机号格式错误、邮箱格式错误、日期格式错误
参数长度测试 参数长度不符,验证长度校验逻辑 密码长度不足或超长、昵称超长
业务逻辑测试 业务规则校验,验证业务逻辑正确性 手机号已存在、活动不存在、奖品已抽完
安全性测试 安全防护校验,验证系统安全性 SQL注入、越权访问、敏感信息泄露

6.1 测试用例设计原则

单一职责:每个测试用例只验证一个测试点,避免一个用例包含多个断言。这样当测试失败时,可以快速定位问题所在。

独立性:测试用例之间应该相互独立,不依赖其他测试用例的执行结果。通过数据文件中转的方式实现数据共享,而不是直接依赖。

可重复性:测试用例应该可以重复执行,每次执行的结果应该一致。通过生成唯一的测试数据(如随机手机号)来避免数据冲突。

可维护性:测试用例的代码应该清晰易懂,避免复杂的逻辑。使用参数化来减少重复代码,使用有意义的变量名和函数名。

6.2 测试用例示例(脑图)

七、编写代码

7.1 用户注册接口

用户注册是系统的入口接口,需要验证各种参数组合和业务规则。注册成功后,将用户信息保存到数据文件中,供后续测试使用。

python 复制代码
import pytest
from utils.request_util import Request
from utils.yaml_util import get_yaml, set_yaml
from utils.phone_util import generate_unique_phone
from utils.mail_util import generate_unique_email
from utils.name_util import generate_unique_nickname

class TestRegister:
    url = "/register"
    schema = {
        "type": "object",
        "required": ["code", "data", "msg"],
        "properties": {
            "code": {"type": "number"},
            "data": {"type": ["object", "null"]},
            "msg": {"type": "string"}
        }
    }
    
    @pytest.mark.order(3)
    @pytest.mark.parametrize("identity,password", [("ADMIN", "Whx041223")])
    def test_register_admin_success(self, identity, password):
        phone = generate_unique_phone()
        name = generate_unique_nickname(phone)
        mail = generate_unique_email(phone)
        
        data = {
            "name": name,
            "mail": mail,
            "phoneNumber": phone,
            "password": password,
            "identity": identity
        }
        r = Request().post(url=self.url, json=data)
        validate(r.json(), schema=self.schema)
        assert r.json()["code"] == 200
        assert r.json()["msg"] == "成功"
        
        register_data = {
            "phoneNumber": phone,
            "password": password,
            "identity": identity,
            "name": name,
            "mail": mail
        }
        set_yaml("data/data.yaml", "register.admin.last", register_data)

7.2 登录接口

登录接口用于获取用户Token,Token是后续接口认证的关键。登录成功后,将Token保存到数据文件中。

python 复制代码
import pytest
from utils.request_util import Request
from utils.yaml_util import get_yaml, set_yaml
from jsonschema import validate

class TestAdminLogin:
    url = "/password/login"
    schema = {
        "type": "object",
        "required": ["code", "data", "msg"],
        "properties": {
            "code": {"type": "number"},
            "data": {
                "type": "object",
                "properties": {
                    "token": {"type": "string"},
                    "identity": {"type": "string"}
                }
            },
            "msg": {"type": "string"}
        }
    }
    
    @pytest.fixture
    def login(self):
        return {
            "loginName": get_yaml("data/data.yaml", "register.admin.last.phoneNumber"),
            "password": get_yaml("data/data.yaml", "register.admin.last.password"),
            "mandatoryIdentity": "ADMIN"
        }
    
    @pytest.mark.order(8)
    def test_admin_login_success(self, login):
        data = {
            "loginName": login["loginName"],
            "password": login["password"],
            "mandatoryIdentity": login["mandatoryIdentity"]
        }
        r = Request().post(url=self.url, json=data)
        validate(r.json(), schema=self.schema)
        assert r.json()["code"] == 200
        
        token = {
            "user_token": r.json()["data"]["token"],
            "identity": r.json()["data"]["identity"]
        }
        set_yaml("data/data.yaml", "data", token)

7.3 创建活动接口

创建活动是一个复杂的接口,需要关联奖品和用户数据。活动创建成功后,将活动ID及关联数据保存到数据文件中,供抽奖接口使用。

python 复制代码
import pytest
from datetime import datetime
from utils.request_util import Request
from utils.yaml_util import get_yaml, set_yaml
from jsonschema import validate

class TestCreateActivity:
    url = "/activity/create"
    schema = {
        "type": "object",
        "required": ["code", "data", "msg"],
        "properties": {
            "code": {"type": "number"},
            "data": {
                "type": "object",
                "properties": {
                    "activityId": {"type": "number"}
                }
            },
            "msg": {"type": "string"}
        }
    }
    
    @pytest.mark.order(19)
    def test_create_activity_success(self):
        prize_ids = get_yaml("data/data.yaml", "prize.list.prizeId", default=[])
        users = get_yaml("data/data.yaml", "user.list.normal_users", default=[])
        
        activity_name = f"抽奖测试_{datetime.now().strftime('%Y%m%d%H%M%S')}"
        body = {
            "activityName": activity_name,
            "description": "年会抽奖活动",
            "activityPrizeList": [
                {"prizeId": prize_ids[0], "prizeAmount": 1, "prizeTiers": "FIRST_PRIZE"}
            ],
            "activityUserList": users[:3]
        }
        header = {"user_token": get_yaml("data/data.yaml", "data.user_token")}
        r = Request().post(url=self.url, json=body, headers=header)
        validate(r.json(), schema=self.schema)
        assert r.json()["code"] == 200
        
        set_yaml("data/data.yaml", "activity.create", {
            "activityId": r.json()["data"]["activityId"],
            "activityPrizeList": body["activityPrizeList"],
            "activityUserList": body["activityUserList"]
        })

7.4 抽奖接口

抽奖接口是系统的核心业务接口,需要遍历活动中的所有奖品进行抽奖。每个奖品抽奖成功后,系统会记录中奖信息。

python 复制代码
import pytest
from datetime import datetime
from utils.request_util import Request
from utils.yaml_util import get_yaml
from jsonschema import validate

class TestDrawPrize:
    url = "/draw-prize"
    schema = {
        "type": "object",
        "required": ["code", "data", "msg"],
        "properties": {
            "code": {"type": "number"},
            "data": {"type": ["object", "null"]},
            "msg": {"type": "string"}
        }
    }
    
    @pytest.mark.order(24)
    def test_draw_prize_success(self):
        activity = get_yaml("data/data.yaml", "activity.create", default={})
        prize_list = activity.get("activityPrizeList", [])
        user_list = activity.get("activityUserList", [])
        header = {"user_token": get_yaml("data/data.yaml", "data.user_token")}
        
        for prize in prize_list:
            body = {
                "activityId": activity.get("activityId"),
                "prizeId": prize.get("prizeId"),
                "prizeTiers": prize.get("prizeTiers"),
                "winningTime": datetime.now().strftime("%Y-%m-%dT%H:%M:%S.000Z"),
                "winnerList": user_list[:prize.get("prizeAmount", 1)]
            }
            r = Request().post(url=self.url, json=body, headers=header)
            validate(r.json(), schema=self.schema)
            assert r.json()["code"] == 200

八、数据流转

8.1 数据流转设计思想

在接口自动化测试中,测试数据的管理是一个核心问题。很多接口之间存在数据依赖关系,例如:创建活动需要先获取奖品ID和用户ID,抽奖需要先获取活动ID。如何管理这些数据依赖,是框架设计的关键。

采用"数据文件中转"的设计思想,将测试过程中产生的数据持久化存储到YAML文件中,后续测试从文件中读取所需数据。

这种方式的优点:

解耦测试用例:测试用例之间通过数据文件间接关联,降低了耦合度。每个测试用例只需要关心自己需要什么数据、产出什么数据,不需要关心数据的来源和去向。

支持增量测试:可以单独执行某个测试用例,只要数据文件中存在所需数据即可。例如,如果已经注册过用户,可以直接执行登录测试,不需要重新注册。

便于问题排查:数据文件记录了测试过程中的所有数据,当测试失败时,可以查看数据文件中的数据是否正确,便于定位问题。

支持测试回放:可以使用固定的数据文件进行测试回放,复现问题场景。

8.2 完整数据流转链路

以下是测试用例执行过程中的完整数据流转链路:

复制代码
注册成功 → 保存用户数据(手机号、密码、邮箱、用户ID)
    ↓
登录成功 → 保存Token和身份信息
    ↓
用户列表 → 保存NORMAL用户ID和姓名
    ↓
奖品列表 → 保存奖品ID列表
    ↓
创建活动 → 使用奖品ID和用户数据,保存活动ID及关联数据
    ↓
抽奖 → 使用活动ID、奖品ID、用户数据执行抽奖
    ↓
中奖记录/活动详情 → 使用活动ID查询结果

8.3 测试用例执行顺序

使用pytest-ordering插件控制测试用例执行顺序,确保数据依赖正确。执行顺序的设计原则是:先执行数据生产者,后执行数据消费者。

Order 测试模块 测试内容 数据产出
1-4 注册模块 完成管理员和普通用户注册 用户数据(手机号、密码、邮箱等)
5-6 验证码模块 发送验证码测试
7-8 登录模块 管理员和普通用户登录 Token、身份信息
9-10 用户列表 查询用户列表 NORMAL用户ID和姓名
11-16 奖品模块 创建奖品、获取奖品列表 奖品ID列表
17-21 活动模块 创建活动、获取活动列表 活动ID及关联数据
22-24 抽奖模块 执行抽奖操作 中奖记录
25-30 记录模块 查询中奖记录、活动详情

九、测试报告

9.1 测试用例统计

本项目共设计了98个测试用例,覆盖了11个接口模块。测试用例分布如下:

接口模块 正向用例 异常用例 合计 覆盖场景
注册接口 2 28 30 参数缺失、格式错误、长度校验、唯一性校验
验证码接口 1 4 5 参数缺失、格式错误
登录接口 1 13 14 参数缺失、格式错误、用户不存在、密码错误
用户列表接口 1 2 3 未登录、参数错误
创建奖品接口 1 3 4 未登录、参数缺失
奖品列表接口 1 5 6 未登录、参数错误
创建活动接口 1 16 17 未登录、参数缺失、格式错误、业务规则
活动列表接口 1 5 6 未登录、参数错误
抽奖接口 1 7 8 未登录、参数缺失、业务规则
中奖记录接口 1 2 3 未登录、参数错误
活动详情接口 1 1 2 未登录、参数错误
总计 12 86 98

9.2 异常场景覆盖

本项目覆盖了以下异常场景,确保系统在各种异常输入下都能正确处理:

  • 参数缺失校验:必填参数为空、请求体为空等场景,验证系统对必填参数的校验逻辑
  • 参数格式校验:手机号格式错误、邮箱格式错误、日期格式错误等,验证系统对参数格式的校验逻辑
  • 参数长度校验:字符串超长、数值超范围等场景,验证系统对参数长度的校验逻辑
  • 业务规则校验:数据不存在、数据已存在、状态不正确等,验证系统对业务规则的校验逻辑
  • 权限校验:未登录访问、越权访问等场景,验证系统的权限控制机制
  • 数据唯一性校验:手机号已存在、邮箱已存在等,验证系统对唯一性约束的处理
  • SQL注入测试:验证系统的安全防护是否到位,防止SQL注入攻击

9.3 allure 生成测试报告

allure_results中生成测试用例的结果集,然后在allure_report中生成测试报告,测试报告为html页面,页面示例:

查看页面得到测试通过了 100%,涉及92条测试用例,总耗时13秒左右。

并且可以查看每一条测试用例的执行,且有日志打印,可以查看该次测试的具体情况。

十、优化方向

10.1 短期优化

支持多环境配置:将服务器地址、数据库配置等提取到配置文件中,支持开发环境、测试环境、生产环境的快速切换。可以通过命令行参数或环境变量指定当前环境。

yaml 复制代码
# config.yaml
environments:
  dev:
    host: "http://dev.example.com"
  test:
    host: "http://test.example.com"
  prod:
    host: "http://prod.example.com"

10.2 长期扩展

集成CI/CD流水线:将接口测试集成到持续集成/持续部署流水线中,实现代码提交后自动执行测试。可以与Jenkins、GitLab CI、GitHub Actions等工具集成,在代码合并前自动执行测试,确保代码质量。

添加数据库校验:除了校验接口响应,还可以直接查询数据库验证数据的正确性。例如,创建活动后查询数据库验证活动数据是否正确写入,抽奖后查询数据库验证中奖记录是否正确生成。

支持性能测试:基于现有的接口测试脚本,扩展性能测试功能。可以使用locust等工具进行接口压力测试,验证系统在高并发场景下的性能表现。

十一、总结

本文详细介绍了抽奖系统接口自动化测试框架的设计与实现过程,从项目背景、技术选型、核心模块、测试用例设计、代码实现、数据流转、测试报告等多个方面进行了阐述。

框架设计:采用分层设计思想,将框架划分为测试用例层、业务逻辑层、接口封装层、工具支持层和数据层。各层职责清晰,便于维护和扩展。

核心封装:封装了HTTP请求、YAML数据管理、JSON Schema响应校验等核心功能。通过封装,简化了测试代码的编写,提高了代码的复用性。

测试用例:设计了98个测试用例,覆盖正向测试、异常测试、边界测试等多种场景。测试用例覆盖了参数校验、业务规则、权限控制、安全性等多个维度。

数据流转:通过YAML文件实现测试数据的持久化和跨用例传递,确保测试用例之间的数据依赖正确。数据流转的设计使得测试用例可以独立执行,提高了测试的灵活性。

接口自动化测试是一项需要持续投入和优化的工作。随着业务的发展,接口会不断变化,测试用例也需要相应更新。一个好的测试框架应该具备良好的可维护性和可扩展性,能够快速适应业务的变化。希望本文能够为读者在接口自动化测试实践中提供一些参考和帮助。


项目源码lottery_system_ApiAutoTest

技术交流:欢迎在评论区留言讨论,共同进步!

相关推荐
沪漂阿龙2 小时前
Python 面向对象编程完全指南:从新手到高手的进阶之路
开发语言·python·microsoft
chushiyunen2 小时前
python中的异常处理
开发语言·python
观书喜夜长2 小时前
大模型应用开发学习-基于 LangChain 框架实现的交互式问答脚本
python·学习
章鱼丸-2 小时前
DAY32 官方文档的阅读
python
于慨2 小时前
docker
python
GinoWi2 小时前
Chapter 7 Python中的函数
python
m0_518019482 小时前
使用Seaborn绘制统计图形:更美更简单
jvm·数据库·python
Hommy882 小时前
【剪映小助手-客户端】构建与部署
python·aigc·剪映小助手
GinoWi2 小时前
Chapter 6 Python中的字典
python