隨着分佈式應用的開發逐漸成爲標配,多個微服務團隊合做來完成垂直業務的開發成爲了一種常態。微服務使得團隊能夠專一於本身的業務邏輯,在和下游依賴和上游對接的團隊聚焦好接口以後,就進入正式的開發。可是,每一個團隊的開發節奏每每不一樣,下游依賴所提供的服務有些時候不能在自測的時候提供穩定的服務。不只是多個團隊,單個團隊中每一個人所負責的模塊之間也會存在依賴關係,也就一樣存在這樣的問題。html
這時候,就須要先在代碼中模擬出依賴的服務,先確保本身開發的代碼中的主流程可以跑通後。等下游依賴的服務發佈後,再去除模擬的服務,用真實的服務測一遍。java
Mock服務能夠依賴於一些框架來實現,最經典的就是Mockito。爲何最近專門來研究一下Mock對象的方法,是由於以前爲了Mock下游服務直接修改了源代碼中的實現。舉個例子,原本應該從下游服務中根據用戶ID獲取用戶的詳情信息,包括用戶名,用戶年齡,用戶性別等。可是由於用戶中心的服務還沒有發佈,我直接修改了源代碼中的實現中,返回了一個虛擬的用戶信息。框架
public Interface UserService{ UserInfo getUser(String userId); } public Class UserServiceImpl implements UserService() { @Autowired private UserCenter userCenter; @Override public UserInfo getUser(String userId) { //註釋了對下游服務的訪問 //return userCenter.getUser(userId); //建立了虛擬的用戶信息並返回 UserInfo userInfo = new UserInfo(); userInfo.setUserName("xxx"); ... return userInfo; } }
緊接着,問題來了。在自測完成以後,我忘記了將源代碼中的註釋內容恢復,直接將Mock實現提交到了代碼倉庫中。由於這個服務不止我一個依賴方調用,致使別人在調用這個接口的時候發現不管怎麼修改用戶ID,得到的用戶數據都是同樣的。由此,我開始瞭解如何在不修改源代碼的狀況下,對服務進行Mock,避免下一次再出現這樣的問題。分佈式
Mockito是Java單元測試中使用率最高的Mock框架之一。它經過簡明的語法和完整的文檔吸引了大量的開發者。Mockito支持用Maven和Gradle來進行依賴引入和管理。這裏只給出Maven中引入依賴的例子:ide
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <scope>test</scope> </dependency>
下文以JUnit和Mockito兩個框架做爲基礎進行詳細說明微服務
依賴的服務1,name方法會返回名稱單元測試
public interface ReliedService { String name(); } @Service public class ReliedServiceImpl implements ReliedService { @Override public String name() { return "rale"; } }
依賴的服務2,welcome方法會返回歡迎語測試
public interface WelcomeLanguageService { String welcome(); } @Service public class WelcomeLanguageServiceImpl implements WelcomeLanguageService { @Override public String welcome() { return "wow"; } }
須要進行測試的服務DemoService。this
public interface DemoService { String hello(); } @Service public class DemoServiceImpl implements DemoService{ private ReliedService reliedService; private WelcomeLanguageService welcomeLanguageService; @Override public String hello() { return welcomeLanguageService.welcome() + " " + reliedService.name(); } //之因此採用setter的方式進行依賴注入,是爲了實現Mock對象的注入 @Autowired public void setReliedService(ReliedService reliedService) { this.reliedService = reliedService; } @Autowired public void setWelcomeLanguageService(WelcomeLanguageService welcomeLanguageService) { this.welcomeLanguageService = welcomeLanguageService; } }
直接使用Mockito提供的mock方法便可以模擬出一個服務的實例。再結合when/thenReturn等語法完成方法的模擬實現。code
import static org.mockito.Mockito.*; @DelegateTo(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = { Application.class }) public class MockDemo1 { private DemoService demoService; @Before public void before() { demoService = mock(DemoService.class); } @Test public void test() { when(demoService.hello()).thenReturn("hello my friend"); System.out.println(demoService.hello()); verify(demoService).hello(); } }
這裏給出了使用@Mock註解來Mock對象時的第一種實現,即便用MockitoAnnotations.initMocks(testClass)。
@DelegateTo(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = { Application.class }) public class MockDemo2 { @Mock private DemoService demoService; @Before public void before() { MockitoAnnotations.initMocks(this); } @Test public void test() { when(demoService.hello()).thenReturn("hello rale"); System.out.println(demoService.hello()); verify(demoService).hello(); } }
在測試用例上帶上了這個註解後,就能夠自由的使用@Mock來Mock對象啦。
@RunWith(MockitoJUnitRunner.class) @DelegateTo(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = { Application.class }) public class MockDemo3 { @Mock private DemoService demoService; @Test public void test() { when(demoService.hello()).thenReturn("hello rale"); System.out.println(demoService.hello()); verify(demoService).hello(); } }
這裏須要注意的是若是使用MockitoRule的話,該對象的訪問級別必須爲public。
@RunWith(JUnit4.class) @DelegateTo(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = { Application.class }) public class MockDemo4 { @Rule public MockitoRule rule = MockitoJUnit.rule(); @Mock private DemoService demoService; @Test public void test() { when(demoService.hello()).thenReturn("hello rale"); System.out.println(demoService.hello()); verify(demoService).hello(); } }
在上面四種方法中,最推薦的就是第二種方法,若是沒法使用@RunWith(MockitoJUnitRunner.class)
時,再考慮別的兼容的方法。
標準的Stub在上文中已經給出了簡單的例子,目前Mockito基於BDD(Behavior Driven Development)的思想還提供了相似的given/willReturn的語法。可是,Spring一樣做爲IOC框架,和Mockito的融合存在必定的問題。即若是須要對Spring Bean中的部分依賴進行Stub時,須要手動的去設置。
Mockito其實提供了一個很是方便的註解叫作@InjectMocks
,該註解會自動把該單元測試中聲明的Mock對象注入到該Bean中。可是,我在實驗的過程當中遇到了問題,即@InjectMocks
若是想要標記在接口上,則該接口必須手動初始化,不然會拋出沒法初始化接口的異常。可是,若是不使用Spring的自動注入,則必須手動的將該類依賴的別的Bean注入進去。
所以目前使用Mockito的妥協方案是直接@Autowire該接口的實現。而後在上面標記InjectMocks註解,此時會將測試中聲明的Mock對象自動注入,而沒有聲明的依賴的對象依然採用Spring Bean的依賴注入:
@RunWith(MockitoJUnitRunner.class) @DelegateTo(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = { Application.class }) public class InjectMockTest { @Mock private WelcomeLanguageService welcomeLanguageService; @Autowired @InjectMocks private DemoServiceImpl demoService; @Before public void before() { MockitoAnnotations.initMocks(this); given(welcomeLanguageService.welcome()).willReturn("hahaha"); } @Test public void test() { System.out.println(demoService.hello()); } }
DemoService中,WelcomeLanguageService會使用Mock對象,而ReliedService會使用Spring Bean自動注入。