单元测试中使用Mockito模拟对象
單元測(cè)試應(yīng)該小巧玲瓏,輕盈快捷。然而,一個(gè)待測(cè)的對(duì)象可能依賴另一個(gè)對(duì)象。它可能需要跟數(shù)據(jù)庫、郵箱服務(wù)器、Web Service、消息隊(duì)列等服務(wù)進(jìn)行交互。但是,這些服務(wù)可能在測(cè)試過程中不可用。假設(shè)這些服務(wù)可用,依賴這些服務(wù)的單元測(cè)試可能相當(dāng)耗時(shí)。要是
這些違背單元測(cè)試小巧玲瓏,輕盈快捷的初衷。單元測(cè)試被期待在幾毫秒內(nèi)執(zhí)行完成。若單元測(cè)試緩慢,你的開發(fā)過程受阻,這會(huì)影響你開發(fā)組的效率。解決之道就是模擬(Mocking),
若你遵循OOP的SOILD原則,且使用Spring的依賴注入,單元測(cè)試中的模擬Mock變得輕而易舉。你不必連接數(shù)據(jù)庫。你只需一個(gè)能返回你期待值的對(duì)象。若你編寫緊密耦合代碼,模擬會(huì)相當(dāng)艱難。我目睹過許多因緊密耦合其它對(duì)象的遺留代碼不能單元測(cè)試。不可測(cè)試代碼不遵循OOP的SOILD原則,且不能使用依賴注入。
Mockito初體驗(yàn)
接下來將學(xué)習(xí)使用Mockito框架。它是一套通過簡(jiǎn)單的方法對(duì)于指定接口或類生產(chǎn)Mock對(duì)象的類庫。使用Mockito,在準(zhǔn)備階段只需少量時(shí)間,可以使用簡(jiǎn)潔的API編寫漂亮的測(cè)試,可以對(duì)具體類創(chuàng)建Mock對(duì)象,并且有監(jiān)視非Mock對(duì)象的功能。
這有兩個(gè)術(shù)語需要了解一下。
-
Stub對(duì)象作用是在測(cè)試時(shí)提供所需的測(cè)試數(shù)據(jù),可以對(duì)各種交互設(shè)置相應(yīng)的回應(yīng)。Mockito中when(...).thenReturn(...)這樣的語法便是設(shè)置方法調(diào)用的返回值。另外也可以設(shè)置方法在何時(shí)調(diào)用會(huì)拋異常等。
-
Mock對(duì)象用來驗(yàn)證測(cè)試中所依賴對(duì)象間的交互是否能夠達(dá)到預(yù)期。Mockito中用verify(...).methodXxx(...)語法驗(yàn)證methodXxx()方法是否按照預(yù)期進(jìn)行調(diào)用。
需要加入到pom.xml的依賴如下:
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>2.16.0</version><scope>test</scope> </dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope> </dependency>創(chuàng)建Mock對(duì)象
可以通過兩種方法來Mock對(duì)象
值得注意的是,對(duì)于final類、匿名類和Java的基本類型是無法進(jìn)行Mock的。
設(shè)定Mock對(duì)象的期望值行為及返回值
有兩種通用基礎(chǔ)設(shè)定寫法:
但是,doReturn(...).when(mockObj.someMethod());會(huì)拋異常。
@Test public void testMockClass() {// 對(duì)方法設(shè)定返回值,也就是設(shè)置數(shù)據(jù)樁when(mockServiceImpl.findUserByUserName("tom")).thenReturn(new User("tom", "1234"));doReturn(true).when(mockServiceImpl).hasMatchUser("tom", "1234");User user = mockServiceImpl.findUserByUserName("tom");boolean isMatch = mockServiceImpl.hasMatchUser("tom", "1234");assertNotNull(user);assertEquals(user.getUserName(), "tom");assertEquals(isMatch, true);}也值得注意的是,static和final修飾的方法無法進(jìn)行設(shè)定的。
驗(yàn)證交互行為
Mock對(duì)象一旦建立便會(huì)自動(dòng)記錄自己的交互行為,所以,可以有選擇地對(duì)其交互行為進(jìn)行驗(yàn)證。
@Test // 模擬接口UserService測(cè)試 public void testMockInterface() {// 對(duì)方法設(shè)定返回值,也就是設(shè)置數(shù)據(jù)樁when(mockUserService.findUserByUserName("tom")).thenReturn(new User("tom", "1234"));doReturn(true).when(mockUserService).hasMatchUser("tom", "1234");// 對(duì)void方法進(jìn)行方法預(yù)期設(shè)定User u = new User("John", "1234");doNothing().when(mockUserService).registerUser(u);// 執(zhí)行方法調(diào)用User user = mockUserService.findUserByUserName("tom");boolean isMatch = mockUserService.hasMatchUser("tom", "1234");mockUserService.registerUser(u);assertNotNull(user);assertEquals(user.getUserName(), "tom");assertEquals(isMatch, true);// 驗(yàn)證交互行為verify(mockUserService).findUserByUserName("tom");// 驗(yàn)證方法只調(diào)用一次verify(mockUserService, times(1)).findUserByUserName("tom");// 驗(yàn)證方法至少調(diào)用一次verify(mockUserService, atLeastOnce()).findUserByUserName("tom");verify(mockUserService, atLeast(1)).findUserByUserName("tom");// 驗(yàn)證方法至多調(diào)用一次verify(mockUserService, atMost(1)).findUserByUserName("tom");verify(mockUserService).hasMatchUser("tom", "1234");verify(mockUserService).registerUser(u); }對(duì)Service層進(jìn)行單元測(cè)試
同常主要Java Web應(yīng)用分Controller,Service,DAO基本三層來進(jìn)行開發(fā)。
接下來通過使用Mockito框架對(duì)Service進(jìn)單元測(cè)試。
Domain領(lǐng)域?qū)ο?#xff1a;
public class Product {}DAO數(shù)據(jù)連接層:
public interface ProductDao {int getAvailableProducts(Product product);int orderProduct(Product product, int orderedQuantity); }Service業(yè)務(wù)邏輯層:
public class ProductService {private ProductDao productDao;public boolean buy(Product product, int orderedQuantity) {int availableQuantity = productDao.getAvailableProducts(product);if (orderedQuantity > availableQuantity) {return false;}productDao.orderProduct(product, orderedQuantity);return true;}}Service測(cè)試用例:
public class ProductServiceTest {private ProductDao productDao;@Beforepublic void setupMock() {//模擬Dao層productDao = mock(ProductDao.class);}@Testpublic void testBuy() {int availableQuantity = 30;Product product = new Product();ProductService productService = new ProductService();//設(shè)置數(shù)據(jù)樁when(productDao.getAvailableProducts(product)).thenReturn(availableQuantity);//doReturn(availableQuantity).when(productDao).getAvailableProducts(product);//這寫法不行//doReturn(availableQuantity).when(productDao.getAvailableProducts(product));//通過Spring測(cè)試框架提供的工具類為目標(biāo)對(duì)象私有屬性設(shè)值,這樣就不用ProductDao另建setProductDao()方法ReflectionTestUtils.setField(productService, "productDao", productDao);Assert.assertFalse(productService.buy(product, 31));Assert.assertTrue(productService.buy(product, 3));//驗(yàn)證交互行為verify(productDao).orderProduct(product, 3);verify(productDao, times(2)).getAvailableProducts(product);}}測(cè)試用例中,用到Spring test框架的ReflectionTestUtils,它可以為目標(biāo)對(duì)象非公有屬性設(shè)值,或調(diào)用非公有setter方法,方便測(cè)試過程中使用。
為了使用ReflectionTestUtils,需要向pom.xml添加下面的依賴
<dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>3.0.5.RELEASE</version><scope>test</scope> </dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>3.0.5.RELEASE</version><scope>test</scope> </dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version><scope>test</scope> </dependency>總結(jié)
本文介紹了Mockito的基本用法,以及通過它Mock對(duì)象對(duì)Service層輔助測(cè)試用例。在Mockito輔助下,單元測(cè)試變得如虎添翼啊!
在編寫代碼過程中,必須反復(fù)調(diào)試它,保證他順利通過。雖代碼通過編譯,只是說明其語法正確,但不能保證其語義也正確。沒有任何人可以輕易承諾這段代碼的行為一定是正確的。幸運(yùn)的是,單元測(cè)試會(huì)為我們的承諾作出保證。編寫單元測(cè)試就是用來驗(yàn)證這段代碼的行為是否與我們期望的一樣。有了單元測(cè)試,我們可以自信地交付自己的代碼,減少后顧之憂。
引用
Mocking in Unit Tests with Mockito
Mockito (Mockito 2.16.0 API)
《Spring 3.x企業(yè)應(yīng)用開發(fā)實(shí)戰(zhàn)》陳雄華、林開雄 著
Spring Framework Reference Documentation 11. Testing
總結(jié)
以上是生活随笔為你收集整理的单元测试中使用Mockito模拟对象的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: KKT条件初步理解
- 下一篇: 《深入理解JVM.2nd》笔记(一):走