简介:软件测试课程设计.zip 是一套面向高校软件工程、计算机及相关专业学生与测试入门者的课程设计资料,以 JavaWeb 网上书店前台系统为被测对象,衔接《Java Web应用开发课程设计》与《软件测试课程设计》两个阶段。压缩包共 904 个文件,约 45.97MB,包含 101 个 jsp 页面、47 个 java 源文件、138 个 class 编译文件及 39 个 xml 配置、32 个 jar 依赖,另有 gif、jpg、png 等界面素材与 js、css 静态资源,完整保留 IDEA 工程结构和可运行项目。已有 2350 人学习,说明其案例选择与测试流程具有较好的参考性。资料围绕白盒测试的逻辑覆盖与基本路径覆盖、黑盒测试的等价类与边界值分析展开,并包含 JUnit 单元测试、功能测试以及 LoadRunner 稳定性与压力测试内容,可帮助读者理解从测试用例设计到性能指标评价的完整过程,适合拿来对照课程设计报告、复现测试步骤或补充测试思路。
1. 解压之后的意外发现:一压缩包里塞着被测系统、用例表和性能脚本
一个开发者把「软件测试课程设计.zip」丢过来,说他只想跑起来交个差,结果解压完盯了一下午:一半是 JavaWeb 工程,一半是 Excel 和 Word,还有个 VuGen 脚本目录,彼此之间谁依赖谁全靠猜。这个反应很典型。这类课程设计包的真实价值不在于「学了测试理论」,它是把一个完整测试流程所需的三部分素材固定在一起:一个能启动、能登录、能查数据的被测系统,一套从需求条目拆出来的用例和缺陷记录,一组针对登录和查询接口的 LoadRunner 脚本。判断它适不适合你,就看三件事能不能连贯做通:系统能不能用 IDEA 起来、用例能不能对着真实页面执行、脚本能不能回放出稳定的并发数据。刚上手测试岗的开发者、要做课程设计的学生、需要给团队搭一份测试模板的人,都能从这份结构里拿到可复用的东西。
2. 目录骨架与技术栈:被测系统、测试资产、性能脚本三块怎么摆
压缩包解压之后最重要的不是立刻打开工程,而是先认出这三块资产之间的依赖方向。测试用例是照着系统的功能页面写的,性能脚本是照着系统的请求路径录制的,所以被测系统是根基,另外两块都是它的下游产物。把这条线理清楚,后面改任何一个模块都知道影响面在哪。
2.1 解压后的三层目录结构与各自职责
这类包通常维持一种相对稳定的骨架,我把它还原出来:
软件测试课程设计/ ├── pom.xml # Maven 依赖与打包配置 ├── sql/ │ ├── schema.sql # 建库建表语句 │ └── init_data.sql # 初始化测试数据(账号、字典、业务样本) ├── src/ │ ├── main/java/ # 控制层、服务层、DAO 层 │ ├── main/resources/ # jdbc.properties、mybatis 映射、日志配置 │ └── main/webapp/ # JSP 页面、静态资源、WEB-INF/web.xml ├── doc/ │ ├── 测试计划.docx # 范围、进度、环境、风险 │ ├── 测试用例.xlsx # 用例编号、模块、步骤、预期结果 │ └── 缺陷记录.xlsx # 缺陷单、状态流转 └── performance/ ├── login_script/ # VuGen 录制的登录事务脚本 └── scenario/ # 场景配置与结果目录sql/目录是整套东西能否跑起来的关键,很多人卡在登录页提示账号不存在,问题根本不在代码,而是init_data.sql没执行,业务表里一条数据都没有。doc/目录不看代码也能读懂,但要把它和src里的真实页面一一对上,才知道每条用例到底点了哪个按钮。performance/目录与src的耦合最隐性,脚本里写死的地址、事务名、参数名必须和系统实际的表单字段对得上,否则录制成功、回放全错。
我一般会先在解压目录顶层补一个README.md,写清这三个目录的执行顺序:先sql,再src,最后performance。改动量极小,但后面自己回头查或者交给别人接手时,这一页纸省掉大量反复摸索。
2.2 技术栈清单:JDK、Tomcat、MySQL、IDEA、LoadRunner 各管一段
| 组件 | 在项目中的角色 | 常见选择 | 需要确认的点 |
|---|---|---|---|
| JDK | 编译与运行被测系统 | 8 或 11 | 与 Maven 编译级别一致 |
| Tomcat | Web 容器,跑 war 包 | 8.5 / 9 | context path 决定访问地址 |
| MySQL | 存储账号与业务数据 | 5.7 / 8.0 | 字符集、时区、驱动版本 |
| IDEA | 开发与调试入口 | 2021 以上 | 是否已配 Maven 与 JDK |
| Maven | 依赖管理与打包 | 3.6+ | 镜像源是否可用 |
| JUnit | 接口/服务层单元测试 | 4.13 或 5.x | 与 pom 中版本一致 |
| LoadRunner | 性能脚本录制与压测 | VuGen + Controller | 协议选 Web(HTTP/HTML) |
这份清单里最容易踩坑的是 MySQL 驱动与 JDK 的版本配合:mysql-connector-java5.x 配 MySQL 8 会出现握手失败,换成 8.x 驱动后连接串还得补上时区参数。LoadRunner 这一侧通常按 Web(HTTP/HTML) 协议录,因为被测系统是纯表单提交,没有异步接口,用这个协议最省事。
版本号我不建议直接照抄别人给的,在你的机器上先跑一次java -version和mvn -v,把 pom 里的<source>和<target>对齐到本地 JDK,比对着文档改半天要快。
2.3 建库、导数据、配连接:让工程第一次启动不报错
顺序上先建库再导数据,最后才改配置文件。建库这一步用命令行最直接:
# 登录 MySQL,创建库并指定字符集,避免中文乱码 mysql -u root -p -e "CREATE DATABASE test_course DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" # 依次导入表结构与初始化数据,顺序不能反 mysql -u root -p test_course < sql/schema.sql mysql -u root -p test_course < sql/init_data.sqlschema.sql只负责 DDL,init_data.sql负责 DML,两者分开的好处是后面反复重置数据时只重跑第二个文件就够了。字符集一定显式写成utf8mb4,默认的 latin1 会让中文用户名直接变成问号,测试用例里那条「用户名支持中文」的断言就永远过不了。
接着改src/main/resources/jdbc.properties:
# 连接串需要显式指定时区与是否使用 SSL,否则 8.x 驱动会直接拒绝连接 jdbc.url=jdbc:mysql://127.0.0.1:3306/test_course?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=你的密码 jdbc.driver=com.mysql.cj.jdbc.DriverserverTimezone是 MySQL 8 驱动必须的参数,漏掉会抛The server time zone value is unrecognized。useSSL=false只是为了本地跑通,正式环境要改成true并配证书。改完这三处,用 IDEA 打开pom.xml让它自动下载依赖,然后配一个 Tomcat 运行配置,Deployment 里选war exploded,Application context 建议设成/test,这样后面所有访问地址都以http://localhost:8080/test/开头,用例和脚本里的路径才好统一。
3. 功能测试落地:从需求条目拆到用例表,再落到 JUnit 断言
系统跑起来之后,最容易出现的偏差是:用例文档写得漂亮,但一条都没对着真实页面执行过。真正让这份课程设计有价值的部分,是把需求条目、页面操作、用例表格和自动化断言这四层串起来,每层都能互相追溯。
3.1 需求条目拆成可测点:登录与列表查询两个典型模块
被测系统一般有两类核心功能,登录和带条件的列表查询,这两块恰好覆盖了测试设计里最常用的几种方法。登录模块的可测点包括:用户名密码正确时跳转首页、密码错误时给出提示、用户名为空时前端拦截、连续错误若干次后的锁定策略、用户名含特殊字符时的转义处理。列表查询模块的可测点包括:无条件查询返回全量分页数据、单条件过滤、多条件组合过滤、结果为空时的空状态提示、页码越界时的处理。
拆可测点的判断标准很简单,一句话能描述出「输入什么、做什么、期望看到什么」的,才算一个可测点。像「登录功能要好用」这种描述没法执行,必须落到「输入长度 21 位的用户名,点击登录,页面提示用户名长度不能超过 20 位」才算数。这一步做扎实,后面写用例基本是填空题。
我一般会在 Excel 里加一列「需求来源」,指向测试计划文档里的章节号。评审时有人问「这条用例为什么存在」,直接指过去,不用来回解释。
3.2 等价类、边界值写进用例表:模板与三条实例
用例表的列不要贪多,固定成这几列就够用:用例编号、所属模块、前置条件、操作步骤、预期结果、优先级、需求来源。下面是从登录模块摘出来的三条,覆盖了有效等价类、无效等价类和边界值:
| 用例编号 | 模块 | 前置条件 | 操作步骤 | 预期结果 | 优先级 |
|---|---|---|---|---|---|
| TC-LOGIN-001 | 登录 | 数据库存在账号 test01/123456 | 输入正确账号密码,点击登录 | 跳转首页,右上角显示 test01 | P0 |
| TC-LOGIN-002 | 登录 | 同上 | 输入 test01 与错误密码,点击登录 | 停留登录页,提示「用户名或密码错误」 | P0 |
| TC-LOGIN-003 | 登录 | 同上 | 用户名输入 20 位字符,密码正确 | 按正常流程校验,不出现长度报错 | P1 |
| TC-LOGIN-004 | 登录 | 同上 | 用户名输入 21 位字符 | 提示长度超限,不发起请求 | P1 |
TC-LOGIN-003 和 004 是典型的边界值配对,20 位是上限那么 20 和 21 都要测。TC-LOGIN-002 的预期结果必须写到提示文案级别,只写「登录失败」在缺陷复现时会扯皮,开发和测试对「失败」的理解可能不一样。
用例执行完不是打个勾就结束,凡是不通过的都要落缺陷单。缺陷单的写法有个硬要求:标题里必须包含模块和现象,比如「登录模块-用户名 21 位时未做长度拦截,请求仍发送至后端」,描述里按「环境-步骤-实际结果-预期结果」四段写。这样的单子开发拿到手,不用问一句话就能复现。
3.3 用 JUnit 把服务层校验跑起来
功能用例是手工执行的,但登录参数校验这类逻辑完全可以用单元测试兜住,改代码时不至于把老功能改坏。下面这段测试针对服务层的登录方法,覆盖正确凭证、错误密码和超长用户名三种情况:
public class LoginServiceTest { private LoginService loginService; @Before public void setUp() { // 用真实数据库连接初始化服务,课程设计阶段不引入 Mock 框架,减少环境依赖 ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); loginService = ctx.getBean(LoginService.class); } @Test public void testLoginSuccess() { // 正确凭证应返回非空用户对象,且用户名与入参一致 User user = loginService.login("test01", "123456"); assertNotNull("正确凭证登录应返回用户对象", user); assertEquals("test01", user.getUsername()); } @Test public void testLoginWrongPassword() { // 密码错误应返回 null,由控制层决定提示文案 User user = loginService.login("test01", "wrong_pwd"); assertNull("密码错误时不应返回用户对象", user); } @Test public void testUsernameTooLong() { // 21 位用户名属于边界外的非法输入,服务层应直接拒绝 String longName = "a".repeat(21); assertThrows(IllegalArgumentException.class, () -> loginService.login(longName, "123456")); } }这段测试有三个地方值得说明。@Before里用真实 Spring 容器而不是 Mock,是因为课程设计的环境本身就是单机 MySQL,引入 Mock 反而多一层学习成本;代价是跑测试前必须先执行init_data.sql,否则第一条断言就会挂。testUsernameTooLong里用了String.repeat,这是 JDK 11 才有的方法,如果你的环境是 JDK 8,换成循环拼接或者StringUtils.repeat。assertThrows属于 JUnit 5 的 API,pom 里得把依赖改成junit-jupiter,用 JUnit 4 的话要写成@Test(expected = IllegalArgumentException.class)。
跑起来的方式是在 IDEA 里右键测试类选 Run,或者在项目根目录执行mvn test。第一次跑常见的问题是applicationContext.xml找不到,检查一下它是不是放在src/main/resources下,Maven 只会把这个目录的东西打进 classpath。
4. LoadRunner 性能脚本:录制、参数化、场景设计与指标解读
性能这一块是整份课程设计里最容易做成「录一遍、跑一遍、截个图」的部分,也最容易在答辩或者代码评审时被追问细节。脚本能不能撑住追问,取决于三件事有没有做:事务边界画对了没、登录态关联做了没、参数化数据够不够。
4.1 录制之前必须准备好的三件事
第一件是清浏览器缓存和 Cookie。带着旧会话录制,脚本里会混进一堆无效请求,回放时各种 302 跳转,排查起来很痛苦。第二件是把一次业务操作压缩成一个事务,比如「登录」这个动作,从提交表单到首页渲染完成,中间可能有若干静态资源请求,这些不属于业务事务,应该过滤掉。
第三件是准备参数化数据文件。几十个并发如果都用同一个账号,服务端的会话锁或者数据库行锁会把响应时间拉得很离谱,测出来的数据没有任何参考价值。常见做法是准备一个users.dat,每行一个账号密码,脚本里用{username}和{password}引用:
test01,123456 test02,123456 test03,123456 test04,123456 test05,123456参数化的更新方式选「每次迭代」,这样每个虚拟用户每次循环都换一个账号;如果选「每次出现」,同一个事务内的账号和密码可能来自不同行,登录必然失败。这个选项在 VuGen 的参数属性面板里,是新手最常忽略的一格。
4.2 脚本骨架:事务、检查点、关联与思考时间
录制出来的原始脚本通常很乱,需要手工整理成下面这个结构:
Action() { // 关联:从登录页响应里提取 sessionid,后续请求都要带上 web_reg_save_param_ex( "ParamName=SessionId", "LB=name=\"sessionid\" value=\"", "RB=\"", SEARCH_FILTERS, LAST); lr_start_transaction("login"); // 打开登录页,建立会话 web_url("login_page", "URL=http://localhost:8080/test/login.jsp", "TargetFrame=", LAST); // 提交登录表单,账号密码来自参数化文件 web_submit_data("login_submit", "Action=http://localhost:8080/test/login.do", "Method=POST", "EncType=application/x-www-form-urlencoded", ITEMDATA, "Name=username", "Value={username}", ENDITEM, "Name=password", "Value={password}", ENDITEM, "Name=sessionid", "Value={SessionId}", ENDITEM, LAST); // 检查点:首页必须出现欢迎语,否则视为事务失败 web_reg_find("Text=欢迎", "SaveCount=welcomeCount", LAST); lr_end_transaction("login", LR_AUTO); // 思考时间模拟真实用户停顿,压测时按比例缩放 lr_think_time(3); return 0; }逐块说明。web_reg_save_param_ex是关联函数,作用是把服务端返回的动态会话标识抓出来存进变量,后面的请求用{SessionId}引用。没有这一步,第二个请求就会被服务端判为未登录,回放时脚本全绿但业务全错,这是性能测试里最隐蔽的一类翻车。
lr_start_transaction和lr_end_transaction之间的部分才会被计入响应时间,所以事务边界要刚好包住业务动作,不要把lr_think_time包进去。LR_AUTO表示由 LoadRunner 根据 HTTP 状态码自动判断事务成功与否,但 HTTP 200 不代表业务成功,所以要在关键请求前加web_reg_find做内容检查。lr_think_time(3)模拟用户看完页面再操作,压测时在 Controller 里设置「忽略思考时间」或者按 10% 比例缩放,否则并发压力会被思考时间吃掉,TPS 上不去还以为是系统性能好。
4.3 场景设计与指标:并发数、TPS、响应时间、错误率怎么读
场景设计用 Controller 里的手动场景就够。起步配置一般是:虚拟用户数 50,每 15 秒启动 5 个,持续运行 10 分钟,结束后每 15 秒停止 5 个。这个梯度能看出系统在什么并发量下开始劣化,比一上来就 500 并发有意义得多。
跑完看四组数据。TPS 曲线如果在某个点之后走平甚至下掉,说明系统到了瓶颈;平均响应时间要配合 90% 响应时间一起看,平均值 1 秒但 90% 值是 5 秒,说明有大量慢请求被平均值掩盖了;错误率超过 1% 就要停下来查日志,别急着加并发;每秒点击数反映的是实际吞吐,如果它和 TPS 走势背离,通常是静态资源或者重定向在消耗带宽。
一个常见的误判是把「响应时间随并发线性上升」当成性能问题。实际上在系统容量以内,响应时间缓慢上升是正常的,真正要盯的是拐点位置和拐点处的错误率。把这几组数据连同场景配置一起存进performance/scenario/目录,后面写测试报告直接引用,不用靠回忆。
5. 避坑与排查:跑不起来、脚本报错、数据对不上的高频现场
这套东西从解压到跑出第一份性能报告,中间的坑集中在三个位置。下面这几条都是我实际遇到的,按「现象-原因-解决」写,遇到同类问题可以直接对着查。
5.1 环境与部署类问题
现象:IDEA 打开工程后满屏红线,javax.servlet相关的类全部标红。原因:Maven 依赖没有下载完成,或者pom.xml里的 servlet-api 作用域写成了compile但没有真正拉下来。解决:先看 IDEA 右侧 Maven 面板有没有报错,点一次 Reload All Maven Projects;如果卡在下载,检查settings.xml里的镜像地址是否可达,实在不行手动执行mvn dependency:resolve看具体哪个包失败。
现象:Tomcat 启动成功,访问http://localhost:8080/test/返回 404。原因:Deployment 里配的是war而不是war exploded,或者 Application context 和访问路径不一致。解决:打开 Run/Debug Configurations,确认 Deployment 选项卡里存在xxx:war exploded,Application context 填/test,同时检查web.xml里welcome-file-list是否指向了实际存在的页面。
现象:登录时抛Access denied for user 'root'@'localhost'。原因:jdbc.properties里的密码和本地 MySQL 不一致,或者该账号没有对test_course库的权限。解决:先用命令行mysql -u root -p验证密码正确,再执行SHOW GRANTS FOR 'root'@'localhost';确认权限范围。改完配置记得重启 Tomcat,热部署不会重读 properties 文件。
5.2 脚本录制与执行类问题
现象:VuGen 录制结束,脚本里只有一个空 Action,没有任何请求。原因:录制时没有正确设置代理端口,或者浏览器走了系统代理导致请求没经过 VuGen。解决:录制前关掉浏览器所有插件,确认 Recording Options 里的端口(默认 7777)没有被占用;用 IE 或者关闭了代理扩展的 Chrome 录制;实在录不到就用手写web_submit_data的方式,报文从浏览器 F12 的 Network 面板里抄,比反复调录制环境快。
现象:脚本回放成功,事务全部通过,但数据库里没有新增数据。原因:缺关联,服务端因为会话失效把请求重定向到了登录页,而重定向也是 HTTP 200,LR_AUTO判定为成功。解决:在关键请求前加web_reg_find检查页面特征文本,同时在 Runtime Settings 里打开「Continue on error」为关闭状态,让失败暴露出来;再检查响应日志里有没有出现登录页的 HTML。
5.3 结果与数据类问题
现象:50 并发跑下来 TPS 只有个位数,但服务器 CPU 和内存都很低。原因:思考时间没有缩放,每个虚拟用户大部分时间在 sleep;或者参数化文件只有几行数据,多个用户抢同一个账号导致服务端会话互相覆盖。解决:Controller 的 Runtime Settings 里把思考时间改为「忽略」或按 10% 缩放,参数化数据行数至少是并发数的 2 倍,更新方式设为每次迭代。
现象:同一份脚本跑两次,响应时间差了三四倍。原因:第一次跑的时候 MySQL 没有缓存,第二次命中了查询缓存;或者两次运行之间有其他程序占用带宽。解决:正式取数前先跑一轮 5 分钟的预热,让连接池和数据库缓存稳定下来,再开始正式场景;测试机上关掉自动更新、同步盘这类后台任务,网络走有线。
6. 二次开发:把这份课程设计改成能反复用的测试模板
跑通之后最有价值的动作是把它改造成自己的模板,而不是每次做新项目都从零写用例和脚本。我按改造成本从低到高排了三层,每层都给出改哪里和怎么验证。
第一层是用例表的模板化。把doc/测试用例.xlsx的列结构固定下来,前四列(编号、模块、前置条件、步骤)不动,把「预期结果」列改成从数据字典里选,避免每个人写的措辞不一样。改完之后用一份新需求试着填十条,如果十分钟内填不完,说明列还是太多。验证方法很简单:找一个人只看表不看系统,能不能照着步骤把用例执行完。
第二层是脚本的公共化。登录这个动作在所有场景里都要用,把它抽成独立的vuser_init或者一个login()函数,业务脚本只负责业务本身。参数化文件改成从外部 CSV 读取,账号池扩容不用改脚本。改造后的结构大致是这样:
| 改造点 | 改哪里 | 验证方式 |
|---|---|---|
| 登录逻辑复用 | 抽到vuser_init,业务脚本调用 | 新增一个查询场景,不重复写登录 |
| 参数池外置 | 参数化文件路径改成相对路径 | 换一份数据文件回放仍成功 |
| 地址配置化 | URL 前缀抽成参数{baseUrl} | 改一处即可切换测试环境 |
| 检查点统一 | 封装checkPage()函数 | 故意让接口返回错误页,脚本能报失败 |
第三层是换被测系统。这套测试资产的耦合点只有三处:用例里的页面元素和提示文案、脚本里的请求路径和字段名、数据库连接配置。换系统时按这三处逐一替换,用例结构、脚本骨架、报告格式都能直接继承。我一般会先把新系统的登录和列表两个功能跑通,确认这三处都改到位了,再铺开写全量用例。
有一个细节值得单独说:改造完成后一定要跑一次基线对比。用改造前的脚本和改造后的脚本对同一个系统各跑 10 并发 5 分钟,TPS 和平均响应时间的偏差应该在 5% 以内,超出这个范围说明改造过程引入了额外开销,比如多了一次不必要的请求或者检查点位置放错了。这一步很像给代码改动跑回归,跳过它,后面所有数据都不可信。
从那以后我每次拿到这种带测试资产的压缩包,都强制自己先做一遍「解压-建库-启动-单测-录脚本」的完整链路再动任何内容,绝不先改代码再回头补环境。这套顺序替我挡掉了太多本可以避免的返工。希望帮到你。
本文还有配套的精品资源,点击获取