溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

微服務(wù)架構(gòu)—自動(dòng)化測(cè)試全鏈路設(shè)計(jì)

發(fā)布時(shí)間:2020-06-10 07:10:35 來源:網(wǎng)絡(luò) 閱讀:22400 作者:王清培 欄目:軟件技術(shù)

微服務(wù)架構(gòu)—自動(dòng)化測(cè)試全鏈路設(shè)計(jì)

標(biāo)簽:microServices autoTest mock unitTest testTrace

  • 背景
  • 被忽視的軟件工程環(huán)節(jié) - DEVTESTOPS
  • 微服務(wù)架構(gòu)下測(cè)試復(fù)雜度和效率問題
  • 開發(fā)階段 unitTest mock 外部依賴
  • 連調(diào)階段 mock 外部依賴
  • 自動(dòng)化測(cè)試階段 mock 需求
  • autoTest Mock Gateway 浮出水面
  • 輕量級(jí)版本實(shí)現(xiàn)
    • 整體邏輯架構(gòu)
    • mock parameter 納入服務(wù)框架標(biāo)準(zhǔn) request contract
    • 使用 AOP + RestEasy HttpClientRequest SPI 初步實(shí)現(xiàn) Mock
  • 總結(jié)

背景

SOA 架構(gòu)到現(xiàn)在大行其道的微服務(wù)架構(gòu),系統(tǒng)越拆越小,整體架構(gòu)的復(fù)雜度也是直線上升,我們一直老生常談的微服務(wù)架構(gòu)下的技術(shù)難點(diǎn)及解決方案也日漸成熟(包括典型的數(shù)據(jù)一致性,系統(tǒng)調(diào)用帶來的一致性問題,還是跨節(jié)點(diǎn)跨機(jī)房復(fù)制帶來的一致性問題都有了很多解決方案),但是有一個(gè)環(huán)節(jié)我們明顯忽略了。

在現(xiàn)在的微服務(wù)架構(gòu)趨勢(shì)下,微服務(wù)在運(yùn)維層面和自動(dòng)化部署方面基本上是比較完善了。從我個(gè)人經(jīng)驗(yàn)來看,上層的開發(fā)、測(cè)試對(duì)微服務(wù)架構(gòu)帶來的巨大變化還在反應(yīng)和學(xué)習(xí)中。

開發(fā)層面討論微服務(wù)的更多是框架、治理、性能等,但是從完整的軟件工程來看我們嚴(yán)重缺失分析、設(shè)計(jì)知識(shí),這也是我們現(xiàn)在的工程師普遍缺乏的技術(shù)。

我們經(jīng)常會(huì)發(fā)現(xiàn)一旦你想重構(gòu)點(diǎn)東西是多么的艱難,就是因?yàn)樵诔跗跇?gòu)造這棟建筑的時(shí)候嚴(yán)重缺失了通盤的分析、設(shè)計(jì),最終導(dǎo)致這個(gè)建筑慢慢僵化最后人見人怕,因?yàn)樗饾u變成一個(gè)怪物。(比如,開發(fā)很少寫 unitTest ,我們總是忽視單元測(cè)試背后產(chǎn)生的軟件工程的價(jià)值。)

被忽視的軟件工程環(huán)節(jié) — DEVTESTOPS

我們有沒有發(fā)現(xiàn)一個(gè)現(xiàn)象,在整個(gè)軟件過程里,測(cè)試這個(gè)環(huán)節(jié)容易被忽視。任何一種軟件工程模型都有 QA 環(huán)節(jié),但是這個(gè)環(huán)節(jié)似乎很薄很弱,目前我們絕大多數(shù)工程師、架構(gòu)師都嚴(yán)重低估了這個(gè)環(huán)節(jié)的力量和價(jià)值,還停留在無技術(shù)含量,手動(dòng)功能測(cè)試低級(jí)效率印象里。

這主要是測(cè)試這個(gè)角色整個(gè)技術(shù)體系、工程化能力偏弱,一部分是客觀大環(huán)境問題,還有一部分自身問題,沒有讓自己走出去,多去學(xué)習(xí)整個(gè)工程化的技術(shù),多去了解開發(fā)的技術(shù),生產(chǎn)上的物理架構(gòu),這會(huì)有助于測(cè)試放大自己的聲音。

導(dǎo)致測(cè)試環(huán)節(jié)在國內(nèi)整個(gè)設(shè)計(jì)創(chuàng)新薄弱的原因還有一個(gè)主要原因就是,開發(fā)工程師普遍沒有完整的工程基礎(chǔ)。在國外IT發(fā)達(dá)國家,日本、美國等,一個(gè)合格的開發(fā)工程師、測(cè)試工程師都是邊界模糊的,自己開發(fā)產(chǎn)品自己測(cè)試,這需要切換思維模式,需要同時(shí)具備這兩種能力,但是這才是整個(gè)軟件工程的完整流程。

我們有沒有想過一個(gè)問題,為什么現(xiàn)在大家都在談?wù)?DevOps,而不是 DevTestOps,為什么偏偏跳過測(cè)試這個(gè)環(huán)節(jié),難道開發(fā)的系統(tǒng)需要具備良好的可運(yùn)維性就不需要可測(cè)試性嗎,開發(fā)需要具備運(yùn)維能力,運(yùn)維需要具備開發(fā)能力,為什么測(cè)試環(huán)節(jié)忽略了。

我們對(duì) QA 環(huán)節(jié)的輕視,對(duì)測(cè)試角色的不重視其實(shí)帶來的副作用是非常大的。

微服務(wù)架構(gòu)下測(cè)試復(fù)雜度和效率問題

微服務(wù)的拆分粒度要比 SOA 細(xì)了很多,從容器化鏡像自動(dòng)部署來衡量,是拆小了之后很方便,但是拆小了之后會(huì)給整個(gè)開發(fā)、測(cè)試環(huán)節(jié)增加很大的復(fù)雜度和效率問題。

SOA 時(shí)期,契約驅(qū)動(dòng) 這個(gè)原則在微服務(wù)里也一樣適用,跨部門需求定義好契約你就可以先開發(fā)上線了。但是這個(gè)里面最大的問題就是當(dāng)前系統(tǒng)的部分連調(diào)問題和自動(dòng)化回歸問題,如果是新系統(tǒng)上線還需要做性能壓測(cè),這外部的依賴如何解決。

也許我們會(huì)說,不是應(yīng)該依賴方先ready,然后我們緊接著進(jìn)行測(cè)試、發(fā)布嗎。如果是業(yè)務(wù)、架構(gòu)合理的情況下,這種場(chǎng)景最大的問題就是我們的項(xiàng)目容易被依賴方牽制,這會(huì)帶來很多問題,比如,研發(fā)人員需要切換出來做其他事情,branch 一直掛著,不知道哪天突然來找你說可以對(duì)接了,也許這已經(jīng)過去一個(gè)月或者更久,這種方式一旦養(yǎng)成習(xí)慣性研發(fā)流程就很容易產(chǎn)生線上 BUG 。

還有一種情況也是合理的情況就是平臺(tái)提供方需要調(diào)用業(yè)務(wù)方的接口,這里面有一般調(diào)用的 callback 接口、交易鏈路上的 marketing 接口、配送 routing 接口等。

這里給大家分享我們目前正在進(jìn)行中的 marketing-cloud (營銷云) 規(guī)則引擎 項(xiàng)目。

marketing-cloud 提供了一些營銷類業(yè)務(wù),有 團(tuán)購、優(yōu)惠券促銷 等,但是我們的業(yè)務(wù)方需要有自己個(gè)性化的營銷活動(dòng)玩法,我們需要在 marketing-cloud 規(guī)則引擎 中抽象出業(yè)務(wù)方營銷活動(dòng)的返回信息,同時(shí)打通個(gè)性化營銷活動(dòng)與公共交易、結(jié)算環(huán)節(jié),形成一個(gè)完整的業(yè)務(wù)流。

微服務(wù)架構(gòu)—自動(dòng)化測(cè)試全鏈路設(shè)計(jì)

這是一個(gè) marketing-cloud 邏輯架構(gòu)圖,跟我們主題相關(guān)的就是 營銷規(guī)則引擎 ,他就是我們這里所說的合理的業(yè)務(wù)場(chǎng)景。

在整個(gè)正向下單過程中,營銷規(guī)則引擎要肩負(fù)起既要提供 marketing-cloud 內(nèi)的共用營銷活動(dòng),還需要橋接外部營銷中心的各類營銷玩法,外部的營銷中心會(huì)有多個(gè),目前我們主要有兩個(gè)。

由于這篇文章不是介紹營銷平臺(tái)怎么設(shè)計(jì),所以這里不打算擴(kuò)展話題。主要是起到拋磚引玉的目的,平臺(tái)型的業(yè)務(wù)會(huì)存在各種各樣的對(duì)外系統(tǒng)依賴的業(yè)務(wù)場(chǎng)景。文章接下來的部分將展開 marketing-cloud 規(guī)則引擎 在打通測(cè)試鏈路上的實(shí)踐。

開發(fā)階段 unitTest mock 外部依賴

在開發(fā)階段,我們會(huì)經(jīng)常性的編寫單元測(cè)試來測(cè)試我們的邏輯,在編寫 unitTest 的時(shí)候都需要 mock 周邊的依賴,mock 出來的對(duì)象分為兩種類型,一種是不具有 Assert 邏輯的 stub 樁 對(duì)象,還有一種就是需要支持 Assertmocker 模擬對(duì)象。

但是我們也不需要明顯區(qū)分他們,兩者的區(qū)別不是太明顯,在編碼規(guī)范內(nèi)可能需要區(qū)分。

我們關(guān)心的是如何解決對(duì)象之間的依賴問題,各種 mock 框架其實(shí)提供了很多非常好用的工具,我們可以很輕松的 mock 周邊的依賴。

given(marketingService.mixMarketingActivity(anyObject())).willReturn(stubResponse);
RuleCalculateResponse response = this.ruleCalculatorBiz.ruleCalculate(request);

這里我們 mockmarketingService.mixMarketingActivity() 方法。

Java 世界里提供了很多好用的 mock 框架,比較流行好用的框架之一 mockito 可以輕松 mock Service 層的依賴,當(dāng)然除了 mockito 之外還有很多優(yōu)秀的 mock 框架。

這些框架大同小異,編寫 unitTest 最大的問題就是如何重構(gòu)邏輯使之更加便于測(cè)試,也就是代碼是否具備很好的可測(cè)試性,是否已經(jīng)消除了絕大多數(shù) private 方法,private 方法是否有某些指責(zé)是我們沒有捕捉到業(yè)務(wù)概念。

連調(diào)階段 mock 外部依賴

在我們完成了所有的開發(fā),完善的單元測(cè)試保證了我們內(nèi)部的邏輯是沒有問題的(當(dāng)然這里不討論 unitTestcase 的設(shè)計(jì)是否完善情況)。

現(xiàn)在我們需要對(duì)接周邊系統(tǒng)開發(fā)進(jìn)行連調(diào)了,這個(gè)周邊系統(tǒng)還是屬于本平臺(tái)之類的其他支撐系統(tǒng)。比如我們的 marketing-cloud 規(guī)則引擎系統(tǒng)下單系統(tǒng) 之間的關(guān)系。在開發(fā)的時(shí)候我們編寫 unitTest 是順利的完成了開發(fā)解決的驗(yàn)證工作,但是現(xiàn)在面對(duì)連調(diào)問題。

系統(tǒng)需要正式的跑起來,但是我們?nèi)狈?duì)外部營銷中心的依賴,我們?cè)趺崔k。其實(shí)我們也需要在連調(diào)階段 mock 外部依賴,只不過這個(gè) mock 的技術(shù)和方法不是通過 unitTest 框架來支持,而是需要我們自己來設(shè)計(jì)我們的整個(gè)服務(wù)的開發(fā)架構(gòu)。

首先要能識(shí)別本次 request 是需要 mock 的,那就需要某種 mock parameter 參數(shù)來提供識(shí)別能力。

我們來看下 marketing-cloud 營銷規(guī)則引擎 在這塊的一個(gè)初步嘗試。

public interface CCMarketingCentralFacade {
    CallResponse callMarketingCentral(CallRequest request);
}
public interface ClassMarketingCentralFacade {
    CallResponse callMarketingCentral(CallRequest request);
}

營銷規(guī)則引擎使用 RestEasy client api 作為 rest 調(diào)用框架。這兩個(gè) Facade 是營銷平臺(tái)對(duì) CCTalk滬江網(wǎng)校 滬江兩大子公司營銷中心發(fā)起調(diào)用的 Facade。

(為了盡量還原我們的工程實(shí)踐干貨同時(shí)需要消除一些敏感信息的情況下,整篇文章所有的代碼實(shí)例,我都刪除了一些不影響閱讀且和本文無關(guān)的代碼,同時(shí)做了一些偽編碼和省略,使代碼更精簡(jiǎn)更便于閱讀。)

在正常邏輯下,我們會(huì)根據(jù)營銷路由 key 來決定調(diào)用哪個(gè)公司的營銷中心接口,但是由于我們?cè)陂_發(fā)這個(gè)項(xiàng)目的時(shí)候暫時(shí)業(yè)務(wù)方還沒有存在的地址讓我們對(duì)接,所以我們自己做了 mock facade,來解決連調(diào)問題。

public class CCMarketingCentralFacadeMocker implements CCMarketingCentralFacade {

    @Override
    public CallResponse callMarketingCentral(CallRequest request) {

        CallResponse response = ...
        MarketingResultDto marketingResultDto = ...
        marketingResultDto.setTotalDiscount(new BigDecimal("90.19"));
        marketingResultDto.setUseTotalDiscount(true);

        response.getData().setMarketingResult(marketingResultDto);

        return response;
    }
}
public class ClassMarketingCentralFacadeMocker implements ClassMarketingCentralFacade {

    @Override
    public CallResponse callMarketingCentral(CallRequest request) {
        CallResponse response = ...

        MarketingResultDto marketingResultDto = ...
        marketingResultDto.setUseCoupon(true);
        marketingResultDto.setTotalDiscount(null);
        marketingResultDto.setUseTotalDiscount(false);

        List<MarketingProductDiscountDto> discountDtos = ...

        request.getMarketingProductTagsParameter().getMarketingTags().forEach(item -> {

            MarketingProductDiscountDto discountDto = ...
            discountDto.setProductId(item.getProductID());
            ...
            discountDtos.add(discountDto);
        });
...
        return response;
    }
}

我們定義了兩個(gè) mock 類,都是一些測(cè)試數(shù)據(jù),就是為了解決在連調(diào)階段的問題,也就是在 DEV 環(huán)境上的依賴問題。

有了 mock facade 之后就需要 request 定義 mock parameter 參數(shù)了。

public abstract class BaseRequest implements Serializable {
    public MockParameter mockParameter;
}
public class MockParameter {

    /**
     * mock cc 營銷調(diào)用接口
     */
    public Boolean mockCCMarketingInterface;

    /**
     * mock class 營銷調(diào)用接口
     */
    public Boolean mockClassMarketingInterface;

    /**
     * 是否自動(dòng)化測(cè)試 mock
     */
    public Boolean useAutoTestMock;

    /**
     * 測(cè)試mock參數(shù)
     */
    public String testMockParam;

}

我們暫且忽略通用型之類的設(shè)計(jì),這里只是我們?cè)谮s項(xiàng)目的情況下做的一個(gè)迭代嘗試,等我們把這整個(gè)流程都跑通了再來考慮重構(gòu)提取框架。

有了輸入?yún)?shù),我們就可以根據(jù)參數(shù)判斷來動(dòng)態(tài)注入 mock facade。

自動(dòng)化測(cè)試階段 mock 需求

我們繼續(xù)向前推進(jìn),過了連調(diào)階段緊接著就進(jìn)入測(cè)試環(huán)節(jié),現(xiàn)在基本上大多數(shù)互聯(lián)網(wǎng)公司都是自動(dòng)化的測(cè)試,很少在有手動(dòng)的,尤其是后端系統(tǒng)。

那么在 autoTest 階段面臨的一個(gè)問題就是,我們需要一個(gè)公共的 autoTest 地址,這個(gè)測(cè)試地址是不變的,我們?cè)谧詣?dòng)化測(cè)試下 mockfacade bean 的地址就是這個(gè)地址,這個(gè)地址輸出的值需要能夠?qū)?yīng)到每次自動(dòng)化腳本執(zhí)行的上下文中。

我們有很多微服務(wù)系統(tǒng)來組成一個(gè)平臺(tái),每個(gè)服務(wù)都有依賴的第三方接口,原來在自動(dòng)化測(cè)試這些服務(wù)的時(shí)候都需要去了解業(yè)務(wù)方系統(tǒng)的接口、DB、前臺(tái)入口等,因?yàn)樵诰帉懽詣?dòng)化腳本的時(shí)候需要同步創(chuàng)建測(cè)試數(shù)據(jù),最后才能 Assert。

這個(gè)跨部門的溝通和協(xié)作效率嚴(yán)重低下,而且人員變動(dòng)、系統(tǒng)變動(dòng)都會(huì)直接影響上線周期,這里絕對(duì)值得創(chuàng)新來解決這個(gè)效率嚴(yán)重阻塞問題。

@Value("${marketing.cloud.business.access.url.mock}")
private String mockUrl;
/**
     * 自動(dòng)化測(cè)試 mocker bean
     */
    @Bean("CCMarketingCentralFacadeTestMock")
    public CCMarketingCentralFacade CCMarketingCentralFacadeTestMock() {
        RestClientProxyFactoryBean<CCMarketingCentralFacade> restClientProxyFactoryBean ...
        restClientProxyFactoryBean.setBaseUri(this.mockUrl);
        ...
    }

    /**
     * 自動(dòng)化測(cè)試 mocker bean
     */
    @Bean("ClassMarketingCentralFacadeTestMock")
    public ClassMarketingCentralFacade ClassMarketingCentralFacadeTestMock()  {
        RestClientProxyFactoryBean<ClassMarketingCentralFacade> restClientProxyFactoryBean ...
        restClientProxyFactoryBean.setBaseUri(this.mockUrl);
        ...
    }

這里的 mockUrl 就是我們抽象出來的統(tǒng)一的 autoTest 地址,在前面的 mock parameter 中有一個(gè) useAutoTestMock Boolean 類型的參數(shù),如果當(dāng)前請(qǐng)求此參數(shù)為 true,我們將動(dòng)態(tài)注入自動(dòng)化測(cè)試 mock bean ,后續(xù)的所有調(diào)用都會(huì)走到 mockUrl 指定的地方。

autoTest Mock Gateway 浮出水面

到目前為止,我們遇到了自動(dòng)化測(cè)試統(tǒng)一的 mock 地址要收口所有微服務(wù)在這方面的需求?,F(xiàn)在最大的問題就是,所有的微服務(wù)對(duì)外依賴的 response 都不相同,自動(dòng)化腳本在執(zhí)行的時(shí)候預(yù)先創(chuàng)建好的 response 要能適配到當(dāng)前測(cè)試的上下文中。

比如,營銷規(guī)則引擎,我們的自動(dòng)化腳本在創(chuàng)建一個(gè)訂單的時(shí)候需要預(yù)先構(gòu)造好當(dāng)前商品(比如,productID:101010),在獲取外部營銷中心提供的活動(dòng)信息和抵扣信息的 response ,最后才能去 Assert 訂單的金額和活動(dòng)信息記錄是否正確,這就是一次 autoTest context 。

微服務(wù)架構(gòu)—自動(dòng)化測(cè)試全鏈路設(shè)計(jì)

有兩種方式來識(shí)別當(dāng)前 autoTest context ,一種是在 case 執(zhí)行的時(shí)候確定商品ID,最后通過商品ID來獲取 mockresponse 。還有一種就是支持傳遞 autoTest mock 參數(shù)給到 mockUrl 指定的服務(wù),可以使用這個(gè)參數(shù)來識(shí)別當(dāng)前測(cè)試上下文。

一個(gè)測(cè)試 case 可能會(huì)穿過很多微服務(wù),這些所有的依賴服務(wù)可能都需要預(yù)設(shè) mock response,這基本上是一勞永逸的。

所以,我們抽象出了 autoTest Mock Gateway(自動(dòng)化測(cè)試mock網(wǎng)關(guān)服務(wù)) ,在整個(gè)自動(dòng)化測(cè)試環(huán)節(jié)還有很多需要支持的工作,服務(wù)之間的鑒權(quán),鑒權(quán) keymock,加解密,加解密 keymock,自動(dòng)化測(cè)試 case 交替并行執(zhí)行等。

作為工程師的我們都希望用系統(tǒng)化、工程化的方式來解決整體問題,而不是個(gè)別點(diǎn)狀問題。有了這個(gè) mock gateway 我們可以做很多事情,也可以普惠所有需要的其他部門。

微服務(wù)架構(gòu)—自動(dòng)化測(cè)試全鏈路設(shè)計(jì)

在一次 autoTest context 里構(gòu)造好 mock response,然后通過 mock parameter 來動(dòng)態(tài)識(shí)別具體的來源服務(wù)進(jìn)行路由、鑒權(quán)、加解密等操作。

MockGateway 是一個(gè)支點(diǎn),我相信這個(gè)支點(diǎn)可以撬動(dòng)很多測(cè)試空間和創(chuàng)新能力。

輕量級(jí)版本實(shí)現(xiàn)

接下來我們將展示在 marketing-cloud 營銷規(guī)則引擎 中的初步嘗試。

整體邏輯架構(gòu)

微服務(wù)架構(gòu)—自動(dòng)化測(cè)試全鏈路設(shè)計(jì)

自動(dòng)化腳本在每跑一個(gè) case 的時(shí)候會(huì)創(chuàng)建當(dāng)前 case 對(duì)應(yīng)的 autoTestContext,這里面都是一些 meta data,用來表示這個(gè) case 中所有涉及到的微服務(wù)系統(tǒng)哪些是需要走 mock gateway 的。

mockGateway 中所有的配置都是有一個(gè) autoTestContext 所對(duì)應(yīng),如果沒有 autoTestContext 說明是所有 case 共用。

將 mock parameter 納入服務(wù)框架標(biāo)準(zhǔn) request contract

要想打通整個(gè)微服務(wù)架構(gòu)中的所有通道,就需要在標(biāo)準(zhǔn) request contract 定義 mockParameter ,這是這一切的前提。

服務(wù)與服務(wù)之間調(diào)用走標(biāo)準(zhǔn)微服務(wù) request contract,服務(wù)與外部系統(tǒng)的依賴可以選擇走 HTTP Header,也可以選擇走標(biāo)準(zhǔn) request ,就要看我們的整個(gè)服務(wù)框架是否已經(jīng)覆蓋所有的產(chǎn)線及一些遺留系統(tǒng)的問題。

public abstract class BaseRequest implements Serializable {
    public MockParameter mockParameter;
}

BaseRequest 是所有 request 的基類,這樣才能保證所有的請(qǐng)求能夠正常的傳遞。

使用 AOP + RestEasy HttpClientRequest SPI 初步實(shí)現(xiàn) Mock

整個(gè)系統(tǒng)的開發(fā)架構(gòu)分層依賴是:facade->biz->service,基本的所有核心邏輯都是在 service 中,請(qǐng)求的 request dto 最多不能越界到 service 層,按照規(guī)范講 request dto 頂多滯留在 biz 層,但是在互聯(lián)網(wǎng)的世界中一些都是可以快速迭代的,并不是多么硬性規(guī)定,及時(shí)重構(gòu)是償還技術(shù)債務(wù)的主要方法。

前面我們已經(jīng)講過,我們采用的 RPC 框架是 RestEasy + RestEasy client ,我們先來看下入口的地方。

@Component
@Path("v1/calculator/")
public class RuleCalculatorFacadeImpl extends BaseFacade implements RuleCalculatorFacade {
    @MockFacade(Setting = MockFacade.SETTING_REQUEST_MOCK_PARAMETER)
    public RuleCalculateResponse ruleCalculate(RuleCalculateRequest request)  {
    ...
    }
}

再看下 service 對(duì)象。

@Component
public class MarketingServiceImpl extends MarketingBaseService implements MarketingService {
    @MockFacade(Setting = MockFacade.SETTING_FACADE_MOCK_BEAN)
    public MarketingResult onlyExtendMarketingActivity(Marketing..Parameter tagsParameter) {
    ...
    }

我們重點(diǎn)看下 @MockFacade annotation 聲明。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MockFacade {

    String SETTING_REQUEST_MOCK_PARAMETER = "setting_request_mock_parameter";
    String SETTING_FACADE_MOCK_BEAN = "setting_facade_mock_bean";

    String Setting();
}

通過這個(gè) annotation 我們的主要目的就是將 mockParameter 放到 ThreadLocal 中去和請(qǐng)求處理完時(shí)的清理工作。還有一個(gè)功能就是 service 層的 mock bean 處理。

@Aspect
@Component
@Slf4j
public class MockMarketingFacadeInterceptor {

    @Before("@annotation(mockFacade)")
    public void beforeMethod(JoinPoint joinPoint, MockFacade mockFacade) {

        String settingName = mockFacade.Setting();

        if (MockFacade.SETTING_REQUEST_MOCK_PARAMETER.equals(settingName)) {

            Object[] args = joinPoint.getArgs();
            if (args == null) return;

            List<Object> argList = Arrays.asList(args);
            argList.forEach(item -> {

                if (item instanceof BaseRequest) {
                    BaseRequest request = (BaseRequest) item;

                    if (request.getMockParameter() != null) {
                        MarketingBaseService.mockParameterThreadLocal.set(request.getMockParameter());
                        log.info("----setting mock parameter:{}", JSON.toJSONString(request.getMockParameter()));
                    }
                }
            });
        } else if (MockFacade.SETTING_FACADE_MOCK_BEAN.equals(settingName)) {

            MarketingBaseService marketingBaseService = (MarketingBaseService) joinPoint.getThis();
            marketingBaseService.mockBean();
            log.info("----setting mock bean.");
        }
    }

    @After("@annotation(mockFacade)")
    public void afterMethod(JoinPoint joinpoint, MockFacade mockFacade) {

        if (MockFacade.SETTING_FACADE_MOCK_BEAN.equals(mockFacade.Setting())) {

            MarketingBaseService marketingBaseService = (MarketingBaseService) joinpoint.getThis();
            marketingBaseService.mockRemove();

            log.info("----remove mock bean.");
        }

        if (MockFacade.SETTING_REQUEST_MOCK_PARAMETER.equals(mockFacade.Setting())) {

            MarketingBaseService.mockParameterThreadLocal.remove();

            log.info("----remove ThreadLocal. ThreadLocal get {}", MarketingBaseService.mockParameterThreadLocal.get());
        }
    }
}

這些邏輯完全基于一個(gè)約定,就是 MarketingBaseService,不具有通用型,只是在逐步的重構(gòu)和提取中,最終會(huì)是一個(gè) plugin 框架。

public abstract class MarketingBaseService extends BaseService {

    protected ClassMarketingCentralFacade classMarketingCentralFacade;

    protected CCMarketingCentralFacade ccMarketingCentralFacade;

    public static ThreadLocal<MockParameter> mockParameterThreadLocal = new ThreadLocal<>();

    public void mockBean() {

        MockParameter mockParameter = mockParameterThreadLocal.get();

        if (mockParameter != null && mockParameter.mockClassMarketingInterface) {
            if (mockParameter.useAutoTestingMock) {
                this.setClassMarketingCentralFacade(SpringContextHolder.getBean("ClassMarketingCentralFacadeTestMock", ClassMarketingCentralFacade.class));
            } else {
                this.setClassMarketingCentralFacade(SpringContextHolder.getBean("ClassMarketingCentralFacadeMocker", ClassMarketingCentralFacadeMocker.class));
            }
        } else {
            this.setClassMarketingCentralFacade(SpringContextHolder.getBean("ClassMarketingCentralFacade", ClassMarketingCentralFacade.class));
        }

        if (mockParameter != null && mockParameter.mockCCMarketingInterface) {
            if (mockParameter.useAutoTestingMock) {
                this.setCcMarketingCentralFacade(SpringContextHolder.getBean("CCMarketingCentralFacadeTestMock", CCMarketingCentralFacade.class));
            } else {
                this.setCcMarketingCentralFacade(SpringContextHolder.getBean("CCMarketingCentralFacadeMocker", CCMarketingCentralFacadeMocker.class));
            }
        } else {
            this.setCcMarketingCentralFacade(SpringContextHolder.getBean("CCMarketingCentralFacade", CCMarketingCentralFacade.class));
        }
    }

    public void mockRemove() {
        mockParameterThreadLocal.remove();
    }
}

我們可以順利的將 request 中的 mockParameter 放到 ThreadLocal 中,可以動(dòng)態(tài)的通過 AOP 的方式來注入相應(yīng)的 mockerBean

現(xiàn)在我們還要處理的就是對(duì) mockGateway 的調(diào)用將 _mockParameter 中的 autoContext 中的標(biāo)示字符串放到 HTTP Header 中去。

@Component
public class MockHttpHeadSetting implements ClientRequestFilter {

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {

        MultivaluedMap<String, Object> header = requestContext.getHeaders();

        MockParameter mockParameter = MarketingBaseService.mockParameterThreadLocal.get();

        if (mockParameter != null && StringUtils.isNotBlank(mockParameter.getTestingMockParam())) {
            header.add("Mock-parameter", mockParameter.getTestingMockParam());
        }
    }
}

接著在 SPI(javax.ws.rs.ext.Providers ) 文件中配置即可

com.hujiang.marketingcloud.ruleengine.service.MockHttpHeadSetting

總結(jié)

在整個(gè)微服務(wù)架構(gòu)的實(shí)踐中,工程界一直缺少探討的就是在微服務(wù)架構(gòu)的測(cè)試這塊,離我們比較近的是自動(dòng)化測(cè)試,因?yàn)樽詣?dòng)化測(cè)試基本上是所有系統(tǒng)都需要的。

但是有一塊我們一直沒有重視的就是 全鏈路壓力測(cè)試 這塊,在生產(chǎn)上進(jìn)行全鏈路的真實(shí)的壓力測(cè)試需要解決很多問題,比較重要的就是 DB 這塊,壓測(cè)的時(shí)候產(chǎn)生的所有交易數(shù)據(jù)不能夠參與結(jié)算、財(cái)務(wù)流程,這就需要借助 影子表 來解決,所有的數(shù)據(jù)都不會(huì)寫入最終的真實(shí)的交易數(shù)據(jù)中去。當(dāng)然還有其他地方都需要解決,一旦打開全鏈路壓測(cè)開關(guān),應(yīng)該需要處理所有產(chǎn)生數(shù)據(jù)的地方,這是一個(gè)龐大的工程,但是也會(huì)非常有意思。

本篇文章只是我們?cè)谶@塊的一個(gè)初步嘗試,我們會(huì)繼續(xù)擴(kuò)展下去,在下次產(chǎn)線全鏈路壓測(cè)的時(shí)候我們就可以借助現(xiàn)在的實(shí)踐架構(gòu)擴(kuò)展起來。

作者:王清培 (滬江集團(tuán)資深JAVA架構(gòu)師)
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI