小林甄选接口自动化项目架构介绍

79 阅读8分钟

一、项目介绍

本项目基于 Python + pytest + sqlalchemy + requests + allure + jsonpath + yaml + Jenkins + Linux 技术栈,构建了一个接口自动化测试框架,用于测试在线购物商城网站的功能,包括:

  • 用户注册
  • 用户登录
  • 商品上架/下架
  • 下单
  • 支付等相关功能

二、项目结构说明

以下是项目目录结构:

project-root/                          # 项目根目录
├─ base/                               # 基础类封装(核心功能)
│  ├─ api_request.py                   # 接口请求基类(封装requests)
│  ├─ test_case_tools.py               # 测试用例工具类(如数据处理、断言)
│  └─ driver.py                        # 浏览器驱动封装(可选,UI自动化用)
├─ common/                             # 公共方法封装(可复用工具)
│  ├─ log_utils.py                     # 日志处理工具
│  ├─ excel_parser.py                  # Excel数据解析工具
│  ├─ yaml_parser.py                   # YAML数据解析工具
│  └─ db_operation.py                  # 数据库操作工具(如MySQL)
├─ conf/                               # 全局配置目录
│  ├─ config.ini                       # 环境配置文件(API地址、账号等)
│  ├─ allure_config.yml                # Allure报告自定义配置
│  └─ env.py                           # 环境变量管理文件
├─ data/                               # 测试数据目录
│  ├─ api_data/                        # 接口测试数据
│  │  ├─ login_data.yaml               # 登录接口测试用例数据
│  │  └─ order_data.xlsx               # 订单接口Excel数据
│  └─ ui_data/                         # UI自动化测试数据(可选)
│     └─ page_elements.yaml            # 页面元素定位数据
├─ logs/                               # 测试日志目录(自动生成)
│  └─ test_20231001.log                # 按日期命名的日志文件
├─ report/                             # 测试报告目录
│  ├─ allure_html/                     # Allure交互式报告(自动生成)
│  ├─ tm_report/                       # TMReport表格报告(自动生成)
│  └─ report_config.py                 # 报告生成配置文件
├─ testcase/                           # 测试用例目录
│  ├─ api_test/                        # 接口测试用例
│  │  ├─ test_login.py                 # 登录接口测试类
│  │  └─ test_order.py                 # 订单接口测试类
│  └─ ui_test/                         # UI自动化测试用例(可选)
│     └─ test_homepage.py              # 首页UI测试类
├─ venv/                               # 虚拟环境目录(自动生成)
├─ conftest.py                         # pytest全局钩子文件(固定名称)
├─ environment.xml                     # Allure报告环境信息文件
├─ extract.yaml                        # 接口依赖参数存储文件
├─ pytest.ini                          # pytest配置文件(固定名称)
├─ requirements.txt                    # 第三方库依赖清单
└─ run.py                              # 主程序入口(执行测试和生成报告)

三、核心代码

1. 程序入口 run.py

代码

import shutil
import pytest
import os
import webbrowser
from conf.setting import REPORT_TYPE

if __name__ == '__main__':
    if REPORT_TYPE == 'allure':
        pytest.main(
            ['-s', '-v', '--alluredir=./report/temp', './testcase', '--clean-alluredir',
             '--junitxml=./report/results.xml'])
        shutil.copy('./environment.xml', './report/temp')
        os.system(f'allure serve ./report/temp')
    elif REPORT_TYPE == 'tm':
        pytest.main(['-vs', '--pytest-tmreport-name=testReport.html', '--pytest-tmreport-path=./report/tmreport'])
        webbrowser.open_new_tab(os.getcwd() + '/report/tmreport/testReport.html')

代码说明

主程序入口
if __name__ == '__main__':
  • 标准 Python 写法,确保脚本作为主程序运行时执行以下逻辑,防止被导入时意外执行。
Allure 报告处理逻辑
if REPORT_TYPE == 'allure':
    pytest.main(
        ['-s', '-v', '--alluredir=./report/temp', './testcase', '--clean-alluredir',
         '--junitxml=./report/results.xml'])
参数说明
参数含义
-s输出所有打印信息(不屏蔽 stdout)
-v显示详细测试结果
--alluredir=./report/temp将 Allure 报告数据保存到指定目录
./testcase指定测试用例所在目录
--clean-alluredir清空之前的报告数据
--junitxml=./report/results.xml生成 JUnit XML 格式测试结果文件
shutil.copy('./environment.xml', './report/temp')
  • 复制环境信息文件 environment.xml 到报告目录,供 Allure 报告显示测试环境信息。
os.system(f'allure serve ./report/temp')
  • 使用 Allure CLI 启动本地服务器,自动在浏览器中打开报告页面。

2. 测试用例 testcase

商务管理代码示例

import allure
import pytest
from common.readyaml import get_testcase_yaml
from base.apiutil_business import RequestBase
from base.generateId import m_id, c_id

@allure.feature(next(m_id) + '电子商务管理系统(业务场景)')
class TestEBusinessScenario:

    @allure.story(next(c_id) + '商品列表到下单支付流程')
    @pytest.mark.parametrize('case_info', get_testcase_yaml('./testcase/Business interface/BusinessScenario.yml'))
    def test_business_scenario(self, case_info):
        allure.dynamic.title(case_info['baseInfo']['api_name'])
        RequestBase().specification_yaml(case_info)

代码说明

参数化装饰器
@pytest.mark.parametrize('case_info', get_testcase_yaml('./testcase/Business interface/BusinessScenario.yml'))
  • 实现参数化测试,读取 YAML 文件中的测试数据,每组数据触发一次测试。
  • case_info 是每组测试数据的变量名,get_testcase_yaml 返回测试用例列表。
示例 YAML 文件
- baseInfo:
    api_name: 获取商品列表
    method: GET
    url: /product/list
- baseInfo:
    api_name: 提交订单
    method: POST
    url: /order/create
测试方法
def test_business_scenario(self, case_info):
  • case_info 是 YAML 文件中的单条测试用例数据。
  • 方法为每个参数化数据执行一次。
动态设置 Allure 标题
allure.dynamic.title(case_info['baseInfo']['api_name'])
  • 使用 YAML 文件中的 api_name 设置 Allure 报告的测试用例标题。
执行接口请求
RequestBase().specification_yaml(case_info)
  • 调用 RequestBasespecification_yaml 方法,发送 HTTP 请求并验证响应。

3. 接口测试 specification_yaml 方法

代码

def specification_yaml(self, base_info, test_case):
    """
    接口请求处理基本方法
    :param base_info: yaml文件里面的baseInfo
    :param test_case: yaml文件里面的testCase
    :return:
    """
    try:
        params_type = ['data', 'json', 'params']
        url_host = self.conf.get_section_for_data('api_envi', 'host')
        api_name = base_info['api_name']
        allure.attach(api_name, f'接口名称:{api_name}', allure.attachment_type.TEXT)
        url = url_host + base_info['url']
        allure.attach(api_name, f'接口地址:{url}', allure.attachment_type.TEXT)
        method = base_info['method']
        allure.attach(api_name, f'请求方法:{method}', allure.attachment_type.TEXT)
        header = self.replace_load(base_info['header'])
        allure.attach(api_name, f'请求头:{header}', allure.attachment_type.TEXT)
        cookie = None
        if base_info.get('cookies') is not None:
            cookie = eval(self.replace_load(base_info['cookies']))
        case_name = test_case.pop('case_name')
        allure.attach(api_name, f'测试用例名称:{case_name}', allure.attachment_type.TEXT)
        val = self.replace_load(test_case.get('validation'))
        test_case['validation'] = val
        validation = eval(test_case.pop('validation'))
        extract = test_case.pop('extract', None)
        extract_list = test_case.pop('extract_list', None)
        for key, value in test_case.items():
            if key in params_type:
                test_case[key] = self.replace_load(value)
        file, files = test_case.pop('files', None), None
        if file is not None:
            for fk, fv in file.items():
                allure.attach(json.dumps(file), '导入文件')
                files = {fk: open(fv, mode='rb')}
        res = self.run.run_main(name=api_name, url=url, case_name=case_name, header=header, method=method,
                                file=files, cookies=cookie, **test_case)
        status_code = res.status_code
        allure.attach(self.allure_attach_response(res.json()), '接口响应信息', allure.attachment_type.TEXT)
        try:
            res_json = json.loads(res.text)
            if extract is not None:
                self.extract_data(extract, res.text)
            if extract_list is not None:
                self.extract_data_list(extract_list, res.text)
            self.asserts.assert_result(validation, res_json, status_code)
        except JSONDecodeError as js:
            logs.error('系统异常或接口未请求!')
            raise js
        except Exception as e:
            logs.error(e)
            raise e
    except Exception as e:
        raise e

方法解析

功能

处理 YAML 文件中的接口测试用例,包括参数构造、变量替换、请求发送、响应断言和数据提取。

步骤
  1. 基础配置

    • 定义请求参数类型:params_type = ['data', 'json', 'params']
    • 获取主机地址:url_host = self.conf.get_section_for_data('api_envi', 'host')
  2. 提取接口信息

    • 获取接口名称、URL、方法、请求头,附加到 Allure 报告。
  3. 处理 Cookie

    • 如果存在 cookies,进行变量替换并转换为字典。
  4. 提取测试用例名称

    • test_case 提取 case_name 并附加到报告。
  5. 处理断言

    • 替换断言表达式中的变量,转换为可执行规则。
  6. 处理请求参数

    • datajsonparams 类型参数进行变量替换。
  7. 处理文件上传

    • 如果存在 files,读取文件并附加到报告。
  8. 发送请求

    • 调用 run_main 发送请求,记录状态码和响应。
  9. 处理响应与断言

    • 转换响应为 JSON,提取数据,执行断言。
  10. 异常处理

    • 捕获并记录异常,确保测试框架识别失败。
示例 YAML
baseInfo:
  api_name: 登录接口
  url: /login
  method: POST
  header:
    Content-Type: application/json
  cookies: null
testCase:
  - case_name: 正常登录
    json:
      username: admin
      password: ${get_password()}
    validation:
      code == 200 and msg == "success"
    extract:
      token: $.data.token

4. 断言判断

相等断言模式

def equal_assert(self, expected_results, actual_results, status_code=None):
    """
    相等断言模式
    :param expected_results: 预期结果,yaml文件validation值
    :param actual_results: 接口实际响应结果
    :return:
    """
    flag = 0
    if isinstance(actual_results, dict) and isinstance(expected_results, dict):
        common_keys = list(expected_results.keys() & actual_results.keys())[0]
        new_actual_results = {common_keys: actual_results[common_keys]}
        eq_assert = operator.eq(new_actual_results, expected_results)
        if eq_assert:
            logs.info(f"相等断言成功:接口实际结果:{new_actual_results},等于预期结果:" + str(expected_results))
            allure.attach(f"预期结果:{str(expected_results)}\n实际结果:{new_actual_results}", '相等断言结果:成功',
                          attachment_type=allure.attachment_type.TEXT)
        else:
            flag += 1
            logs.error(f"相等断言失败:接口实际结果{new_actual_results},不等于预期结果:" + str(expected_results))
            allure.attach(f"预期结果:{str(expected_results)}\n实际结果:{new_actual_results}", '相等断言结果:失败',
                          attachment_type=allure.attachment_type.TEXT)
    else:
        raise TypeError('相等断言--类型错误,预期结果和接口实际响应结果必须为字典类型!')
    return flag
说明
  • 功能:验证实际结果的字段值是否完全等于预期值。

  • 场景:检查 code 是否为 200,或某个字段是否匹配预期。

  • 逻辑

    • 提取公共 key,构造新的实际结果字典。
    • 使用 operator.eq 进行深度比较。
    • 记录成功/失败日志并附加到 Allure 报告。

包含断言模式

def contains_assert(self, value, response, status_code):
    """
    字符串包含断言模式,断言预期结果的字符串是否包含在接口的响应信息中
    :param value: 预期结果,yaml文件的预期结果值
    :param response: 接口实际响应结果
    :param status_code: 响应状态码
    :return: 返回结果的状态标识
    """
    flag = 0
    for assert_key, assert_value in value.items():
        if assert_key == "status_code":
            if assert_value != status_code:
                flag += 1
                allure.attach(f"预期结果:{assert_value}\n实际结果:{status_code}", '响应代码断言结果:失败',
                              attachment_type=allure.attachment_type.TEXT)
                logs.error("contains断言失败:接口返回码【%s】不等于【%s】" % (status_code, assert_value))
        else:
            resp_list = jsonpath.jsonpath(response, "$..%s" % assert_key)
            if isinstance(resp_list[0], str):
                resp_list = ''.join(resp_list)
            if resp_list:
                assert_value = None if assert_value.upper() == 'NONE' else assert_value
                if assert_value in resp_list:
                    logs.info("字符串包含断言成功:预期结果【%s】,实际结果【%s】" % (assert_value, resp_list))
                else:
                    flag = flag + 1
                    allure.attach(f"预期结果:{assert_value}\n实际结果:{resp_list}", '响应文本断言结果:失败',
                                  attachment_type=allure.attachment_type.TEXT)
                    logs.error("响应文本断言失败:预期结果为【%s】,实际结果为【%s】" % (assert_value, resp_list))
    return flag
说明
  • 功能:验证响应中是否包含预期值,支持状态码和 JSON 字段检查。

  • 场景:验证 msg 是否包含 "登录成功",或状态码是否为 200

  • 逻辑

    • 遍历断言规则,分别处理状态码和响应内容。
    • 使用 jsonpath 提取字段值,判断是否包含预期值。
    • 记录日志并附加到 Allure 报告。

四、项目结语

本项目基于 pytest 和 Allure 构建了一套稳定、可扩展的接口自动化测试框架,适用于电商系统业务流程测试。通过数据驱动、变量替换、断言机制和报告可视化,显著提升测试效率与质量。未来将持续优化,增强通用性和智能化水平。