首次使用MVC框架,如何为Model类编写JUnit测试并逐步进阶?
嘿,很高兴你开始接触JUnit测试,而且选了MVC里最核心的Model层入手——挑GateInfoDatabase作为第一个测试对象绝对是明智的,毕竟它和最多类交互,搞定它能帮你快速摸透测试的核心思路。下面我一步步带你从简单到深入搭建测试:
首先确保你的测试包结构和主项目对应,比如主项目里GateInfoDatabase在com.yourproject.model包下,那测试包就建com.yourproject.model.test(IDE一般会自动识别这个结构,方便后续类的引用)。
然后确认JUnit依赖已经加好:
- 如果用Maven,在
pom.xml里加:
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency>
- 如果用Gradle,在
build.gradle里加:
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
先别碰复杂的交互,先测那些不需要依赖其他类的方法——比如getter/setter、简单的数据计算或状态判断方法。
举个例子,假设GateInfoDatabase有个getGateCount()方法,测试它的初始化状态和添加数据后的变化:
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class GateInfoDatabaseTest { private GateInfoDatabase db; // 每个测试方法执行前都初始化一个干净的实例,避免测试间互相影响 @BeforeEach void setUp() { db = new GateInfoDatabase(); } @Test void getGateCount_InitialState_ReturnsZero() { assertEquals(0, db.getGateCount()); } @Test void getGateCount_AfterAddingOneGate_ReturnsOne() { db.addGate(new Gate("TestGate")); assertEquals(1, db.getGateCount()); } }
这种测试写起来简单,能帮你快速熟悉JUnit的基本注解(@Test、@BeforeEach)和断言方法。
因为GateInfoDatabase继承了Observable,它的核心职责之一就是在数据变化时通知观察者。这时候咱们可以自己写个简单的测试观察者,来验证通知是否正常触发:
import java.util.Observable; import java.util.Observer; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class GateInfoDatabaseTest { // 自定义测试观察者,用来记录是否收到通知 class TestObserver implements Observer { boolean isNotified = false; Object receivedArg; @Override public void update(Observable o, Object arg) { isNotified = true; receivedArg = arg; } } @Test void update_WhenGateAdded_NotifiesObservers() { GateInfoDatabase db = new GateInfoDatabase(); TestObserver observer = new TestObserver(); db.addObserver(observer); // 执行修改数据的操作 Gate testGate = new Gate("MainGate"); db.addGate(testGate); // 验证观察者收到了通知,且参数正确 assertTrue(observer.isNotified); assertEquals(testGate, observer.receivedArg); } }
这个测试能帮你确认Observable的逻辑是否正常工作,是Model层的核心测试点之一。
当GateInfoDatabase依赖其他类(比如数据持久化的DAO、配置类)时,千万别用真实的实现来测试——不然你的测试会依赖外部资源(比如真实数据库),变得不稳定。这时候可以用Mockito来模拟这些依赖:
先加Mockito依赖(Maven为例):
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>4.11.0</version> <scope>test</scope> </dependency>
然后写测试,验证GateInfoDatabase是否正确调用了依赖类的方法:
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import static org.mockito.Mockito.verify; class GateInfoDatabaseTest { @Mock // 模拟一个GateDao对象 private GateDao mockGateDao; private GateInfoDatabase db; @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); // 初始化模拟对象 db = new GateInfoDatabase(mockGateDao); // 把模拟对象注入到测试类里 } @Test void saveGate_DelegatesToDao() { Gate testGate = new Gate("BackGate"); db.saveGate(testGate); // 验证mockGateDao的save方法被正确调用了一次,参数就是testGate verify(mockGateDao).save(testGate); } }
这种测试能帮你确保GateInfoDatabase和依赖类的交互逻辑是正确的,而且完全不依赖外部资源,跑起来又快又稳。
等基础测试都搞定了,就可以开始测试一些极端情况:
- 测试添加
null的Gate时,是否抛出预期的IllegalArgumentException - 测试当数据库为空时,调用
deleteGate()是否能正确处理(比如不崩溃、返回false) - 测试移除Observer后,数据变化时是否不再发送通知
举个异常测试的例子:
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertThrows; class GateInfoDatabaseTest { @Test void addGate_WithNullGate_ThrowsIllegalArgumentException() { GateInfoDatabase db = new GateInfoDatabase(); // 验证调用addGate(null)时会抛出预期的异常 assertThrows(IllegalArgumentException.class, () -> db.addGate(null)); } }
当你对单元测试熟门熟路后,可以尝试写一些集成测试——比如让GateInfoDatabase和真实的内存数据库(比如H2)一起工作,测试端到端的数据流程。不过这一步可以放在后面,先把单元测试的基础打扎实。
慢慢来,一开始不用追求100%的代码覆盖率,先把核心功能的测试写好,然后逐步扩展。测试的本质是帮你兜底,确保后续改代码时不会不小心破坏原有功能,所以写得越扎实,后面开发越省心。
内容的提问来源于stack exchange,提问作者user9488593

