JUnit + Mockito 单元测试(一)

本文介绍JUnit框架的基础使用方法,从单元测试的重要性和JUnit的基本概念入手,深入探讨如何使用JUnit进行有效的单元测试,包括测试方法的编写、注解的使用及测试结果的分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

未接触 JUnit 之前,曾经对茫茫的代码不知所措……哪怕是自己写的……多写注释?重构代码?甚至为一个方法去写一篇技术文章来解释?——这些都是试过,感觉不是“控制代码”的可行之道,甚至说”徒劳“的。关于单元测试(Unit test),之前亦略有所闻,感觉用处不大,因为对一个方法检测返回的结果是否正确,——有点无聊——心想,我写的方法当然能返回预期的结果,这还有说?不至于那么低级的错误也犯得着吧!?于是对所谓测试的东东感觉简直就是在增加工作量——我把代码写漂亮点就行了。

事实结果证明,无论你怎么把代码写得漂亮,终究还是你个人主观的愿望在作崇——因为你不能既当运动员,又当裁判员。

还是来自台湾的朋友说得好,我把问题一五一十地说下:

試想一個情境,當有一個同事離職了,老闆交辦要開始維護他所留下來的程式,時程上不太允許重寫程式,老闆也不會答應。那要怎麼開始的呢?先看文件?然後再閱讀程式碼,一邊碎念,一邊感嘆自己的悲情?假以時日後,才漸漸懂得如何運用前同事的程式?在這過程中,需求還是不斷進來,必須對原來已經穩定的程式加以修修改改,一不小心又藏了一隻 bug 。然後 bug 爆發,老闆就會質疑原本好好的程式怎麼會出包,最後開會被釘到牆上。很熟悉吧,也很無奈,難道這是程式員的宿命?

遇到这种情况,该怎么办?作者且说,

所以一般的流程是先讀程式碼,了解程式碼後,才去運用。其間也沒有其他工具輔助,頂多畫畫流程圖。然後又換人接手維護,再一次進行前述的苦情循環。現有的開發環境和工具對這情形是沒有多大幫助的。但是一直等我遇到了單元測試,這情形就改觀了許多。

但是,引入单元测试之后,就是:

當我拿到像一鍋粥的程式碼,我不會馬上跳進去和它們攪和一起。我會先快速掃一下,依據經驗找出程式壞味道(bad smell),針對這壞味道所在的目標程式,先進行單元測試撰寫。我會針對我對目標程式介面的認知,撰寫我自認為正確邏輯的測試。然後測試它,如果測試 pass ,就是我的認知符合我對目標程式的期待,並且把這樣的認知,透過單元測試寫了下來,確認了這樣的"規格";如果測試 fail ,就是我的認知和程式行為不一致,要不是程式有問題,就是我的認知是錯的。此時我才會跳進目標程式碼,作細節探究。假設是我的認知錯誤,則修正測試程式;反之,就是目標程式的問題,就小心的修改目標程式,直到測試 pass 。

這樣的過程,不僅只有探索,而且記錄了結果。這還可以作為日後的回歸測試的依據。

這樣程式設計的思維方式從原本的讀碼 -> 了解 -> 使用,轉變成讀碼 -> 使用 -> 了解,僅是後兩步驟順序互換,就會帶來非常不一樣的效果。寫程式時的思維,會由原本的先了解程式細節切入如何使用,轉變成觀察使用的程式介面,探索如何使用。這有一點退一步觀賞的意味,不管是正在欣賞的是藝術作品,還是密密麻麻的程式碼,道理是相似的。這樣就不容易當局者迷,墮入程式碼的五里霧中,分辨不清方向。它也促使你重新思考這樣的設計是否合理,繼而考慮是否要重新設計。

所以啊,还是虚心地扫下盲,来:

JUnit ——是一个开源的 Java 测试框架,主要用于编写白盒测试,回归测试。无论白盒测试还是回归测试,都是运行可重复的测试。所谓”回归测试“——就是,软件或环境的修复或更正后的“再测试”,自动测试工具对这类测试尤其有用;而所谓”单元测试“——就是,最小粒度的测试,以测试某个功能或代码块。一般由程序员来做,因为它需要知道内部程序设计和编码的细节。对于持续发展的产品,单元测试在后期的维护,回归有重要等方面有重要作用。

好吧,不多说废话,否则 94 《论”单元测试“的重要性》云云……本文提到的 jar 包,分别如下:

新建 JUnit 测试

首先新建一个 测试类 Class,导入依赖包 import static org.junit.Assert.*;,也可以通过 IDE 界面来操作: File → New → JUnit → JUnit Test case。编写以下方法。

@Test
public void testMultiply() {
	MyClass tester = new MyClass();
	assertEquals( "10 x 5 must be 50 ", 50, tester.multiply( 10, 5 ));
}

在测试类中,并不是每一个方法都是用于测试的,你必须使用“注解”来明确表明哪些是测试方法,例如 @Test 是必须的注解,表明这是一个测试用例。我们可以看到,在某些方法的前有 @Before、@Test、@Ignore等字样,这些就是 JUnit 的注解,以一个“@”作为开头。掌握这些标注的含义非常重要。

接着,你要测试哪个类,那么你首先就要创建一个该类的对象,如:

MyClass tester = new MyClass();

这里的 assertEquals 是 Assert 静态方法。Assert 包含了一组静态的测试方法,用于期望值和实际值比对是否正确,即测试失败,Assert 类就会抛出一个 AssertionFailedError 异常,JUnit 将这种错误归入 Failes 并加以记录,同时标志为未通过测试。如果该类方法中指定一个 String 类型的传参则该参数将被做为 AssertionFailedError 异常的标识信息,告诉测试人员改异常的详细信息。

然后运行这个测试用例,右键点击需要测试的类并且选择 Run Run AsJUnit Test。如下图所示。

运行返回的结果如下,


进度条是红颜色表示发现错误,具体的测试结果在进度条上面有表示“共进行了4个测试,其中1个测试被忽略,一个测试失败”。

注解和断言的作用

下面补充一些 JUnit 的注解。

  • @Test (expected = Exception.class)       表示预期会抛出Exception.class 的异常
  • @Ignore 含义是“某些方法尚未完成,暂不参与此次测试”。这样的话测试结果就会提示你有几个测试被忽略,而不是失败。一旦你完成了相应函数,只需要把@Ignore注解删去,就可以进行正常的测试。
  • @Test(timeout=100)       表示预期方法执行不会超过 100 毫秒,控制死循环
  • @Before 表示该方法在每一个测试方法之前运行,可以使用该方法进行初始化之类的操作
  • @After 表示该方法在每一个测试方法之后运行,可以使用该方法进行释放资源,回收内存之类的操
  • @BeforeClass  表示该方法只执行一次,并且在所有方法之前执行。一般可以使用该方法进行数据库连接操作,注意该注解运用在静态方法。
  • @AfterClass    表示该方法只执行一次,并且在所有方法之后执行。一般可以使用该方法进行数据库连接关闭操作,注意该注解运用在静态方法。

下面简单介绍一下上边用到的静态类 junit.framework.Assert。该类主要包含七个方法:

  • assertEquals() 方法,用来查看对象中存的值是否是期待的值,与字符串比较中使用的 equals() 方法类似;
  • assertFalse() 和 assertTrue() 方法,用来查看变量是是否为 false 或 true,如果 assertFalse() 查看的变量的值是 false 则测试成功,如果是 true 则失败,assertTrue() 与之相反。
  • assertSame() 和 assertNotSame() 方法,用来比较两个对象的引用是否相等和不相等,类似于通过“==”和“!=”比较两个对象;
  • assertNull() 和 assertNotNull() 方法,用来查看对象是否为空和不为空。

更多用法请参阅 JUnit 文档。

TestSuite

如果你须有多个测试单元,可以合并成一个测试套件进行测试,况且在一个项目中,只写一个测试类是不可能的,我们会写出很多很多个测试类。可是这些测试类必须一个一个的执行,也是比较麻烦的事情。鉴于此, JUnit 为我们提供了打包测试的功能,将所有需要运行的测试类集中起来,一次性的运行完毕,大大的方便了我们的测试工作。并且可以按照指定的顺序执行所有的测试类。下面的代码示例创建了一个测试套件来执行两个测试单元。如果你要添加其他的测试单元可以使用语句 @Suite.SuiteClasses 进行注解。

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith( Suite.class )
@SuiteClasses( { JUnit1Test.class, StringUtilTest.class } )
public class JSuit {

} 
TestSuite 测试包类——多个测试的组合 TestSuite 类负责组装多个 Test Cases。待测得类中可能包括了对被测类的多个测试,而 TestSuit 负责收集这些测试,使我们可以在一个测试中,完成全部的对被测类的多个测试。 TestSuite 类实现了 Test 接口,且可以包含其它的 TestSuites。它可以处理加入Test 时的所有抛出的异常。

TestResult 结果类集合了任意测试累加结果,通过 TestResult 实例传递个每个测试的 Run() 方法。TestResult 在执行 TestCase 是如果失败会异常抛出
TestListener 接口是个事件监听规约,可供 TestRunner 类使用。它通知 listener 的对象相关事件,方法包括测试开始 startTest(Test test),测试结束 endTest(Test test),错误,增加异常 addError(Test test, Throwable t) 和增加失败 addFailure(Test test, AssertionFailedError t) 。TestFailure 失败类是个“失败”状况的收集类,解释每次测试执行过程中出现的异常情况,其 toString() 方法返回“失败”状况的简要描述。

小结

再唠叨几句:

  • JUnit 可以指定 Runner 运行器
  • JUnit 可以参数化测试,详见这里
  • 事实上在Junit 中使用try-catch 来捕获异常是没有必要的,Junit 会自动捕获异常。那些没有被捕获的异常就被当成错误处理
  • 不要认为压力大,就不写测试代码。相反编写测试代码会使你的压力逐渐减轻,因为通过编写测试代码,你对类的行为有了确切的认识。你会更快地编写出有效率地工作代码。

参见资源:

Spring Boot是个用于构建Java应用程序的开源框架,它提供了种简化了配置的方式来快速构建应用程序。JUnit个用于编写和运行单元测试的开源测试框架,而Mockito个用于创建和管理模拟对象的Java库。 下面是个使用Spring Boot、JUnitMockito进行单元测试的示例: 假设我们有个UserService类,它依赖于个UserRepository接口来访问数据库并进行些操作。我们想要对UserService的方法进行单元测试。 首先,我们需要创建测试类,命名为UserServiceTest。在测试类中,我们将使用JUnit的注解来标记测试方法,并使用Mockito来创建模拟对象。示例代码如下: ```java @RunWith(MockitoJUnitRunner.class) public class UserServiceTest { @InjectMocks private UserService userService; @Mock private UserRepository userRepository; @Test public void testGetUserById() { // 配置模拟对象的行为 User user = new User("1", "John"); when(userRepository.findById("1")).thenReturn(user); // 调用测试的方法 User result = userService.getUserById("1"); // 验证结果 assertEquals("John", result.getName()); } } ``` 在上面的示例中,我们使用了@RunWith注解来指定使用MockitoJUnitRunner运行测试,这样就能自动创建和管理模拟对象。使用@InjectMocks注解将被测试的对象自动注入到测试类中,使用@Mock注解创建模拟对象。 在testGetUserById方法中,我们首先使用when方法配置userRepository模拟对象的行为,表示当传入参数为"1"时,返回个指定的User对象。 然后,我们通过调用userService的getUserById方法来测试该方法的逻辑。最后,使用assertEquals断言来验证结果是否符合预期。 以上就是个使用Spring Boot、JUnitMockito进行单元测试的示例。通过使用Mockito创建模拟对象,我们可以更容易地测试各个方法的逻辑,而不依赖于实际的数据库。这样可以提高测试效率并确保代码的质量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值