
银行账户单元测试实验报告一、实验目的掌握 Maven 项目配置学会引入 JUnit 5 单元测试依赖学会使用 JUnit5 编写单元测试理解Nested分组结构化测试理解单元测试三大用例类型正常场景、边界场景、异常场景学会编写独立无依赖的测试用例验证业务规则、捕获非法输入二、实验内容本次实验实现一个银行账户类BankAccount提供存款、取款、转账、利息计算功能内置业务约束账户余额不能为负数取款、转账金额必须大于 0且不能超过当前余额计息利率范围0.0 ~ 1.0 参数非法时程序主动抛出异常。 我们为这个类编写全套单元测试验证每一条业务规则是否生效。三、项目结构【示意图 1项目目录截图】src ├── main │ └── java │ └── com/example │ └── BankAccount.java // 业务源代码 └── test └── java └── com/example └── BankAccountTest.java // JUnit5测试代码四、核心代码4.1 pom.xmlMaven 依赖配置作用引入 JUnit5 测试依赖让项目支持单元测试?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdbank-account-demo/artifactId version1.0-SNAPSHOT/version properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding junit-jupiter.version5.9.2/junit-jupiter.version /properties dependencies dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter-api/artifactId version${junit-jupiter.version}/version scopetest/scope /dependency dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter-engine/artifactId version${junit-jupiter.version}/version scopetest/scope /dependency /dependencies build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-surefire-plugin/artifactId version3.1.2/version /plugin /plugins /build /project4.2 BankAccount.java 业务类代码package com.example; /** * 功能管理账户的基本操作包括存款、取款、转账和利息计算 * 要求 * - 账户余额不能为负数 * - 取款金额必须大于0且不超过余额 * - 转账金额必须大于0且不超过余额 * - 利率范围0.0 ~ 1.0即0% ~ 100% */ public class BankAccount { private String accountId; private String ownerName; private double balance; // 常量 private static final double MIN_BALANCE 0.0; private static final double MIN_AMOUNT 0.0; // 构造函数 public BankAccount(String accountId, String ownerName, double initialBalance) { // 参数校验 if (accountId null || accountId.trim().isEmpty()) { throw new IllegalArgumentException(账户ID不能为空); } if (ownerName null || ownerName.trim().isEmpty()) { throw new IllegalArgumentException(账户持有人姓名不能为空); } if (initialBalance MIN_BALANCE) { throw new IllegalArgumentException(初始余额不能为负数); } this.accountId accountId; this.ownerName ownerName; this.balance initialBalance; } // Getter方法 public String getAccountId() { return accountId; } public String getOwnerName() { return ownerName; } public double getBalance() { return balance; } /** * 存款操作 * param amount 存款金额必须 0 * throws IllegalArgumentException 当存款金额 ≤ 0 时 */ public void deposit(double amount) { if (amount MIN_AMOUNT) { throw new IllegalArgumentException(存款金额必须大于0); } this.balance amount; } /** * 取款操作 * param amount 取款金额必须 0 且 ≤ 余额 * throws IllegalArgumentException 当取款金额 ≤ 0 时 * throws IllegalStateException 当余额不足时 */ public void withdraw(double amount) { if (amount MIN_AMOUNT) { throw new IllegalArgumentException(取款金额必须大于0); } if (amount this.balance) { throw new IllegalStateException(余额不足无法取款。当前余额 this.balance); } this.balance - amount; } /** * 转账操作从当前账户转出 * param targetAccount 目标账户不能为null * param amount 转账金额必须 0 且 ≤ 余额 * throws IllegalArgumentException 当目标账户为null或转账金额 ≤ 0时 * throws IllegalStateException 当余额不足时 */ public void transferTo(BankAccount targetAccount, double amount) { if (targetAccount null) { throw new IllegalArgumentException(目标账户不能为空); } if (amount MIN_AMOUNT) { throw new IllegalArgumentException(转账金额必须大于0); } if (amount this.balance) { throw new IllegalStateException(余额不足无法转账。当前余额 this.balance); } // 执行转账先扣款再存款 this.balance - amount; targetAccount.balance amount; } /** * 计算利息 * param rate 年利率范围 [0.0, 1.0] * return 利息金额 * throws IllegalArgumentException 当利率不在有效范围内时 */ public double calculateInterest(double rate) { if (rate 0.0 || rate 1.0) { throw new IllegalArgumentException(利率必须在0.0 ~ 1.0之间); } return this.balance * rate; } }4.3 BankAccountTest.java 测试代码Nested 分组结构化package com.example; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; DisplayName(BankAccount 银行账户单元测试) public class BankAccountTest { // 构造方法测试 Test DisplayName(构造账户正常创建账户初始余额合法) void shouldCreateAccountSuccessfullyWhenParamsValid() { BankAccount account new BankAccount(A001, 张三, 1000.0); assertEquals(A001, account.getAccountId()); assertEquals(张三, account.getOwnerName()); assertEquals(1000.0, account.getBalance()); } Test DisplayName(构造账户账户ID为null抛出异常) void shouldThrowIllegalArgExWhenAccountIdNull() { IllegalArgumentException ex assertThrows(IllegalArgumentException.class, () - new BankAccount(null, 张三, 100)); assertEquals(账户ID不能为空, ex.getMessage()); } Test DisplayName(构造账户账户ID为空串抛出异常) void shouldThrowIllegalArgExWhenAccountIdBlank() { IllegalArgumentException ex assertThrows(IllegalArgumentException.class, () - new BankAccount( , 张三, 100)); assertEquals(账户ID不能为空, ex.getMessage()); } Test DisplayName(构造账户持有人姓名为null抛出异常) void shouldThrowIllegalArgExWhenOwnerNameNull() { IllegalArgumentException ex assertThrows(IllegalArgumentException.class, () - new BankAccount(A001, null, 100)); assertEquals(账户持有人姓名不能为空, ex.getMessage()); } Test DisplayName(构造账户初始余额为负数抛出异常) void shouldThrowIllegalArgExWhenInitialBalanceNegative() { IllegalArgumentException ex assertThrows(IllegalArgumentException.class, () - new BankAccount(A001, 张三, -50)); assertEquals(初始余额不能为负数, ex.getMessage()); } Test DisplayName(构造账户初始余额等于0创建成功边界) void shouldCreateAccountWhenInitialBalanceZero() { BankAccount account new BankAccount(A002, 李四, 0.0); assertEquals(0.0, account.getBalance()); } // 嵌套存款测试 Nested DisplayName(存款 deposit) class DepositTest { Test DisplayName(正常存入正数金额余额增加) void shouldIncreaseBalanceWhenDepositValidAmount() { BankAccount account new BankAccount(A001, 张三, 100); account.deposit(200); assertEquals(300, account.getBalance()); } Test DisplayName(存款金额等于0抛出异常) void shouldThrowIllegalArgExWhenDepositAmountZero() { BankAccount account new BankAccount(A001, 张三, 100); IllegalArgumentException ex assertThrows(IllegalArgumentException.class, () - account.deposit(0)); assertEquals(存款金额必须大于0, ex.getMessage()); } Test DisplayName(存款金额负数抛出异常) void shouldThrowIllegalArgExWhenDepositAmountNegative() { BankAccount account new BankAccount(A001, 张三, 100); IllegalArgumentException ex assertThrows(IllegalArgumentException.class, () - account.deposit(-10)); assertEquals(存款金额必须大于0, ex.getMessage()); } } // 嵌套取款测试 Nested DisplayName(取款 withdraw) class WithdrawTest { Test DisplayName(正常取款余额减少) void shouldDecreaseBalanceWhenWithdrawValidAmount() { BankAccount account new BankAccount(A001, 张三, 500); account.withdraw(200); assertEquals(300, account.getBalance()); } Test DisplayName(取款金额等于余额余额变为0边界) void shouldBalanceZeroWhenWithdrawAllBalance() { BankAccount account new BankAccount(A001, 张三, 500); account.withdraw(500); assertEquals(0, account.getBalance()); } Test DisplayName(取款金额为0抛出异常) void shouldThrowIllegalArgExWhenWithdrawAmountZero() { BankAccount account new BankAccount(A001, 张三, 500); IllegalArgumentException ex assertThrows(IllegalArgumentException.class, () - account.withdraw(0)); assertEquals(取款金额必须大于0, ex.getMessage()); } Test DisplayName(取款金额大于余额抛出余额不足异常) void shouldThrowIllegalStateExWhenWithdrawOverBalance() { BankAccount account new BankAccount(A001, 张三, 500); IllegalStateException ex assertThrows(IllegalStateException.class, () - account.withdraw(600)); assertTrue(ex.getMessage().contains(余额不足)); } } // 嵌套转账测试 Nested DisplayName(转账 transferTo) class TransferToTest { Test DisplayName(正常转账转出账户扣钱、目标账户加钱) void shouldTransferSuccessWhenParamValid() { BankAccount from new BankAccount(A001, 张三, 1000); BankAccount to new BankAccount(A002, 李四, 200); from.transferTo(to, 300); assertEquals(700, from.getBalance()); assertEquals(500, to.getBalance()); } Test DisplayName(目标账户为null抛出异常) void shouldThrowIllegalArgExWhenTargetAccountNull() { BankAccount from new BankAccount(A001, 张三, 1000); IllegalArgumentException ex assertThrows(IllegalArgumentException.class, () - from.transferTo(null, 100)); assertEquals(目标账户不能为空, ex.getMessage()); } Test DisplayName(转账金额为0抛出异常) void shouldThrowIllegalArgExWhenTransferAmountZero() { BankAccount from new BankAccount(A001, 张三, 1000); BankAccount to new BankAccount(A002, 李四, 200); IllegalArgumentException ex assertThrows(IllegalArgumentException.class, () - from.transferTo(to, 0)); assertEquals(转账金额必须大于0, ex.getMessage()); } Test DisplayName(转账金额超过余额抛出余额不足异常) void shouldThrowIllegalStateExWhenTransferOverBalance() { BankAccount from new BankAccount(A001, 张三, 500); BankAccount to new BankAccount(A002, 李四, 200); IllegalStateException ex assertThrows(IllegalStateException.class, () - from.transferTo(to, 600)); assertTrue(ex.getMessage().contains(余额不足)); } } // 嵌套利息计算测试 Nested DisplayName(利息计算 calculateInterest) class CalculateInterestTest { Test DisplayName(利率0.0利息为0边界) void shouldReturnZeroInterestWhenRateZero() { BankAccount account new BankAccount(A001, 张三, 1000); double interest account.calculateInterest(0.0); assertEquals(0, interest); } Test DisplayName(利率1.0利息等于本金边界) void shouldReturnFullBalanceInterestWhenRateOne() { BankAccount account new BankAccount(A001, 张三, 1000); double interest account.calculateInterest(1.0); assertEquals(1000, interest); } Test DisplayName(正常利率0.05计算正确) void shouldCalculateCorrectInterestWhenRateValid() { BankAccount account new BankAccount(A001, 张三, 1000); double interest account.calculateInterest(0.05); assertEquals(50, interest); } Test DisplayName(利率小于0抛出异常) void shouldThrowIllegalArgExWhenRateLessThanZero() { BankAccount account new BankAccount(A001, 张三, 1000); IllegalArgumentException ex assertThrows(IllegalArgumentException.class, () - account.calculateInterest(-0.01)); assertEquals(利率必须在0.0 ~ 1.0之间, ex.getMessage()); } Test DisplayName(利率大于1.0抛出异常) void shouldThrowIllegalArgExWhenRateGreaterThanOne() { BankAccount account new BankAccount(A001, 张三, 1000); IllegalArgumentException ex assertThrows(IllegalArgumentException.class, () - account.calculateInterest(1.1)); assertEquals(利率必须在0.0 ~ 1.0之间, ex.getMessage()); } } }五、测试运行效果【示意图 2IDEA 测试运行树截图】 IDEA 测试窗口会分级展示说明Nested就是用来分组把存款、取款、转账、利息分开层次一目了然。所有测试用例互相独立每个测试方法都会新建账户对象测试顺序打乱也不会互相影响。【示意图 3测试全部通过的绿色运行结果截图】 运行方式IDEA 直接右键运行测试类或者命令行执行mvn test。 绿色代表用例通过红色代表功能不符合预期。六、测试思路说明一共三类测试用例正常场景输入合法数据验证功能结果是否正确边界场景测试临界值比如全部余额取出、利率取 0、利率取 1边界位置最容易出错异常场景传入非法参数校验程序是否抛出指定异常拦截错误操作。七、实验心得本次实验使用 JUnit5 完成银行账户类单元测试借助Nested注解实现测试用例分组结构清晰直观。测试覆盖正常、边界与异常场景验证了存款、取款、转账、计息的业务规则。我认识到单元测试可以提前发现代码缺陷保障程序健壮性同时测试用例必须相互独立、命名语义化方便后期查看和维护。