針對Dubbo接口Mock的解決方案詳解
背景
為了提升減輕測試回歸壓力,提高項(xiàng)目開發(fā)交付質(zhì)量,我們開發(fā)和測試團(tuán)隊(duì)合作在部分項(xiàng)目內(nèi)執(zhí)行自動(dòng)化測試。
為了更好的理解,我們對mock的需要,先來看下wiki上對自動(dòng)化測試的解釋。
在軟件測試中, 自動(dòng)化測試指的是使用獨(dú)立于待測軟件的其他軟件來自動(dòng)執(zhí)行測試、比較實(shí)際結(jié)果與預(yù)期并生成測試報(bào)告這一過程。
我們的自動(dòng)化測試是按照我們預(yù)設(shè)的流程執(zhí)行的,我們不希望受到第三方服務(wù)的影響(上下線,接口返回錯(cuò)誤數(shù)據(jù)),所以在調(diào)用三方接口的時(shí)候,我們會(huì)采取mock,返回我們預(yù)期的數(shù)據(jù)。
在自動(dòng)化測試中,我們針對http,dubbo,mq消息這三種接口進(jìn)行了mock,本文講解的是我們對dubbo接口進(jìn)行mock的解決方案。
Dubbo目前提供方案
首先我們來看下dubbo框架本身提供的mock特性。
dubbo mock特性的核心代碼如下
//from MockClusterInvoker public Result invoke(Invocation invocation) throws RpcException { Result result = null; //獲取方法級(jí)別mock配置 String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim(); //沒有配置 或者 =false if (value.length() == 0 || value.equalsIgnoreCase("false")) { //no mock result = this.invoker.invoke(invocation); } else if (value.startsWith("force")) { // force 開頭 強(qiáng)制進(jìn)行mock if (logger.isWarnEnabled()) { logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl()); } //force:direct mock result = doMockInvoke(invocation, null); } else { //不是force的話 是失敗了再進(jìn)行mock //fail-mock try { result = this.invoker.invoke(invocation); } catch (RpcException e) { //如果是業(yè)務(wù)異常不進(jìn)行mock if (e.isBiz()) { throw e; } if (logger.isWarnEnabled()) { logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e); } result = doMockInvoke(invocation, e); } } return result; }
針對url中key=mock對應(yīng)value的不同,分別對應(yīng)3種邏輯
- value = null 不走mock
- value = force xxx 強(qiáng)制走mock邏輯
- value = xxx 調(diào)用服務(wù)失敗后走mock邏輯
看第三個(gè)邏輯,有沒有感覺到這其實(shí)是一個(gè)降級(jí),失敗降級(jí),而第二個(gè)邏輯,就稱為強(qiáng)制降級(jí)了。
dubbo提供的官方文檔,也將這個(gè)mock定義為降級(jí)
想了解Dubbo Mock表達(dá)式具體如何配置,可以看 Dubbo之降級(jí)Mock源碼分析
是否滿足我們需求
我們的需求是
- 第三方是否在線不影響我們的mock
- 配置靈活簡單
經(jīng)過測試,在設(shè)置check=false之后,給接口配置mock=force:return null之后,如果提供者不在線,會(huì)拋出沒有提供者異常,不滿足需求1
測試方式,對dubbo官方demo 增加如下配置
//from DemoServiceComponent @Reference(mock = "force:return null",check = false) private DemoService demoService;
對于需求2,也存在以下問題
- 我們不可能去動(dòng)原有項(xiàng)目中的dubbo配置,所以我們只能通過往dubbo的注冊中心增加override配置來觸發(fā)強(qiáng)制mock,使用上不方便
- 從第1點(diǎn)也可以看到,mock功能依賴注冊中心,我們的mock環(huán)境和測試環(huán)境都是使用同一個(gè)注冊中心,不可行
- mock value文檔不夠詳細(xì),針對復(fù)雜類型的返回,構(gòu)造費(fèi)勁
所以結(jié)論是,實(shí)現(xiàn)上和使用上都不能滿足我們需求,Dubbo的mock功能還是專注于生產(chǎn)級(jí)別的降級(jí)需求,我們需要開發(fā)方便我們使用的mock方案。
我們開發(fā)的擴(kuò)展方案
我們開發(fā)針對dubbo框架的mock方案設(shè)計(jì)要點(diǎn)如下
- 同樣的使用對Cluster擴(kuò)展點(diǎn)包裝類來植入mock邏輯,保證服務(wù)下線不影響我們自動(dòng)化測試運(yùn)行
- 使用properties配置文件來管理接口的mock開關(guān),配置可以托管到apollo,無代碼侵入
- 轉(zhuǎn)發(fā)請求到我司的EsayMock服務(wù)器,配置接口返回類型的Json數(shù)據(jù)即可
能不能用Filter來做
之前在網(wǎng)上看到過類似的方案是使用Filter來實(shí)現(xiàn)的,其實(shí)我們第一版也是通過Filter來做,但是存在一個(gè)問題,我們mock的接口的提供者必須在線。
下面從源碼的角度來解釋為何出現(xiàn)這個(gè)問題
在使用zookeeper為注冊中心,以及check=false的前提下
Filter邏輯的植入是通過Protocol的包裝類ProtocolFilterWrapper,ProtocolFilterWrapper會(huì)對除了RegistryProtocol的其他Protocol植入Filter調(diào)用鏈邏輯。
//from ProtocolFilterWrapper public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { return protocol.refer(type, url); } return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER); }
問題就出在RegistryProtocol,RegistryProtocol通過Cluster,Directory模塊間接依賴了DubboProtocl,而在Directory模塊中,也就是RegistryDirectory中會(huì)對提供者數(shù)量進(jìn)行檢查,如果為0,會(huì)拋出異常。這一切都發(fā)生在對DubboProtocl生成的invoker調(diào)用之前。
DubboProtocol生成的invoker,封裝了filter邏輯以及對遠(yuǎn)端服務(wù)調(diào)用邏輯
RegistryProtcol生成的invoekr,在DubboProtocol基礎(chǔ)上封裝了集群調(diào)用,負(fù)載均衡等服務(wù)治理功能
//from RegistryDirectory private void refreshInvoker(List<URL> invokerUrls) { Assert.notNull(invokerUrls, "invokerUrls should not be null"); //這邊為什么是一個(gè),針對沒有提供者目錄,dubbo框架會(huì)自動(dòng)返回一個(gè)empty的url if (invokerUrls.size() == 1 && invokerUrls.get(0) != null && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) { this.forbidden = true; // Forbid to access this.invokers = Collections.emptyList(); routerChain.setInvokers(this.invokers); destroyAllInvokers(); // Close all invokers } //... } public List<Invoker<T>> doList(Invocation invocation) { if (forbidden) { // 1. No service provider 2. Service providers are disabled throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist)."); } //... }
沒看過dubbo源碼的朋友可能看不懂,你可以看了dubbo refer原理之后再來品味
不足
mock服務(wù)器中的json和dubbo接口不是強(qiáng)關(guān)聯(lián),不過問題不大,我們跑的都是預(yù)設(shè)流程。
開源項(xiàng)目
講了這么多,都是原理性的內(nèi)容,下面貼上鏈接,歡迎大家使用以及提建議。
以上就是針對Dubbo接口Mock的解決方案詳解的詳細(xì)內(nèi)容,更多關(guān)于Dubbo接口Mock解決的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
全網(wǎng)最深分析SpringBoot MVC自動(dòng)配置失效的原因
這篇文章主要介紹了全網(wǎng)最深分析SpringBoot MVC自動(dòng)配置失效的原因,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07SpringBoot配置連接兩個(gè)或多個(gè)數(shù)據(jù)庫的實(shí)現(xiàn)
本文主要介紹了SpringBoot配置連接兩個(gè)或多個(gè)數(shù)據(jù)庫的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05SpringBoot整合Quartz實(shí)現(xiàn)定時(shí)任務(wù)詳解
這篇文章主要介紹了Java?任務(wù)調(diào)度框架?Quartz,Quartz是OpenSymphony開源組織在Job?scheduling領(lǐng)域又一個(gè)開源項(xiàng)目,完全由Java開發(fā),可以用來執(zhí)行定時(shí)任務(wù),類似于java.util.Timer。,下面我們來學(xué)習(xí)一下關(guān)于?Quartz更多的詳細(xì)內(nèi)容,需要的朋友可以參考一下2022-08-08Java并發(fā)系列之ReentrantLock源碼分析
這篇文章主要為大家詳細(xì)介紹了Java并發(fā)系列之ReentrantLock源碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02