全栈测试实战:基于Spring Boot图书管理系统的环境部署与接口自动化测试
1. 项目概述:一个全栈测试工程师的实战演练场
最近在梳理自己的技术栈,想找一个能串联起多个技能点的实战项目。一个完整的图书管理系统(LMS)测试,恰好成了我的“磨刀石”。它不像一个简单的登录功能测试,而是涵盖了从底层环境搭建、手工探索性测试,到接口功能验证,再到自动化脚本编写的完整流程。我选择在 Ubuntu 这个经典的 Linux 发行版上进行,一方面是因为它作为服务器环境的普及性,另一方面也是想脱离 Windows 的舒适区,在更贴近生产环境的情况下操作。整个项目,我把它看作一次“全链路”的测试实战,目标不仅仅是跑通几个用例,而是要深入理解一个 Web 应用从部署到测试的每一个环节,并形成一套可复用、可扩展的测试策略。如果你是一名测试工程师、DevOps 或是对质量保障感兴趣的后端开发,这个实战过程或许能给你带来一些启发。
2. 环境准备与系统部署:打造稳固的测试基石
任何实战的第一步,都是搭建一个稳定、可控的测试环境。对于这个 LMS 项目,环境准备本身就包含了许多值得细说的知识点。
2.1 Ubuntu 基础环境配置
我使用的是 Ubuntu 22.04 LTS 版本,长期支持版能保证环境的稳定性。首先从官网下载 ISO 镜像,在 VMware 虚拟机中完成安装。这里有几个关键点需要注意:虚拟机的网络我选择了“桥接模式”,这样虚拟机可以获得一个独立的局域网 IP,方便后续主机访问其 Web 服务;磁盘分配建议不少于 40GB,并选择“将虚拟磁盘存储为单个文件”,管理起来更方便。
系统安装完成后,第一件事就是更新软件源并升级现有包:
sudo apt update && sudo apt upgrade -y这个命令会同步最新的软件包列表并升级所有可升级的包,是保证系统安全性和软件兼容性的基础操作。
接下来,安装一些必备的工具。curl和wget用于从网络下载文件,vim或nano是文本编辑器,net-tools包含了ifconfig等网络诊断工具(虽然ip命令更现代,但ifconfig更直观)。
sudo apt install -y curl wget vim net-tools注意:在服务器环境下,我强烈建议禁用图形界面以节省资源。如果你安装的是桌面版,可以切换到多用户文本模式:
sudo systemctl set-default multi-user.target,然后重启。需要图形界面时再临时用startx命令启动。
2.2 LMS 系统部署方案选型
图书管理系统有很多现成的开源项目,如 Koha、Evergreen 等,但它们通常比较庞大,依赖复杂。为了聚焦测试本身,我选择了一个相对轻量级但结构清晰的 Java Web 项目,它基于 Spring Boot + MyBatis + MySQL 架构,提供了清晰的前后端分离接口。这个选择基于以下几点考量:首先,Spring Boot 是当前企业级 Java 开发的事实标准,其接口规范(RESTful)具有代表性;其次,项目结构清晰,数据库表设计完整,包含了用户、图书、借阅、归还等核心业务实体,非常适合作为测试对象。
部署过程分为几个步骤:
- 安装 Java 环境:LMS 需要 JDK 8 或以上版本。
sudo apt install -y openjdk-11-jdk java -version # 验证安装 - 安装与配置 MySQL:
登录 MySQL,创建项目专用的数据库和用户:sudo apt install -y mysql-server sudo mysql_secure_installation # 运行安全脚本,设置 root 密码并移除匿名用户等CREATE DATABASE lms_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'lms_user'@'%' IDENTIFIED BY 'YourStrongPassword123!'; GRANT ALL PRIVILEGES ON lms_db.* TO 'lms_user'@'%'; FLUSH PRIVILEGES;实操心得:生产环境务必使用强密码,并将
'%'替换为具体的应用服务器 IP 以收紧权限。这里为了测试方便,允许了所有主机连接。 - 部署应用:将打包好的 Spring Boot Jar 文件上传至服务器,例如放在
/opt/lms目录下。使用nohup命令在后台启动:
使用cd /opt/lms nohup java -jar lms-application.jar --server.port=8080 > lms.log 2>&1 &tail -f lms.log可以实时查看启动日志,确认没有报错且看到类似 “Started Application in X seconds” 的提示,说明服务已成功运行。 - 验证服务:在服务器本机使用
curl测试,或在宿主机浏览器访问http://<ubuntu_ip>:8080,应该能看到 LMS 的登录页面或 API 文档(如 Swagger UI)界面。
3. 手工探索性测试:像用户一样思考,像侦探一样挖掘
在自动化之前,深入的手工测试不可或缺。它能帮助测试人员快速理解业务、熟悉系统,并发现那些自动化脚本难以捕获的交互逻辑和用户体验问题。我对 LMS 的手工测试主要围绕核心业务流程展开。
3.1 业务流程遍历与功能点拆解
我首先将 LMS 的核心业务拆解为几个主要模块:用户认证、图书信息管理、借阅流程、归还与续借、查询与统计。针对每个模块,设计正向和反向的测试场景。
例如,在“借阅流程”中:
- 正向场景:注册用户登录 -> 查询图书(支持书名、作者、ISBN等多条件)-> 查看图书详情(库存应大于0)-> 执行借阅 -> 验证“我的借阅”列表中出现该记录,且图书库存减1。
- 反向场景:
- 未登录用户尝试借阅,应跳转至登录页或返回明确的未授权错误。
- 借阅库存为0的图书,系统应提示“库存不足”。
- 用户已借阅该书且未归还,再次借阅应提示“您已借阅该图书”。
- 用户存在超期未还记录或已达借阅上限,尝试借阅新书应被阻止并给出相应提示。
测试时,我使用浏览器的开发者工具(F12)密切关注网络请求(Network 标签页)和前端控制台(Console 标签页)。任何一个操作触发的 API 调用、请求参数、响应状态码和返回数据,都一目了然。这为后续的接口测试提供了最直接的依据。
3.2 边界值、异常与兼容性测试
这是体现测试工程师功力的地方。我不仅关注“正确路径”,更关注系统在“刁难”下的表现。
- 边界值:输入框是重灾区。在“添加图书”时,书名、作者名的长度限制是多少?输入恰好等于最大长度、最大长度+1、为空、全空格时,系统的处理是否合理?ISBN 号格式校验是否严格?
- 异常操作:快速连续点击“借阅”按钮,系统是否做了防重提交处理?借阅过程中突然关闭浏览器或断开网络,事务状态是否一致?是否会生成脏数据?
- 数据兼容性:从 Excel 批量导入图书信息时,如果单元格包含特殊字符(如单引号、换行符)、中文、超长文本,导入功能是否能正确处理?
- 浏览器兼容性:虽然项目可能是前后端分离,但前端页面在不同浏览器(Chrome, Firefox, Edge)下的布局和基本交互是否一致?某些 CSS 或 JS 特性是否有兼容性问题?
手工测试阶段,我习惯用 Markdown 或 Excel 简单记录发现的问题、复现步骤和当时的猜想。这个记录不是最终的报告,而是帮助自己理清思路,并为后续编写自动化测试用例提供最真实的素材。
4. 接口测试:聚焦数据交换与业务逻辑的核心
当手工测试对业务逻辑和接口有了基本了解后,就可以进入更高效、更精准的接口测试阶段。接口测试跳过了前端界面,直接验证后端 API 的准确性、健壮性和性能,是自动化测试的主要阵地。
4.1 接口文档分析与测试用例设计
我首先查阅了后端提供的 Swagger UI 文档(访问http://<ip>:8080/swagger-ui.html),它清晰地列出了所有 API 端点、请求方法、参数和响应模型。如果没有 Swagger,就需要通过分析前端网络请求或与开发沟通来整理接口文档。
以“借阅图书”接口为例:
- 接口:
POST /api/borrow - 请求头:
Content-Type: application/json,Authorization: Bearer <jwt_token> - 请求体:
{“bookId”: 123, “userId”: 456} - 预期响应:成功时返回
{“code”: 200, “message”: “借阅成功”, “data”: {…}},失败时返回相应的错误码和消息。
基于此,我设计了一系列测试用例,并用表格管理起来:
| 用例ID | 测试目的 | 请求参数 | 预期结果 | 测试类型 |
|---|---|---|---|---|
| IT_BORROW_001 | 正常借阅 | 有效 bookId, userId,且库存>0 | HTTP 200,返回成功信息 | 正向测试 |
| IT_BORROW_002 | 借阅库存为0的图书 | bookId 对应库存为0 | HTTP 400/409,提示库存不足 | 反向测试 |
| IT_BORROW_003 | 未授权访问 | 不传或传错误的 Token | HTTP 401 | 安全测试 |
| IT_BORROW_004 | 参数类型错误 | bookId 传字符串 “abc” | HTTP 400,参数校验失败 | 异常测试 |
| IT_BORROW_005 | 重复借阅同一本书 | 短时间内重复发送同一请求 | HTTP 400/409,提示已借阅 | 幂等性测试 |
4.2 使用 Postman 进行高效的手动接口测试
在编写自动化脚本前,我使用 Postman 对所有关键接口进行手动验证和调试。Postman 的优势在于可以方便地管理环境变量(如base_url,token)、组织集合(Collection)、编写前置脚本(Pre-request Script)和测试断言(Tests)。
我的典型工作流是:
- 创建环境变量
base_url为http://<ubuntu_ip>:8080。 - 创建一个“用户认证”请求,登录成功后,在 Tests 标签页编写脚本,将返回的 token 保存到环境变量:
if (pm.response.code === 200) { var jsonData = pm.response.json(); pm.environment.set("access_token", jsonData.data.token); } - 创建“借阅图书”等业务请求,在 Authorization 标签页选择 “Bearer Token”,值填入
{{access_token}}。 - 在 Tests 标签页为每个请求编写断言,验证状态码和关键响应字段:
pm.test("Status code is 200", function () { pm.response.to.have.status(200); }); pm.test("Response has success message", function () { var jsonData = pm.response.json(); pm.expect(jsonData.message).to.eql("借阅成功"); }); - 使用 Postman 的 Collection Runner 批量运行一组接口测试,并查看汇总报告。
这个过程不仅能快速验证接口功能,其导出的 Collection JSON 文件还可以被后续的 Python 自动化框架(如pytest+requests)直接或间接引用,提高效率。
5. Python 接口自动化测试框架搭建
手工接口测试验证了基本功能,但要实现回归测试、持续集成,就必须自动化。Python 的requests库和pytest框架是构建接口自动化测试套件的黄金组合。
5.1 项目结构与核心模块设计
我为自动化测试项目设计了清晰的结构:
lms_auto_test/ ├── conftest.py # pytest 全局配置、夹具定义 ├── requirements.txt # 项目依赖 ├── config/ │ └── settings.py # 全局配置(基础URL、数据库连接等) ├── common/ │ ├── __init__.py │ ├── request_client.py # 封装的 requests 客户端 │ └── logger.py # 日志模块 ├── test_data/ │ └── test_data.yaml # 测试数据管理 ├── test_cases/ │ ├── __init__.py │ ├── test_auth.py # 认证相关测试 │ ├── test_book.py # 图书管理测试 │ └── test_borrow.py # 借阅流程测试 └── reports/ # 测试报告目录conftest.py:这是 pytest 的核心扩展文件。我在这里定义了一些fixture,比如一个返回已认证请求会话的auth_session,每个测试用例可以直接使用这个会话,无需重复处理登录和 token。import pytest from common.request_client import RequestClient from config import settings @pytest.fixture(scope="session") def auth_session(): """获取一个已认证的请求会话""" client = RequestClient(base_url=settings.BASE_URL) login_data = {"username": settings.TEST_USER, "password": settings.TEST_PWD} resp = client.post("/api/login", json=login_data) token = resp.json()["data"]["token"] client.session.headers.update({"Authorization": f"Bearer {token}"}) yield client client.session.close() # 测试结束后关闭会话common/request_client.py:对requests.Session()进行封装,加入重试机制、统一的日志记录和异常处理。import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry class RequestClient: def __init__(self, base_url): self.base_url = base_url self.session = requests.Session() # 配置重试策略 retry_strategy = Retry( total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504] ) adapter = HTTPAdapter(max_retries=retry_strategy) self.session.mount("http://", adapter) self.session.mount("https://", adapter) def request(self, method, endpoint, **kwargs): url = f"{self.base_url}{endpoint}" # 这里可以加入请求日志 response = self.session.request(method, url, **kwargs) # 这里可以加入响应日志和基础断言(如状态码非5xx) return response # 简化的 get, post 方法...
5.2 测试用例编写与数据驱动
有了稳固的基础设施,编写测试用例就变得非常清晰。以“借阅图书”的正向测试为例:
import pytest from test_data import test_data class TestBorrowBook: """借阅功能测试类""" @pytest.mark.parametrize("book_data", test_data.VALID_BORROW_CASES) def test_borrow_book_success(self, auth_session, book_data): """测试正常借阅流程""" # 1. 先查询图书,确保库存足够(前置条件) book_info = auth_session.get(f"/api/books/{book_data['book_id']}").json() assert book_info["data"]["stock"] > 0, "图书库存不足,无法执行借阅测试" # 2. 执行借阅请求 borrow_payload = {"bookId": book_data["book_id"], "userId": book_data["user_id"]} response = auth_session.post("/api/borrow", json=borrow_payload) # 3. 断言 assert response.status_code == 200 resp_json = response.json() assert resp_json["code"] == 200 assert "借阅成功" in resp_json["message"] # 可以进一步断言返回的借阅记录ID不为空等 # 4. 后置验证:再次查询图书,库存应减1 book_info_after = auth_session.get(f"/api/books/{book_data['book_id']}").json() assert book_info_after["data"]["stock"] == book_info["data"]["stock"] - 1这里我使用了pytest.mark.parametrize来实现数据驱动,将测试数据VALID_BORROW_CASES(可以从 YAML 文件加载)与测试逻辑分离,使得添加新的测试数据非常方便。
5.3 测试报告与持续集成
执行测试后,生成一份直观的报告至关重要。我使用pytest-html插件来生成 HTML 报告:
pytest test_cases/ -v --html=reports/report.html --self-contained-html这份报告会详细列出每个测试用例的执行结果(通过/失败)、耗时、错误信息和日志,便于分析和归档。
更进一步,可以将这个测试套件集成到 Jenkins 或 GitLab CI 中。每次代码提交或定时任务,自动在测试服务器上部署最新版本的 LMS,然后运行这套自动化测试,并将报告发送到指定邮箱或群聊。这样,我们就构建了一个基本的持续测试流水线,能快速反馈版本质量。
6. 常见问题排查与实战心得
在实际操作中,不可能一帆风顺。下面记录了几个我遇到的典型问题及解决方法,希望能帮你避开这些坑。
6.1 环境与部署问题
- 问题:应用启动失败,端口被占用。
- 排查:使用
sudo netstat -tlnp | grep :8080查看 8080 端口被哪个进程占用。 - 解决:如果是不需要的进程,用
kill -9 <PID>结束它。或者修改 Spring Boot 的application.properties中的server.port配置,换一个端口。
- 排查:使用
- 问题:应用无法连接 MySQL 数据库。
- 排查:首先检查应用日志中的错误信息。常见错误是 “Access denied for user”。在 MySQL 服务器上,登录后执行
SELECT host, user FROM mysql.user;查看lms_user是否允许从应用服务器 IP 连接。 - 解决:如果用户权限正确,检查 Ubuntu 防火墙
sudo ufw status。如果防火墙开启,需要允许 3306 端口:sudo ufw allow 3306。另外,确认 MySQL 配置文件/etc/mysql/mysql.conf.d/mysqld.cnf中的bind-address是0.0.0.0或具体 IP,而不是127.0.0.1(后者只允许本地连接)。
- 排查:首先检查应用日志中的错误信息。常见错误是 “Access denied for user”。在 MySQL 服务器上,登录后执行
- 问题:Python 测试脚本在本地能跑,在 Ubuntu 服务器上跑失败。
- 排查:通常是环境依赖问题。检查
requests,pytest,pyyaml等库是否已安装。使用pip list查看。 - 解决:在项目根目录下使用
pip install -r requirements.txt一键安装所有依赖。最好在虚拟环境(venv)中进行。
- 排查:通常是环境依赖问题。检查
6.2 接口测试问题
- 问题:接口返回 403 Forbidden 或 401 Unauthorized。
- 排查:99% 的原因是 Token 问题。检查请求头中的
Authorization字段格式是否正确(Bearer + 空格 + Token),Token 是否已过期。可以在 Postman 中重新登录获取新 Token 试试。 - 解决:在自动化脚本中,实现 Token 的自动刷新机制。在
request_client.py的请求方法中,如果捕获到 401 状态码,则自动调用登录接口刷新 Token,然后重试原请求。
- 排查:99% 的原因是 Token 问题。检查请求头中的
- 问题:测试数据污染。自动化测试反复创建用户、借阅图书,导致数据库中出现大量垃圾数据,可能影响后续测试。
- 解决:实施测试数据生命周期管理。
- 创建时使用随机标识:如用户名
test_user_<timestamp>,书名测试图书_<random_string>。 - 使用测试固件(Fixture)清理:在
pytest的 fixture 中,使用yield提供数据,并在测试结束后执行清理 SQL。@pytest.fixture def temporary_book(auth_session): # 创建一本临时图书 book_data = {"name": f"自动化测试图书_{int(time.time())}", ...} create_resp = auth_session.post("/api/books", json=book_data) book_id = create_resp.json()["data"]["id"] yield book_id # 测试结束后,删除这本图书 auth_session.delete(f"/api/books/{book_id}") - 准备独立测试数据库:最彻底的方式是让 CI 流水线在每次测试前,从一个干净的数据库快照恢复数据。
- 创建时使用随机标识:如用户名
- 解决:实施测试数据生命周期管理。
6.3 自动化脚本设计心得
- 断言要精准且有层次:不要只断言 HTTP 状态码 200。业务操作成功,后端通常会返回一个自定义的业务码(如
code: 200)和成功信息。同时,对于关键业务数据的变化(如库存数量、借阅状态)也要进行断言,这能发现一些逻辑错误。 - 测试用例要独立:每个测试用例应该能独立运行,不依赖其他用例的执行顺序。这意味着你需要通过 API 自己创建测试所需的数据(前置条件),并在测试后清理(后置操作)。
pytest的 fixture 是管理这类依赖的绝佳工具。 - 日志是救命稻草:在
request_client和关键操作步骤中加入详细的日志记录(使用 Pythonlogging模块),记录请求 URL、参数、响应状态码和正文(敏感信息如密码需脱敏)。当测试失败时,详细的日志能让你快速定位问题是出在请求发送、网络传输、还是业务逻辑处理环节。 - 考虑异步接口:如果 LMS 有异步操作(如批量导入图书、生成报表),测试脚本需要能够轮询或通过回调来验证最终结果,而不是简单发完请求就断言。
