
简介这份压缩包面向完成软件测试课程设计的高校学生与Java Web测试入门者以网上书店前台系统为被测对象把JavaWeb开发、IDEA工程构建与软件测试流程串成一个可复用的课程设计案例。内容围绕白盒测试的逻辑覆盖与基本路径覆盖、黑盒测试的等价类与边界值分析展开并对核心功能代码使用JUnit做单元测试同时借助LoadRunner完成稳定性与压力测试读者可据此理解测试用例设计、执行与结果分析。压缩包共904个文件、约45.97MB包含jsp页面、java源码、class编译文件、jar依赖、xml配置以及gif/jpg/png界面素材工程与测试文档结构较完整便于按模块查阅和复现。已有2351人学习适合需要一套完整测试方案、单测示例和性能测试思路的读者参考。1. 一个能直接跑起来的软件测试课程设计比你想象的好用拿到这份《软件测试课程设计.zip》的时候我没急着看文档先解压翻代码——通宵赶过课程设计的人都有经验最怕那种只有十几页 Word 的“测试方案”系统截图全靠 P 图。这份不一样它有一套完整的 JavaWeb MySQL 学籍管理小系统当被测物配套的是围绕这个系统做的功能用例、LoadRunner 压测脚本、缺陷记录和测试报告。适合两类人一类是要交课程设计、需要能演示能复现的同学另一类是想搞明白“单元测试、功能测试、性能测试到底怎么串起来”的开发者。它代码不复杂Servlet JDBC JSP 的老三样但正因为简单登录链路、分页查询、并发瓶颈这些点都能一眼看穿拿来做测试靶场再合适不过。2. 目录结构与技术选型一个 JavaWeb 测试靶场是怎么搭起来的很多人拆包第一件事是找 README我更习惯直接看目录——目录结构里藏着作者的思路也藏着你要花多久才能跑起来。这份工程的排序很典型源码、SQL 脚本、测试文档、压测脚本四块分开互不干扰改代码不会冲掉测试物料改用例也不用碰 Java 目录。2.1 目录结构逐层拆解解压后根目录大致是这个样子不同版本的小差异不影响整体判断software-test-course-design/ ├── src/ │ ├── main/ │ │ ├── java/com/example/stms/ │ │ │ ├── controller/ # Servlet 层LoginServlet、StudentServlet │ │ │ ├── service/ # 业务层接口 impl │ │ │ ├── dao/ # 数据层BaseDao、StudentDao、UserDao │ │ │ ├── entity/ # 实体User、Student、PageResult │ │ │ └── util/ # DBUtil、MD5Util、StringUtils │ │ ├── resources/ │ │ │ └── jdbc.properties # 数据库连接四要素 │ │ └── webapp/ │ │ ├── WEB-INF/web.xml │ │ ├── jsp/ # login.jsp、student_list.jsp、student_edit.jsp │ │ └── static/ # css / js / 分页插件 │ └── test/java/com/example/stms/ │ └── service/ # JUnit 测试类 ├── sql/ │ └── stms.sql # 建库建表 初始数据 ├── docs/ # 测试计划、用例表、缺陷记录、测试报告 ├── lr/ # LoadRunner 脚本目录.usr / .c / 参数文件 └── pom.xml这个划分最有价值的地方是controller / service / dao三层切得干净。测试同学最怕“业务逻辑埋在 JSP 里”那种代码既没法写单元测试也没法定位性能瓶颈。这里 Service 层是纯 Java 类输入输出都是对象你用 JUnit 直接 new 出来就能测不需要启 TomcatDAO 层只负责 SQL 和结果集映射出了问题一看就知道是 SQL 的事还是业务的事。docs/和lr/单独拿出来放是为了让你在改代码之后能顺手把测试用例和脚本一起更新而不是回头找不到原件。pom.xml在根目录说明这是个标准 Maven 工程导入 IDEA 之后依赖会自动下前提是你本地仓库能连通这一点第 5 章会专门说。2.2 技术栈组合为什么是 Servlet JDBC 而不是上 SSM从pom.xml和web.xml能读出这套选型我把它列成表格方便你判断要不要动它层次技术 / 版本作用备注运行环境JDK 1.8编译与运行别用 17Servlet 3.1 容易踩模块化坑Web 容器Tomcat 8.5Servlet 容器与 Servlet 3.1 对齐视图JSP JSTL 1.2页面渲染用c:forEach出表格控制层Servlet 3.1WebServlet路由分发注解式不用配一堆 xml数据层原生 JDBC 手写 DBUtilCRUD没有 ORMSQL 全透明数据库MySQL 5.7 / 8.0数据存储8.0 要换com.mysql.cj.jdbc.Driver连接池无DriverManager 直连——这恰恰是压测的教学点单测JUnit 4.12单元测试Test、Before够用压测LoadRunner 12.x性能测试Web(HTTP/HTML) 协议课程设计不上 Spring不是水平不够是刻意为之。第一被测系统越简单测试结论越干净——你压出来的响应时间就是 Servlet JDBC MySQL 的不会掺进框架的 AOP 代理和连接池排队时间。第二源码可读性高初学者能顺着LoginServlet一路读到UserDao.findByUsername中间不跳任何一层。第三也是最实际的一点没有连接池DriverManager.getConnection()每次请求新建物理连接50 并发就能把 MySQL 打满这正好给你一个“性能瓶颈长什么样”的真实样本。提示如果你打算把这份工程当毕设往上加功能我一般会先补 Druid 连接池和 MyBatis但补之前一定先跑一遍原始版本留一份基线数据不然优化完了你没法证明“优化有效”。2.3 数据库表结构与初始化脚本sql/stms.sql是整个工程能不能跑起来的第一道门槛脚本长这样我按实际字段整理过核心就这三张表CREATE DATABASE IF NOT EXISTS stms DEFAULT CHARACTER SET utf8mb4; USE stms; -- 登录用户表password 存的是 MD5不是明文 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE, password CHAR(32) NOT NULL, role TINYINT NOT NULL DEFAULT 0 -- 0 学生 1 管理员 ) ENGINEInnoDB; -- 班级表给学生的 class_id 做关联 CREATE TABLE t_class ( id INT PRIMARY KEY AUTO_INCREMENT, class_name VARCHAR(64) NOT NULL, major VARCHAR(64) ) ENGINEInnoDB; -- 学生表压测和分页测试的主表 CREATE TABLE t_student ( id INT PRIMARY KEY AUTO_INCREMENT, sno VARCHAR(20) NOT NULL UNIQUE, -- 学号唯一键 name VARCHAR(32) NOT NULL, gender TINYINT NOT NULL DEFAULT 0, class_id INT NOT NULL, phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_name (name), -- 模糊查询走不到但排序能缓解 KEY idx_class (class_id) ) ENGINEInnoDB; INSERT INTO t_user(username, password, role) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, 1); -- 123456 的 MD5几点说明都是踩过才知道的。utf8mb4不是utf8因为姓名里偶尔会有生僻字用utf8存进去会变成问号后面导出报表全是“?”你就知道疼了。t_user.password用CHAR(32)而不是VARCHAR(255)是因为 MD5 定长 32 位定长字段在索引和比较上都更省。预置账号的密码是123456的 MD5登录的时候 Service 层会把用户输入也做一次 MD5 再比对这在功能测试里是一条典型的“等价类”用例正确密码、错误密码、空密码、超长密码。初始化顺序不能反先CREATE DATABASE再USE最后建表。用 Navicat 或命令行source都行但一定要确认字符集是 utf8mb4否则后面查出来的中文全是乱码你会以为是 Servlet 的锅其实建库那一步就错了。3. 环境搭建与首次运行IDEA Tomcat MySQL 走到登录页从解压到看见登录页正常节奏是二十分钟卡住的话一晚上。下面这条链路是我按顺序排的每一步都有明确的“走通了是什么样”你可以对着排查。3.1 版本对齐JDK 8 / Tomcat 8.5 / MySQL 5.7 为什么要配套这套组合不是随便配的。Servlet 3.1 的注解WebServlet需要 Tomcat 8 及以上才完整支持Tomcat 7 也能跑但有兼容问题JDK 8 之后的 9、11、17 引入了模块化老工程的javax.servlet包名在新容器里会变成jakarta.servlet直接 ClassNotFound。所以我的建议是先严格按作者给的版本跑通跑通之后再升级别一上来就折腾环境。组件推荐版本检查命令常见错误JDK1.8.0_xxxjava -version版本过高导致Unsupported class file major versionMaven3.6mvn -v没配JAVA_HOME导致编译失败Tomcat8.5.x启动日志无红色端口 8080 被占用MySQL5.7 / 8.0mysql -uroot -p8.0 需换驱动类名和时区参数java -version输出的如果是1.8.0_181这类就是对的如果冒出来17.0.x先去 IDEA 的Project Structure → SDK里把工程和模块的 SDK 都改成 8别只改一处两处不一致是最常见的翻车点。3.2 在 IDEA 里导入工程、挂 Tomcat、配数据源导入走File → Open选pom.xmlIDEA 会自动识别成 Maven 工程并下载依赖。下载完之后你去External Libraries里扫一眼应该能看到javax.servlet-api、junit、mysql-connector-java这三个缺哪个就手动Reload All Maven Projects。接着配 TomcatRun → Edit Configurations → → Tomcat Server → Local几个关键项Application server指向本地解压的 Tomcat 8.5 目录Deployment加一个Artifact选xxx:war explodedApplication context填/stmsOn Update action选Update classes and resources改 JSP 不用重启Application context这一项要注意它决定了你访问的 URL 前缀。填/stms就访问http://localhost:8080/stms/jsp/login.jsp填/就是根路径。工程里如果大量用了${pageContext.request.contextPath}那填什么都行如果作者图省事写死了/stms那你就得跟着填/stms否则所有静态资源 404。数据源不用在 IDEA 里配工程用的是jdbc.properties文件下一节说。3.3 初始化数据库与修改 jdbc.propertiessrc/main/resources/jdbc.properties一般是这四行jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://127.0.0.1:3306/stms?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password你的密码逐个说com.mysql.cj.jdbc.Driver是 MySQL 8.0 驱动的新类名5.x 驱动用com.mysql.jdbc.Driver写错了就是ClassNotFoundException这个错误信息很有迷惑性看着像依赖没下其实是类名不对。serverTimezoneAsia/Shanghai是 8.0 必加项不加会报The server time zone value is unrecognized而且这个错只在运行时报编译期一点提示都没有。allowPublicKeyRetrievaltrue是 8.0 默认加密方式导致的不加的话本地连接可能被拒。useSSLfalse是消掉那串黄色警告本地开发无所谓。改完这个文件记得一件事它在resources下编译后会进target/classes如果你改了源文件但没重新BuildTomcat 跑的还是旧配置你会对着配置发呆半小时。3.4 冒烟验证五个必点页面与一分钟自检环境弄好了别急着做测试先按顺序点一遍验证“系统是活的”访问/stms/jsp/login.jsp页面能出、样式正常样式丢了说明静态资源路径有问题用admin / 123456登录能跳转到学生列表跳不过去先看控制台异常栈列表页有数据、分页条显示总数没数据就回去查 SQL 脚本有没有执行成功点“新增”填一条学号重复的数据应该被拒绝这一步验证唯一键和异常处理模糊搜索一个姓结果条数正确验证LIKE拼接和参数绑定这五步走完你可以认为基础设施没问题了。后面所有测试活动——写用例、跑 JUnit、录 LoadRunner 脚本——都建立在这五步之上。血泪经验是千万别跳过冒烟直接录脚本LoadRunner 回放一堆 500 的时候你根本分不清是脚本错了还是系统本来就是坏的。4. 核心代码拆解登录链路、分页查询、JUnit 与 LoadRunner 脚本这一章是整份工程最值得读的部分。测试做得好不好很大程度上取决于你对被测代码的理解深度——你连它哪一步查库、哪一步写 Session 都不知道用例设计就只能靠拍脑袋。4.1 登录请求的完整链路一次 POST 从 LoginServlet 到 Dao先看控制层这段代码几乎是所有 JavaWeb 课程设计的标准骨架WebServlet(/login) public class LoginServlet extends HttpServlet { private final UserService userService new UserServiceImpl(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 必须在第一次 getParameter 之前设置否则中文参数已按 ISO-8859-1 解码 req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); // Service 内部完成空值校验 - MD5 - 查库 - 比对 User user userService.login(username, password); if (user null) { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/jsp/login.jsp).forward(req, resp); return; } // 登录成功写 Session压测时会成为共享资源竞争点 req.getSession().setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /student?actionlist); } }这段的每个细节都能对应一条测试用例。setCharacterEncoding必须在getParameter之前调用写反了中文用户名就是乱码这是功能测试里的字符集用例。user null的分支只返回一句含糊的“用户名或密码错误”安全上是对的但测试上你要分别覆盖“账号不存在”和“密码错误”两种输入确认提示语一致——如果某天有人改成了“该用户不存在”那就是一个信息泄露缺陷。sendRedirect用的是重定向而不是转发所以登录成功后浏览器地址栏会变这一点在你用 LoadRunner 做脚本关联时非常关键重定向会产生两个 HTTP 请求脚本里必须把Location头或者JSESSIONID做关联否则后续请求全部 302。再往下是 Service 和 Dao逻辑很短public User login(String username, String password) { if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { return null; // 空值直接短路不打库 } String md5 MD5Util.encode(password); // 与库里存的 32 位 MD5 对齐 return userDao.findByUsernameAndPwd(username, md5); }短但它决定了你的用例边界空字符串、纯空格、超长字符串比如 500 个字符、SQL 注入串 or 11、大小写混写。因为用了PreparedStatement注入串会被当普通字符串处理查不到人返回 null——这条用例的期望结果就是“登录失败且系统不报 500”而不是“登录成功”。4.2 分页查询手写 limit 的 JDBC 版本分页是这套系统里唯一有点技术含量的地方也是最容易出 bug 的地方public PageResultStudent findByPage(String keyword, int pageNum, int pageSize) { StringBuilder where new StringBuilder( FROM t_student WHERE 11 ); ListObject params new ArrayList(); // 关键字条件姓名或学号模糊匹配两个占位符共用一个值 if (StringUtils.isNotBlank(keyword)) { where.append( AND (name LIKE ? OR sno LIKE ?) ); params.add(% keyword %); params.add(% keyword %); } // 先查总数注意这里复用同一套 where 条件 int total studentDao.count(where.toString(), params.toArray()); // 再查当页数据LIMIT 的两个参数追加在最后 String sql SELECT id,sno,name,gender,class_id,phone,create_time where ORDER BY id DESC LIMIT ?, ?; params.add((pageNum - 1) * pageSize); // offset params.add(pageSize); // size ListStudent rows studentDao.query(sql, params.toArray()); return new PageResult(total, pageNum, pageSize, rows); }关键在于两点。第一count和query必须复用同一套where条件否则会出现“总数 100翻到第 5 页却是空的”这种玄学现象本质是 count 少了过滤条件。第二LIMIT ?, ?的两个参数是追加在params末尾的顺序不能反——第一个是偏移量(pageNum-1)*pageSize第二个是页大小。这里如果传成LIMIT pageSize, offset第一页看着正常翻到第二页就开始错位属于典型的“测一页测不出来”的 bug。测试这条链路时我一般设计这么几组pageNum0非法值、pageNum1边界、pageNum总页数最后一页、pageNum总页数1越界、pageSize0会查出全表性能隐患。后端如果没做pageNum 1的兜底offset会变成负数MySQL 直接抛语法错误前端就是一片白。4.3 给 Service 层补 JUnit 单元测试这份工程的src/test/java下有 JUnit 用例模式很朴素但该有的都有public class UserServiceTest { private UserService userService; Before // 每个 Test 之前都执行保证用例互不干扰 public void setUp() { userService new UserServiceImpl(); } Test public void 登录_正确账号密码_返回用户对象() { User user userService.login(admin, 123456); assertNotNull(正确凭证应能登录成功, user); assertEquals(admin, user.getUsername()); } Test public void 登录_密码错误_返回null() { assertNull(userService.login(admin, wrong-pwd)); } Test public void 登录_用户名为空_返回null() { assertNull(userService.login(, 123456)); assertNull(userService.login(null, 123456)); } }用中文方法名在 JUnit 4 里完全合法好处是测试报告一跑出来一眼就知道哪条挂了不用回头翻代码。Before每次都重建 Service 实例避免用例之间共享状态——这一点在涉及数据库的测试里尤其重要因为 DAO 是有副作用的。注意这类测试是连真实数据库的不是 Mock所以跑之前数据库必须是通的而且测试数据要可重复比如固定用admin这个预置账号别用测试过程中新增的数据否则第二次跑就失败了。想再进一步就把 Dao 抽成接口用 Mockito 打桩让单测彻底脱离数据库。但对课程设计来说连库的测试反而更贴近真实能顺带验证 SQL 写没写错。4.4 测试用例设计等价类、边界值落到具体字段docs/里的用例表不是拿来看的是要能对着执行的。我把登录和学生新增两个模块的用例抽出来格式是这样用例编号模块测试点输入数据预期结果优先级TC-LOGIN-01登录正确凭证admin / 123456跳转列表页Session 有值高TC-LOGIN-02登录密码错误admin / 111111停留登录页提示错误高TC-LOGIN-03登录用户名为空空 / 123456提示“用户名不能为空”中TC-LOGIN-04登录超长用户名300 个字符不报 500提示错误中TC-STU-01新增学生学号重复已存在的 sno提交失败提示学号已存在高TC-STU-02新增学生手机号 11 位边界13800000000保存成功中TC-STU-03新增学生手机号 12 位138000000001校验失败中设计方法是老三样等价类划分把输入分成有效类和无效类边界值盯住 0、1、最大值、最大值1场景法串起“登录→查询→新增→退出”的完整流程。真正拉开差距的是优先级那一列——高优先级的用例是每次回归都要跑的中低优先级的可以只在版本发布前跑一遍。课程设计答辩的时候老师最爱问的就是“你为什么这么排优先级”答案应该是“按业务影响面和出错概率”不是“按我写的顺序”。4.5 LoadRunner 脚本录制、参数化与场景编排lr/目录下是性能测试的部分用的是 Web(HTTP/HTML) 协议。录制之后的脚本要改改完大概是这样Login() { /* 集合点让 N 个虚拟用户同时发起登录制造真实并发 */ lr_rendezvous(login_point); lr_start_transaction(login); web_submit_data(login, Actionhttp://{host}/stms/login, MethodPOST, RecContentTypetext/html, ITEMDATA, Nameusername, Value{p_username}, ENDITEM, Namepassword, Value{p_password}, ENDITEM, LAST); lr_end_transaction(login, LR_AUTO); /* 登录成功后必须关联 JSESSIONID否则后续请求全 302 */ web_reg_save_param_ex( ParamNamejsession, LBSet-Cookie: JSESSIONID, RB;, SEARCH_FILTERS, LAST); return 0; }几个参数得说清楚。{p_username}和{p_password}是参数化变量对应一个.dat参数文件里面放 100 行不同的账号Parameter type选FileSelect next row选UniqueUpdate value on选Each iteration——这三项配错压出来全是同一个账号登录缓存命中率虚高数据没意义。lr_rendezvous是集合点只有在场景里设置了集合策略比如 100 个用户到齐 80% 才放行才生效不加集合点所谓“并发”其实是排队。lr_start_transaction/lr_end_transaction圈定事务边界LoadRunner 的响应时间统计就是按这个区间算的你不圈事务响应时间那一列全是空的。场景设计上课程设计常用的梯度是 10 / 30 / 50 并发每档跑 5 分钟观察 TPS 和平均响应时间。因为没有连接池你大概率会看到 30 并发时 TPS 见顶、响应时间开始爬坡50 并发直接出现Too many connections。这不是脚本的问题这是被测系统的真实瓶颈——性能测试报告里最有价值的一句话就是“瓶颈定位在数据库连接创建建议引入连接池”。5. 常见问题排查五个能让课程设计卡一晚上的坑下面这五条基本覆盖了从导入到压测的全流程。每条按“现象 → 原因 → 解决”写对号入座就行。5.1 现象启动就报 ClassNotFoundException: com.mysql.cj.jdbc.Driver现象Tomcat 启动没报错一访问登录页就抛异常栈顶是ClassNotFoundException指向驱动类。原因两种可能。一是pom.xml里的mysql-connector-java版本是 5.1.x但jdbc.properties写的是 8.0 的com.mysql.cj.jdbc.Driver二是依赖在 Maven 里是provided或test作用域打包进WEB-INF/lib时被排除了。解决先看pom.xml的版本号5.1.x 配com.mysql.jdbc.Driver8.0.x 配com.mysql.cj.jdbc.Driver一一对应。再看 IDEA 的Project Structure → Artifacts → WEB-INF/lib里有没有那个 jar没有就把依赖作用域改成compile重新Build Artifacts。我一般还会在DBUtil的静态块里加一行Class.forName(driver)显式加载报错信息更直白。5.2 现象列表页中文全是问号数据库里查却是正常的现象MySQL 客户端里SELECT出来的姓名正常页面上显示???或者一堆菱形方块。原因链路上有三处编码任何一处不对都会这样——数据库/表的字符集、JDBC URL 的characterEncoding、HTTP 请求响应的编码。工程里最容易漏的是第三处web.xml没配CharacterEncodingFilterJSP 页面也没写% page contentTypetext/html;charsetUTF-8 %。解决按顺序查。SHOW CREATE TABLE t_student看是不是utf8mb4JDBC URL 里确认useUnicodetruecharacterEncodingutf8都在web.xml或 Servlet 里补req.setCharacterEncoding(UTF-8)和resp.setContentType(text/html;charsetUTF-8)。三处对齐还不行就看 Tomcat 的server.xml里 Connector 有没有URIEncodingUTF-8老版本 Tomcat 默认不是 UTF-8GET 请求的中文参数会挂在这一步。5.3 现象LoadRunner 回放全是 302或者提示“未找到关联值”现象录制的脚本直接回放登录事务失败web_submit_data之后跟着一串 302业务请求全部跳回登录页。原因LoadRunner 录制的是“一次会话”的完整轨迹回放时是新的会话。服务端的JSESSIONID变了你却没把响应里的新 Cookie 取出来传给后续请求服务端认为你没登录自然全部重定向。解决在登录请求之前加web_reg_save_param_ex抓Set-Cookie里的JSESSIONID后续每个请求用web_add_cookie(JSESSIONID{jsession};DOMAIN127.0.0.1)带上。判断关联做没做对的方法很简单回放时打开Runtime Settings → Log → Extended log看每次请求的 Header 里 Cookie 是不是同一个值。另外重定向本身也会产生额外请求web_submit_data后面加一个web_url显式跟随比让 LoadRunner 自动跳转更可控。5.4 现象并发上到 50 就开始大量超时MySQL 报 Too many connections现象10 并发、30 并发都正常50 并发时错误率飙到 40%日志里全是Too many connections或者连接超时。原因工程用的是DriverManager.getConnection()直连每个请求创建一次物理连接、用完关掉。并发一高连接创建速度和 MySQL 的max_connections默认 151顶上了再加上 TCP 三次握手和认证开销响应时间被拉长连接释放更慢雪崩。解决短期把max_connections调大只能缓解治标。正确做法是引入连接池Druid 或 HikariCP 都行initialSize5、maxActive20、maxWait3000改完重跑同一组并发对比 TPS 曲线。这一步做完你的性能测试报告就有了“优化前 vs 优化后”的对比数据比单纯贴一张响应时间图表有说服力得多。5.5 现象JUnit 本地跑全绿Maven 打包时测试全挂现象在 IDEA 里点绿色三角跑测试全过执行mvn package时Tests run: 3, Failures: 3。原因IDEA 跑测试用的是工程的工作目录和编码配置Maven 用的是surefire插件的默认配置。最常见的是编码不一致——测试类和方法名是中文surefire默认平台编码在 Windows 上是 GBK读源码就乱码了其次是工作目录不同测试里读相对路径的配置文件读不到。解决在pom.xml的surefire插件里显式指定argLine-Dfile.encodingUTF-8/argLine同时给maven-compiler-plugin配上encodingUTF-8/encoding。如果确实只是本地想快速打包用mvn package -DskipTests跳过但别养成习惯——跳过测试打包等于把单元测试这道防线自己拆了。6. 二次开发的改造思路从课程设计到能拿得出手的项目这份工程的上限不在代码在于你有没有把它往“工程化”方向推一步。我给你三条改造线从易到难都能在原有结构上动刀不用推倒重来。第一条是补连接池和事务。DAO 层现在是每个方法自己getConnection、自己close改成ThreadLocal持有连接配合 Filter 在请求结束时统一提交或回滚。这一步做完你会发现“新增学生 新增用户”这种跨表操作的测试用例才有意义——现在没有事务中间失败就是脏数据你连缺陷都不好提。改造项动哪几个文件验证方式收益连接池DBUtil、pom.xml50 并发错误率对比性能报告有优化前后数据事务控制BaseDao、新增TransactionFilter制造异常看是否回滚能提“数据一致性”类缺陷参数校验Service层入口边界值用例全部通过减少 500 错误第二条是把测试物料变成可执行的。docs/里的用例表现在是 Excel手工执行。你可以用 JUnit 5 的ParameterizedTest把等价类用例直接写成参数化测试把“用例表”和“自动化脚本”合成一份。这样每次改完代码跑一遍mvn test就是一次回归比对着 Excel 一条条点强太多。我一般会先从登录模块下手因为它的输入输出最干净改造代价最小跑通之后再往业务模块铺。第三条是把 LoadRunner 的结论落回代码。压测报告里写“响应时间随并发线性增长”这只是现象。你要做的是回到代码里找到那一行——比如LoginServlet里req.getSession().setAttribute在 50 并发下的锁竞争或者findByPage里count和query两次往返。找到之后改一版用同一套脚本再压一次两次数据摆在一起这才叫性能测试。只跑一次压测就交报告的老师一问“为什么慢”就答不上来。最后说个习惯。我早期做这类课程设计最常犯的错是“改完代码不重跑基线”压测数据一会儿好一会儿坏最后自己都不知道哪个数是准的。从那以后我每次动完代码都强制先把冒烟那五步走一遍再跑 1 并发基线确认功能没坏、单请求响应时间没有异常漂移才敢往上加并发。这个动作花不了五分钟但能省掉你一晚上的自我怀疑。希望帮到你。本文还有配套的精品资源点击获取