☰
ShopXO 商城系统 测试报告
2026/10/1 2:48:39 网站建设 项目流程

目录

一、功能测试

1.1 测试用例总汇表

1.2 代表性用例展示

1.3 缺陷统计

1.4 重点缺陷描述

二、 UI自动化测试

2.1 技术选型

2.2 项目结构

2.3 核心代码实现

(1)Driver 工具类封装

(2)全局 Fixture 配置

(3)测试用例示例

2.4 遇到的问题及解决方案

问题一:类名直接调用实例方法报错

问题二:相对路径写错导致目录创建失败

问题三:创建目录和保存截图路径不一致

2.5 运行结果

三、 性能测试

3.1 测试工具

3.2 测试场景设计

3.3 测试结果

3.4 结果分析

3.5 性能结论

四、接口测试

4.1 测试工具与框架

4.2 框架搭建

4.2.1 项目目录结构

4.2.2 核心模块设计

4.3 测试用例示例

4.3.1 登录接口(参数化 + 异常场景)

4.3.2 商品列表接口(分页参数化)

4.3.3 购物车接口(Base64 编码 + 规格参数)

4.5 运行结果

本次代码存放的gitee仓库:https://gitee.com/sddsfsdf455/shop-xo_auto_test.git


本文是对 ShopXO v6.9.1 开源电商系统的一次完整测试实践,涵盖功能测试、UI 自动化、性能压测和接口自动化四个部分。

项目背景:ShopXO 是一款基于 ThinkPHP 开发的开源 B2C 电商商城系统,包含前台商品浏览、购物车、订单、会员中心,以及后台商品管理、订单管理、用户管理等功能。

测试环境:

  • 前台地址:http://121.43.128.122/
  • 后台地址:http://121.43.128.122/adminhchlav.php
  • 系统版本:ShopXO v6.9.1
  • 测试浏览器:Edge

测试范围:前台商城(首页 / 商品 / 搜索 / 注册登录 / 购物车 / 订单)、后台管理(商品 / 订单 / 用户)、前后台数据一致性、安全越权测试。

测试结论速览:共执行 57 条用例,成功 48 条,成功率 84.2%;发现缺陷 5 个(严重 1、一般 3、轻微 1)。核心浏览和登录功能正常,主要问题集中在第三方服务未配置(支付 / 短信 / 邮件)。

一、功能测试

1.1 测试用例总汇表

模块用例数成功失败阻塞备注
首页4310图片加载失败
商品7700
搜索3300
用户8710
购物车5500
订单4211无支付方式
后台登录与首页6600
后台商品管理5500
后台订单管理3201
前后台数据一致性4202
安全4400
兼容性2200
后台用户管理2200
合计575034成功率 87.7%

1.2 代表性用例展示

正确账号密码登录:

登陆成功:

加入购物车:

操作成功:

后台订单管理:

1.3 缺陷统计

严重程度数量
严重1
一般3
轻微1
合计5

1.4 重点缺陷描述

BUG004(严重):订单结算页无支付方式,无法提交订单:

BUG002/003(一般):手机 / 邮箱注册验证码发送失败:

BUG001(轻微):首页图片加载失败 → 贴截图:

二、 UI自动化测试

2.1 技术选型

UI 自动化测试采用Python + Selenium + pytest技术栈:

  • Selenium:Web 应用自动化测试框架,支持多浏览器驱动
  • pytest:测试框架,提供 fixture 机制、断言和测试报告
  • webdriver-manager:自动管理浏览器驱动,无需手动下载对应版本

2.2 项目结构

ShopXO_auto_test/ ├── common/ │ └── Utils.py # Driver 工具类封装 ├── images/ # 测试截图存放目录 ├── test/ │ ├── test_login_suc.py # 登录成功用例 │ ├── test_login_fail.py # 登录失败用例 │ ├── test_check_elements.py # 元素检查用例 │ ├── test_product_detail.py # 商品详情用例 │ └── test_shopcar_nologin.py# 未登录购物车用例 ├── conftest.py # pytest 全局 fixture └── pytest.ini # pytest 配置文件

2.3 核心代码实现

(1)Driver 工具类封装

在common/Utils.py中封装Driver类,统一管理浏览器驱动的创建和截图功能:

import datetime import os.path import sys from selenium import webdriver from selenium.webdriver.edge.service import Service from webdriver_manager.microsoft import EdgeChromiumDriverManager class Driver: driver = "" def __init__(self): # 创建驱动对象,返回驱动实例 self.driver = webdriver.Edge(service=Service(EdgeChromiumDriverManager().install())) # 隐式等待两秒,防止页面元素加载失败 self.driver.implicitly_wait(2) @staticmethod def screen_shoot(driver): # dirname-->生成放置图片的images子文件夹 dirname = datetime.datetime.now().strftime("%Y-%m-%d") # 因为每天都会实时更新时间 所以会生成多个不同日期的子文件夹 if not os.path.exists("./images/" + dirname): os.mkdir("./images/" + dirname) # 生成图片存放路径和名称 filename = sys._getframe().f_back.f_code.co_name + datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S") + ".png" driver.save_screenshot("./images/" + dirname + "/" + filename)

(2)全局 Fixture 配置

在conftest.py中配置 session 级别的 fixture,整个测试会话只启动一次浏览器,所有用例执行完毕后自动关闭:

import pytest from common.Utils import Driver @pytest.fixture(scope="session") # 整个测试会话只启动一次浏览器 def driver(): driver_util = Driver() yield driver_util.driver # 所有测试跑完后,自动关闭浏览器 driver_util.driver.quit()

(3)测试用例示例

以登录成功用例为例,演示元素定位、操作和截图:

import pytest from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from common.Utils import Driver @pytest.mark.order(2) class Test_login_suc: def test_login_suc(self, driver): url = "http://121.43.128.122/" driver.get(url) driver.find_element(By.CSS_SELECTOR, "body > div.body-content-container > div.body-content-formal-container > div.am-container.slideall > div > div.mod-vip > div.member-login > a.am-btn-primary.btn.am-fl.am-radius").click() # 添加显示等待 灵活 wait = WebDriverWait(driver, 2) wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "body > div.body-content-container > div.body-content-formal-container > div.am-g.user-login-container.am-padding-top-0.am-padding-bottom-0.theme-data-edit-event > div > div.am-container > div.am-u-sm-12.am-u-md-6.am-u-lg-4.user-right-container > div.am-radius-lg.am-background-white > div > div.am-tabs-bd.am-border-0 > div.am-tab-panel.am-active > form"))) # 输入正确的账号密码 driver.find_element(By.CSS_SELECTOR, "body > div.body-content-container > div.body-content-formal-container > div.am-g.user-login-container.am-padding-top-0.am-padding-bottom-0.theme-data-edit-event > div > div.am-container > div.am-u-sm-12.am-u-md-6.am-u-lg-4.user-right-container > div.am-radius-lg.am-background-white > div > div.am-tabs-bd.am-border-0 > div.am-tab-panel.am-active > form > div:nth-child(1) > input").send_keys("11") driver.find_element(By.CSS_SELECTOR, "body > div.body-content-container > div.body-content-formal-container > div.am-g.user-login-container.am-padding-top-0.am-padding-bottom-0.theme-data-edit-event > div > div.am-container > div.am-u-sm-12.am-u-md-6.am-u-lg-4.user-right-container > div.am-radius-lg.am-background-white > div > div.am-tabs-bd.am-border-0 > div.am-tab-panel.am-active > form > div.am-form-group.am-form-group-refreshing.am-margin-top-main.am-form-input-material > input").send_keys("************") # 点击登录 driver.find_element(By.CSS_SELECTOR, "body > div.body-content-container > div.body-content-formal-container > div.am-g.user-login-container.am-padding-top-0.am-padding-bottom-0.theme-data-edit-event > div > div.am-container > div.am-u-sm-12.am-u-md-6.am-u-lg-4.user-right-container > div.am-radius-lg.am-background-white > div > div.am-tabs-bd.am-border-0 > div.am-tab-panel.am-active > form > div.am-form-group.am-form-group-refreshing.am-margin-top-lg.am-padding-0 > button").click() # 检查页面元素是否正常 driver.find_element(By.CSS_SELECTOR, "body > div.body-content-container > div.body-content-formal-container > div.am-container.slideall > div > div.mod-vip > div.m-baseinfo.am-flex.am-flex-items-center > div > span") driver.find_element(By.CSS_SELECTOR, "body > div.body-content-container > div.body-content-formal-container > div.am-container.slideall > div > div.mod-vip > div.m-baseinfo.am-flex.am-flex-items-center > a > img") Driver.screen_shoot(driver)

2.4 遇到的问题及解决方案

在实现截图功能过程中,先后遇到三个问题,记录如下:

问题一:类名直接调用实例方法报错

报错信息:

TypeError: Driver.screen_shoot() missing 1 required positional argument: 'self'

原因分析:screen_shoot()最初定义为实例方法,但测试中通过类名Driver.screen_shoot()直接调用。由于conftest.py的 fixture 返回的是driver_util.driver(WebDriver 实例),而非Driver类实例,无法通过实例调用该方法。

解决方案:将screen_shoot()改为静态方法(@staticmethod),并接收driver参数,调用时传入 WebDriver 实例:Driver.screen_shoot(driver)。

问题二:相对路径写错导致目录创建失败

报错信息:

FileNotFoundError: [WinError 3] 系统找不到指定的路径。: '../images/2026-09-28'

原因分析:代码中使用了../images/(两个点),表示上一级目录下的 images 文件夹。而测试运行时工作目录是项目根目录,images 文件夹就在当前目录下,应该使用./images/(一个点)。

问题三:创建目录和保存截图路径不一致

现象:目录创建成功,但截图文件始终不生成,也不报错。

原因分析:创建目录时用的是./images/,但保存截图时误写为../images/,两处路径不一致。Selenium 的save_screenshot()失败时只返回False,不会抛出异常,因此难以察觉。

解决方案:将创建目录和保存截图的路径统一为./images/。

2.5 运行结果

执行pytest命令运行全部测试用例:

三、 性能测试

3.1 测试工具

本次性能测试采用Apache JMeter 5.5作为压测工具。JMeter 是一款开源的性能测试工具,支持对 HTTP、数据库等多种服务进行压力测试,能够模拟多用户并发访问,并通过聚合报告、响应时间趋势图、吞吐量趋势图等监听器全面采集性能指标。

测试计划中使用了jp@gc - Stepping Thread Group(阶梯线程组)来模拟用户量逐步递增的并发场景,更贴近真实用户逐步涌入系统的访问模式。

3.2 测试场景设计

针对 ShopXO 电商系统的核心高频页面,选取以下三个接口进行并发压测:

序号接口名称说明
1首页系统首页加载,包含商品推荐、轮播图等模块
2商品列表商品分类列表页,包含分页、筛选等数据查询
3商品详情单个商品详情页,包含商品信息、规格、图片等加载

同时配置了以下监听器用于结果采集与分析:

  • jp@gc - Active Threads Over Time:监控活动线程数随时间的变化
  • jp@gc - Response Times Over Time:监控响应时间随时间的变化趋势
  • jp@gc - Transactions per Second:监控每秒事务数(吞吐量)变化
  • 聚合报告:汇总各接口的核心性能指标

3.3 测试结果

本次压测共发起357个请求,全部请求均成功,异常率为0.00%。聚合报告核心数据如下:

接口名称样本数平均响应时间 (ms)中位数 (ms)90% 百分位 (ms)95% 百分位 (ms)99% 百分位 (ms)最小值 (ms)最大值 (ms)异常率吞吐量 (次 / 秒)
首页1274920419482201102213644796170590.00%1.7
商品列表120349329636541761310152552156280.00%1.6
商品详情1103149301250545495628066264440.00%1.5
总体357389533976502822012074552170590.00%4.7

3.4 结果分析

(1)系统稳定性

三个接口的异常率均为 0.00%,说明在当前并发压力下系统能够稳定处理所有请求,未出现服务不可用或报错的情况,系统稳定性良好。

(2)响应时间分析

  • 总体平均响应时间为3895ms(约 3.9 秒),中位数为3397ms,说明半数以上请求能在 3.4 秒内完成。
  • 首页响应时间最长,平均 4920ms,95% 百分位达到 11022ms,最大响应时间 17059ms。原因是首页加载内容较多,包含轮播图、推荐商品、分类导航等多个模块,数据查询和资源加载开销较大。
  • 商品列表平均响应时间 3493ms,表现居中。
  • 商品详情响应时间最短,平均 3149ms,且 99% 百分位仅 6280ms,响应时间波动最小,接口性能最稳定。

(3)吞吐量分析

  • 系统总体吞吐量为4.7 次 / 秒,其中首页 1.7 次 / 秒、商品列表 1.6 次 / 秒、商品详情 1.5 次 / 秒,三个接口吞吐量分布较为均衡。
  • 首页由于响应时间较长,单位时间内完成的请求数相对受限,是影响整体吞吐量的主要瓶颈。

3.5 性能结论

结论:在当前阶梯并发压测场景下,ShopXO 系统核心接口均能正常响应,无异常请求,系统稳定性达标。但整体平均响应时间约 3.9 秒,首页接口响应偏慢(平均近 5 秒),存在一定的性能优化空间。

四、接口测试

4.1 测试工具与框架

本次接口自动化测试基于 Python 生态搭建,核心技术栈如下:

工具 / 库版本用途
pytest8.3.2测试框架,用例管理与执行
requests2.31.0HTTP 请求库,发送接口请求
jsonschema4.23.0响应数据结构校验
PyYAML6.0.1测试数据读写
allure-pytest2.13.5测试报告生成
pytest-order1.3.0用例执行顺序控制

4.2 框架搭建

4.2.1 项目目录结构

ShopXO_api_auto_test/ ├── cases/ # 测试用例(6个测试文件) │ ├── __init__.py │ ├── test_login.py │ ├── test_home_page.py │ ├── test_goods_list.py │ ├── test_goods_detail.py │ └── test_cart.py ├── utils/ # 工具类 │ ├── requests_utils.py │ ├── logging_utils.py │ └── yaml_utils.py ├── data/ # 测试数据 ├── logs/ # 三级日志(all/info/err) ├── allure-report/ # Allure报告 ├── allure-results/ # Allure结果数据 ├── conftest.py # 全局Fixture └── pytest.ini # pytest配置

4.2.2 核心模块设计

(1)请求封装模块(requests_utils.py)

封装统一的请求入口,基于requests.Session()实现 Cookie 自动管理,同时集成日志记录:

import requests from utils.logging_utils import Logger host = "http://121.43.128.122/?" class Requests: def __init__(self): # 创建 Session 对象,自动保存和携带 Cookie self.session = requests.Session() self.log = Logger.getLogger() def get(self, url, **kwargs): self.log.info(f"准备发起的GET请求,utl: {url}") self.log.info(f"接口信息:{kwargs}") r = self.session.get(url, **kwargs) self.log.info(f"接口返回状态码为:{r.status_code}") self.log.info(f"接口返回信息: {r.text}") return r def post(self, url, **kwargs): self.log.info(f"准备发起的POST请求,url: {url}") self.log.info(f"接口信息:{kwargs}") r = self.session.post(url, **kwargs) self.log.info(f"接口返回状态码为:{r.status_code}") self.log.info(f"接口返回信息: {r.text}") return r

(2)日志模块(logging_utils.py)

实现日志分级存储,通过自定义 Filter 精确控制各级别日志的输出:

import logging import os import time class info_filter(logging.Filter): def filter(self, record): return record.levelno == logging.INFO class err_filter(logging.Filter): def filter(self, record): return record.levelno == logging.ERROR class Logger: # 创建日志类 logger = None # 使用类方法 把日志记录器挂在Logger类上 全局共同使用 @classmethod def getLogger(cls): if cls.logger is None: # 创建日志记录器对象 cls.logger = logging.getLogger("ShopXO") # 设置日志最低等级为DEBUG cls.logger.setLevel(level=logging.DEBUG) """ 文件存放格式: logs 2026-9-29.log 2026-9-29_info.log 2026-9-29_err.log """ # 创建logs文件夹 Logs_Path = "./logs/" if not os.path.exists(Logs_Path): os.mkdir(Logs_Path) # 生成实时时间 now = time.strftime("%Y-%m-%d") # 文件名称 以及文件存放路径 log_name = Logs_Path + now + ".log" log_info_name = Logs_Path + now + "_info.log" log_err_name = Logs_Path + now + "_err.log" # 创建一个日志写入器,设置日志文件写入位置 all_handler = logging.FileHandler(filename=log_name, encoding="utf-8") info_handler = logging.FileHandler(filename=log_info_name, encoding="utf-8") err_handler = logging.FileHandler(filename=log_err_name, encoding="utf-8") # 创建日志格式器,设置日志输出格式 formatter = logging.Formatter( "%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s:%(lineno)d)] - %(message)s" ) # 将格式器添加到写入器中 all_handler.setFormatter(formatter) info_handler.setFormatter(formatter) err_handler.setFormatter(formatter) # 添加日志过滤器 精确捕捉指定日志级别的文件 info_handler.addFilter(info_filter()) err_handler.addFilter(err_filter()) # 添加日志写入器到日志记录器中 # 日志记录器使用日志写入器处理日志信息 cls.logger.addHandler(all_handler) cls.logger.addHandler(info_handler) cls.logger.addHandler(err_handler) return cls.logger

(3)全局 Fixture 配置(conftest.py)

通过scope="session"的 fixture 实现全局共享一个 Requests 实例,并自动登录获取登录态:

import pytest from utils.requests_utils import Requests @pytest.fixture(scope="session") def req(): req = Requests() return req

4.3 测试用例示例

4.3.1 登录接口(参数化 + 异常场景)

登录接口采用参数化方式覆盖正常登录和多种异常场景:

import pytest from jsonschema.validators import validate from utils.requests_utils import host @pytest.mark.order(1) class TestLogin: url = host + "s=user/login&system_type=default" schema = { "type": "object", "properties": { "msg": { "type": "string" }, "code": { "type": "number" }, "data": { "type": ["object", "string"], "properties": { "body_html": { "type": "string" }, "user_id": { "type": "string" } }, "required": [ "body_html", "user_id" ] } }, "required": [ "msg", "code", "data" ] } @pytest.mark.parametrize("login", [ { "accounts": ***, "pwd": ******* } ]) def test_login_suc(self, login, req): params = { "accounts": login["accounts"], "pwd": login["pwd"], "type": "username" } r = req.post(url=self.url, params=params) validate(instance=r.json(), schema=self.schema) assert r.json()["code"] == 0 assert r.json()["msg"] == "登录成功" assert r.json()["data"]["user_id"] == "20" @pytest.mark.parametrize("login", [ { "accounts": 111, "pwd": 1111111111, "type": "username" }, { "accounts": 1111111111111, "pwd": 1111111111, "type": "username" }, { "accounts": "", "pwd": 1111111111, "type": "username" }, { "accounts": 11, "pwd": "", "type": "username" }, { "accounts": 11, "pwd": 1111111111, "type": "" }, { "accounts": 11, "pwd": 1111111111, "type": "qe2313##" } ]) def test_login_fail(self, login, req): params = { "accounts": login["accounts"], "pwd": login["pwd"], } r = req.post(url=self.url, params=params) validate(instance=r.json(), schema=self.schema) assert r.json()["data"] == "" assert r.json()["code"] != 0
4.3.2 商品列表接口(分页参数化)

商品列表接口通过参数化验证不同分页参数的正确性:

import pytest from jsonschema.validators import validate from utils.requests_utils import host class Test_goods_list: url = host + "s=category/datalist&system_type=default" schema = { "type": "object", "properties": { "msg": { "type": "string" }, "code": { "type": "number" }, "data": { "type": "object", "properties": { "page_start": { "type": "number" }, "page_size": { "type": "number" }, "page": { "type": "number" }, "page_total": { "type": "number" }, "total": { "type": "number" }, "data": { "type": "string" } }, "required": [ "page_start", "page_size", "page", "page_total", "total", "data" ] } }, "required": [ "msg", "code", "data" ] } @pytest.mark.parametrize("data", [ { "category_id": 2, "page": 1, "page_size": 10 } ]) def test_right_data(self, req, data): params = { "category_id": data["category_id"], "page": data["page"], "page_size": data["page_size"] } r = req.post(url=self.url, params=params) validate(instance=r.json(), schema=self.schema) assert r.json()["msg"] == "处理成功" assert r.json()["code"] == 0 assert r.json()["data"]["page"] == 1 @pytest.mark.parametrize("page, page_size", [ (1, 10), (2, 10), (1, 20), (3, 5), ]) # 商品列表分页 def test_goods_list_pagination(self, req, page, page_size): params = { "category_id": 2, "page": page, "page_size": page_size } r = req.get(url=self.url, params=params) validate(instance=r.json(), schema=self.schema) assert r.json()["code"] == 0 def test_goods_list_false_id(self, req): params = { "category_id": 66666666, "page": 2, "page_size": 10 } r = req.get(url=self.url, params=params) validate(instance=r.json(), schema=self.schema) assert r.json()["msg"] == "没有相关数据" assert r.json()["code"] == -1 def test_goods_list_false_page(self, req): params = { "category_id": 2, "page": 6666666666666, "page_size": 10 } r = req.get(url=self.url, params=params) validate(instance=r.json(), schema=self.schema) assert r.json()["data"]["page_total"] == 1 # 总共就1页 assert r.json()["code"] == 0 def test_goods_list_false_page_size(self, req): params = { "category_id": 2, "page": 2, "page_size": 666666666 } r = req.get(url=self.url, params=params) validate(instance=r.json(), schema=self.schema) assert r.json()["msg"] == "处理成功" assert r.json()["code"] == 0
4.3.3 购物车接口(Base64 编码 + 规格参数)

购物车加入接口的商品数据需要进行 Base64 编码后传输,且需包含完整的商品规格信息:

import base64 import json import pytest from jsonschema.validators import validate from utils.requests_utils import host class Test_cart: url = host + "s=cart/save&system_type=default" schema = { "type": "object", "properties": { "msg": { "type": "string" }, "code": { "type": "number" }, "data": { "type": ["object","string"], "properties": { "total_price": { "type": "string" }, "buy_number": { "type": "string" } }, "required": [ "total_price", "buy_number" ] } }, "required": [ "msg", "code", "data" ] } def test_cart_suc(self, req): goods_list = [{ "goods_id": "11", "stock": 1, "spec": [ {"type": "尺码", "value": "L"} ] }] goods_data = base64.b64encode(json.dumps(goods_list).encode("utf-8")).decode("utf-8") data = {"goods_data": goods_data} r = req.post(url=self.url, data=data) validate(instance=r.json(), schema=self.schema) assert r.json()["msg"] == "加入成功" assert r.json()["code"] == 0 def test_cart_wrong_id(self, req): goods_list = [{ "goods_id": "999999", "stock": 1, "spec": [ {"type": "尺码", "value": "L"} ] }] goods_data = base64.b64encode(json.dumps(goods_list).encode("utf-8")).decode("utf-8") data = {"goods_data": goods_data} r = req.post(url=self.url, data=data) validate(instance=r.json(), schema=self.schema) assert r.json()["msg"] == "1、商品不存在或已删除" assert r.json()["code"] == -2 assert r.json()["data"] == "" def test_cart_wrong_stock(self, req): goods_list = [{ "goods_id": "11", "stock": 999999, "spec": [ {"type": "尺码", "value": "L"} ] }] goods_data = base64.b64encode(json.dumps(goods_list).encode("utf-8")).decode("utf-8") data = {"goods_data": goods_data} r = req.post(url=self.url, data=data) validate(instance=r.json(), schema=self.schema) assert r.json()["code"] == -1 assert r.json()["data"] == ""

4.5 运行结果

本次接口自动化测试共覆盖 5 个核心模块,执行 20+ 个测试用例,整体通过率 100% 。测试结果统计如下:

模块用例数通过失败通过率
用户登录770100%
首页 / UEditor 配置550100%
商品列表880100%
商品详情550100%
购物车330100%
合计28280100%

测试结论:ShopXO 系统核心接口功能正常,异常处理较为完善,登录、商品浏览等主流程接口稳定性达标。购物车模块因规格参数复杂未完全覆盖,后续可补充完善。测试框架基于 pytest + requests 搭建,具备良好的可扩展性和可维护性。

Allure 测试报告总览:

各模块用例执行详情:

本次代码存放的gitee仓库:https://gitee.com/sddsfsdf455/shop-xo_auto_test.git

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询