简介这份实验报告面向软件测试初学者与高校学生围绕Junit单元测试的安装配置与实例应用展开帮助读者从零理解单元测试框架在实际项目中的落地方式。文档以经典的「佣兵问题」为案例完整呈现了枪机、枪托、枪管销售佣金计算的Java实现并给出边界值测试方法的分析过程与多组测试用例涵盖销售额与佣金的分段计算逻辑。资源包共1个docx文件约1.09MB内容包含问题描述、完整代码、测试用例设计与测试结果结构紧凑、便于直接参考。目前已有4895人学习下载适合正在学习软件测试课程或需要完成Junit实验报告的学生对照练习也可作为理解边界值分析与断言校验的入门范例。1. 从一份实验报告说起Junit 单元测试到底怎么落地很多人第一次接触 Junit 单元测试是在一份实验报告里看到一个佣金计算器枪机 45 美元、枪托 30 美元、枪管 25 美元按销售额分段抽佣然后要求你写测试用例、跑通断言。听起来像个课堂作业但它其实是一个相当典型的“业务规则密集、边界条件多、手工验证容易翻车”的场景。这份《Junit单元测试的实验报告.docx》把需求、被测代码、边界值用例表、IDEA 里 Junit 的搭建步骤、测试代码和运行结果串成了一条完整链路适合正在学软件测试、需要交实验报告或者想把 Junit 真正用进日常开发的人。它不教你写 Hello World而是直接给你一个能跑、能改、能扩的测试样板。2. 被测系统拆解佣金计算器的业务规则与代码结构2.1 需求背后的三个硬约束这个佣金程序看着简单实际上藏了三层约束任何一层没处理好测试就会变成玄学。第一层是数量限额枪机 1 到 70、枪托 1 到 80、枪管 1 到 90超出范围直接判输入错误。第二层是完整性要求销售商每月至少要售出一支完整步枪也就是说三类零件不能全为零代码里用locks1 stocks1 barrels1来卡这个条件。第三层是分段计佣1000 美元以内含抽 10%1000 到 1800 美元含的部分抽 15%超过 1800 美元的部分抽 20%。这三层约束叠在一起意味着测试不能只测“正常算对”还要测“越界拒绝”和“边界值精确命中”。实验报告里那张 27 条用例的表本质上就是在给这三层约束做交叉覆盖。我一般会先把业务规则翻译成判定条件再决定用边界值还是等价类而不是上来就随手写几个数字。2.2 Salary 类的字段与构造逻辑被测类叫Salary放在main包下字段全是私有价格写死在字段初始化里public class Salary { private int lockPrice 45; // 枪机单价 private int stockPrice 30; // 枪托单价 private int barrelPrice 25; // 枪管单价 private int locks; private int stocks; private int barrels; private double totalPrice; private double commission; public Salary(int clocks, int cstocks, int cbarrels) { locks clocks; stocks cstocks; barrels cbarrels; } }构造器只做赋值不做校验校验被推迟到totalPrice()里。这种写法在实验场景里够用但放到真实项目里要小心对象一旦构造出来就处于“可能非法”的状态调用方如果先调commission()再调totalPrice()拿到的就是默认值 0 算出来的佣金。参数名clocks和字段locks差一个字母也是这份代码里一个容易看漏的小坑读的时候别把枪机数量看成了时钟。2.3 totalPrice 与 commission 的联动关系totalPrice()先校验数量范围合法才累加销售额非法直接返回 -1commission()则依赖totalPrice字段是否大于 0 来判断能不能算佣金。两者是隐式耦合的commission()不会自己重新算销售额它读的是上一次totalPrice()留下的结果。public double totalPrice() { if (locks 1 locks 70 stocks 1 stocks 80 barrels 1 barrels 90) { totalPrice locks * lockPrice stocks * stockPrice barrels * barrelPrice; System.out.println(销售金额为 totalPrice 美元); return totalPrice; } else { System.out.println(输入有错误); return -1; } } public double commission() { if (totalPrice 0) { if (totalPrice 1800) { commission (totalPrice - 1800) * 0.2 800 * 0.15 1000 * 0.1; } else if (totalPrice 1000) { commission (totalPrice - 1000) * 0.15 1000 * 0.1; } else { commission totalPrice * 0.1; } return commission; } else { return -1; } }分段公式里800 * 0.15和1000 * 0.1是固定档位金额不是变量改税率的时候这两处要同步改否则算出来的佣金会对不上。这也是后面写测试时最值得盯的地方边界点 1000 和 1800 附近公式切换是否平滑直接决定用例能不能抓到 bug。3. 测试工程搭建在 IDEA 里把 Junit 跑起来3.1 插件与依赖的两步准备实验报告里给的搭建路径是先在 Settings 的 Plugins 里搜 Junit 安装再去 Project Structure 里给模块添加对应版本的 Junit 库。这套流程在旧版 IDEA 里很常见现在更省事的做法是直接建 Maven 工程在pom.xml里引依赖版本由构建工具管换机器也不用重新配库。dependency groupIdjunit/groupId artifactIdjunit/artifactId version4.13.2/version scopetest/scope /dependencyscope设成test表示这个依赖只在测试编译和运行期可见不会打进最终产物。如果坚持用 IDEA 自带方式添加库记得选和 JDK 匹配的版本Junit 4 需要 Hamcrest 核心包配合缺了会在assertThat上编译报错。3.2 用 CtrlShiftT 生成测试骨架在Salary.java编辑区按 CtrlShiftTIDEA 会弹出创建测试的对话框勾选要生成的方法选好 Junit 4确认后就在test目录下生成对应包路径的测试类。这一步的价值不只是省手写而是保证测试类和被测类包名一致后面跑单测时类路径不会错位。生成的骨架里通常带Before和After分别对应每个测试方法执行前后的准备与清理。这个佣金例子没有外部资源要释放两个方法留空即可但保留它们是个好习惯等以后被测类开始读文件、连数据库直接往里填就行。3.3 测试类的继承与注解混用问题实验报告里的测试类写成了public class SalaryTest extends TestCase同时又用了Test、Before、After这些 Junit 4 注解。这是 Junit 3 和 Junit 4 两种风格的混搭extends TestCase是 Junit 3 的路子靠方法名test开头来识别测试Junit 4 则靠注解识别。混着写一般能跑但容易出现“方法没被识别成测试”的情况尤其是方法名不以 test 开头又只靠继承的时候。我一般会二选一要么纯 Junit 4不继承 TestCase只用注解要么纯 Junit 3。混用带来的排查成本远大于省下的那点改动量。4. 核心测试代码逐段讲断言、边界值与用例设计4.1 equals 辅助方法为什么长得这么怪被测类里有个equals(double etotalPrice, double ecommission, Object object)它不是重写Object.equals而是一个三参数的自定义比较方法传入期望销售额、期望佣金和一个对象内部把对象强转成Salary后重新算一遍再和期望值比。public boolean equals(double etotalPrice, double ecommission, Object object) { if (object instanceof Salary) { Salary salary (Salary) object; return salary.totalPrice() etotalPrice salary.commission() ecommission; } return false; }这个方法有两个隐患。一是它和Object.equals(Object)签名不同属于重载而非重写集合类里不会用到它二是内部又调了一次totalPrice()和commission()等于把计算逻辑重复执行如果被测方法有副作用比如打印、写库测试里就会多出一次。实验报告里用它来做断言本质上是把“期望值比较”塞进了被测类测试代码反而变薄了。真实项目里更推荐把期望值写在测试方法里用assertEquals直接比别让被测类承担断言的职责。4.2 一条测试方法的完整写法报告里的测试方法把输入写死成 10、20、30然后断言销售额 1800、佣金 220Test public void testTotalSalary() { int locks 10; int stocks 20; int barrels 30; Salary salary new Salary(locks, stocks, barrels); Assert.assertTrue(salary.equals(1800, 220, salary)); }10×45 20×30 30×25 450 600 750 1800正好落在第二档边界上佣金 1000×0.1 800×0.15 100 120 220。这条用例选得很准它同时命中了“销售额等于 1800”和“分段公式切换点”两个边界。参数说明一下locks是枪机数上限 70stocks是枪托数上限 80barrels是枪管数上限 90三个都必须是正整数否则totalPrice()返回 -1。不过assertTrue(salary.equals(...))的失败信息很弱只告诉你“期望 true 实际 false”不告诉你差在哪。改成下面这样失败时能直接看到实际值Test public void testBoundary1800() { Salary salary new Salary(10, 20, 30); assertEquals(1800.0, salary.totalPrice(), 0.001); assertEquals(220.0, salary.commission(), 0.001); }第三个参数0.001是浮点比较的容差double 运算有精度误差直接比较容易在末位翻车这是写金额相关断言时的血泪经验。4.3 边界值用例表怎么读、怎么补报告里那张 27 条用例的表按销售额分了几个簇最小值附近100、125、130、145、500 中点、1000 边界上下、1400 中点、1800 边界上下、4800 中点、7800 最大值附近外加越界输入。这个分簇思路是对的边界值测试的核心就是盯住“刚好等于、刚好小于、刚好大于”三类点。但表里有个地方值得注意用例 26 和 27 是越界输入期望结果是“输入错误”可totalPrice()返回的是 -1而commission()在totalPrice不大于 0 时也返回 -1。测试越界场景时断言要针对 -1 写而不是针对某个金额写否则用例本身就会失败。补用例时我一般会再加一条“三类零件全为 0”的验证完整性约束是否真的拦住。4.4 用参数化测试压缩用例数量报告结尾自己也提到边界值用例太多应该找等价用例减少数量。Junit 4 的参数化测试正好干这件事把输入和期望值抽成数据表一个方法跑完所有组合。RunWith(Parameterized.class) public class SalaryParamTest { private int locks, stocks, barrels; private double expectedTotal, expectedCommission; public SalaryParamTest(int l, int s, int b, double t, double c) { this.locks l; this.stocks s; this.barrels b; this.expectedTotal t; this.expectedCommission c; } Parameterized.Parameters public static CollectionObject[] data() { return Arrays.asList(new Object[][]{ {1, 1, 1, 100, 10}, // 最小值 {10, 10, 10, 1000, 100}, // 第一档边界 {10, 20, 30, 1800, 220}, // 第二档边界 {70, 80, 90, 7800, 1420} // 最大值 }); } Test public void testCommission() { Salary salary new Salary(locks, stocks, barrels); assertEquals(expectedTotal, salary.totalPrice(), 0.001); assertEquals(expectedCommission, salary.commission(), 0.001); } }RunWith(Parameterized.class)告诉 Junit 用参数化运行器data()返回的每个Object[]是一组参数顺序要和构造器一致Parameterized.Parameters标注数据源方法。这样四条数据一次跑完报告里 27 条用例可以压缩成十来组有代表性的数据维护成本低很多。5. 避坑与排查跑 Junit 时最容易翻车的几件事5.1 测试方法没被执行绿条一片但什么都没测现象是点运行后全部通过但断点根本没进测试方法。原因通常是测试类没被识别要么方法名不以 test 开头又没加Test要么测试类放在了src/main而不是src/test下。解决方式是检查目录结构确认测试类在 test 源根里方法上补Test再重新跑一次看执行计数是否对得上。5.2 浮点断言偶发失败现象是同一条用例今天过明天不过或者换个 JDK 就红。原因是 double 直接比较1800.0000000001 和 1800.0 被判不等。解决方式是所有金额断言都带容差用assertEquals(expected, actual, 0.001)别用assertTrue(a b)。5.3 越界用例断言写错方向现象是越界输入那条用例一直红。原因是totalPrice()对非法输入返回 -1而用例里期望的是某个金额。解决方式是把越界用例的期望值明确写成 -1并且单独断言commission()也返回 -1别和正常金额用例混在一个断言里。5.4 构造器参数名和字段名看混现象是测试数据填错算出来的销售额对不上。原因是构造器参数写的是clocks字段是locks读代码时容易把枪机数量当成时钟。解决方式是测试里用带注释的字面量或者干脆把参数名改成和字段一致减少阅读负担。5.5 依赖版本和 JDK 不匹配现象是编译报NoClassDefFoundError或 Hamcrest 相关类找不到。原因是 Junit 4 需要 Hamcrest 核心包手动加库时只加了 junit 没加 hamcrest。解决方式是改用 Maven 引依赖让构建工具把传递依赖带进来或者手动把 hamcrest-core 一起加到模块库中。6. 二次开发与扩展把实验代码改成能复用的测试模板这份实验报告的价值不在那 27 条用例本身而在它给了一个可复制的骨架被测类、边界值分析、测试类、断言、运行结果。真要拿去做二次开发我建议先做三件事。第一把Salary里的校验从totalPrice()抽出来做成独立的validate()方法让构造完就能知道对象是否合法测试也能单独测校验逻辑不用每次都绕到金额计算。第二把分段佣金公式里的档位金额和税率提成常量测试里用常量做期望值改税率时只改一处。第三把equals那个三参数方法删掉断言全部回到测试类里用assertEquals加容差失败信息才看得懂。扩展方向上可以给这个模板加两层。一层是参数化测试把边界值表直接喂给Parameterized用例数量降下来但覆盖不降另一层是异常路径测试用Test(expected IllegalArgumentException.class)或者assertThrows验证非法输入是否按预期抛错而不是靠返回 -1 这种隐式约定。如果后面要接 CI把测试类放进src/test/java用mvn test跑失败时看 surefire 报告里的实际值和期望值比在 IDEA 里点绿条靠谱。改造点改前改后收益校验位置藏在 totalPrice 里独立 validate 方法可单独测试对象状态明确断言方式被测类内 equals测试类 assertEquals 带容差失败信息可读用例组织27 条平铺参数化数据表维护成本低非法输入返回 -1抛异常或返回明确错误码调用方不易漏判有个细节我踩过把校验抽出去之后commission()里totalPrice 0的判断要跟着改否则合法但销售额恰好为 0 的场景会被误判。改完一定把边界用例重跑一遍尤其是 1000 和 1800 两个切换点。从那以后我每次动计费逻辑都强制先把边界值用例跑一遍再提交希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
多协议物联网平台架构设计与优化实践 1. 多协议物联网平台的核心价值 在工业4.0和智慧城市快速发展的今天,设备通信协议的碎片化已成为物联网落地的主要障碍。一个支持Modbus、OPC UA、MQTT、CoAP等多协议的物联网平台,就像配备多国语言翻译器的国际会议中心,能无缝连接不同厂商的… · 2026/11/3 10:59:29
Session与Token:Web身份认证机制对比与实践指南 1. 身份认证机制的本质需求 在Web应用开发中,身份认证始终是保障系统安全的第一道防线。当用户首次登录后,服务端需要一种机制来持续识别该用户的身份,避免每次操作都重复验证账号密码。这就引出了会话(Session)和令牌… · 2026/11/3 10:59:29
JUnit单元测试环境搭建全攻略:从版本选择到避坑排查 简介:这份实验报告文档面向正在学习软件测试与Java单元测试的学生及初学者,围绕Junit的安装配置与实例应用展开,帮助读者理解单元测试在实际编码中的作用与基本流程。文档以经典的“佣兵问题”为案例,完整呈现了枪机、枪托、枪管销… · 2026/11/3 10:59:29
【Ubuntu】GTX 1080 Ti 显卡占用为0但功率高达60W的排查与解决 背景
实验室有一台四卡 1080 Ti 的深度学习服务器,平时主要用来跑训练。某天没任务的时候我随手敲了一下 nvidia-smi,结果发现了一个奇怪的现象:四张卡显存占用全是 0,利用率也是 0,可功率每张都在 55~60W 左右&#x… · 2026/11/3 19:29:23
HandBrake 开源转码实战:安装、预设、CLI 与验收(程序员向) HandBrake 开源转码实战:安装、预设、CLI 与验收(程序员向)
HandBrake(仓库:GitHub 仓库 HandBrake/HandBrake)是跨平台开源视频转码工具,面向「把各种片源压成可播放、可分享、体积可控的成品」… · 2026/11/3 19:29:23
高精度计算入门与immense数据模型精讲(一) 跳转链接
高精度计算入门与immense数据模型精讲(二)高精度计算入门与immense数据模型精讲(三)高精度计算入门与immense数据模型精讲(四)高精度计算入门与immense数据模型精讲(五)高… · 2026/11/3 19:29:23
电脑一键息屏工具,双击就能关闭屏幕 软件介绍
Monoff 是一款用来息屏的小工具,双击打开之后电脑屏幕就直接关掉了,属于那种功能单一但很直接的类型。它最后一次更新是在 2011 年,到现在停更已经有段时间了,目前能用到的最后版本是 v1.6.0。 唤醒方式
屏幕关掉之后想… · 2026/11/3 19:29:23
高精度计算入门与immense数据模型精讲(二) 跳转链接
高精度计算入门与immense数据模型精讲(一)高精度计算入门与immense数据模型精讲(三)高精度计算入门与immense数据模型精讲(四)高精度计算入门与immense数据模型精讲(五)高… · 2026/11/3 19:29:23
Java还能学吗?AI时代Java工程师的出路与收藏指南 本文分析Java在AI时代的现状与前景,指出Java后端岗位需求依然旺盛,但初级岗位竞争激烈,高级岗位需求增长。AI辅助编程正淘汰只会执行任务的程序员,而能设计架构、处理复杂问题的高级Java工程师依然抢手。未来Java将向云原生、AI应… · 2026/11/3 19:28:23
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46