乾貨 | 質量保障新手段,攜程迴歸測試平臺實踐

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"一、系統迴歸問題"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"迴歸測試是軟件生命週期一個十分重要的環節,但項目在隨着版本的逐步迭代,功能日益增多,系統愈加複雜,在測試過程中測試人員常常需要回歸穩定版本的功能以保證不被待發布版本需求所影響。若要對系統進行全方位迴歸,這個測試的工作量將會非常龐大,而且可能幾百上千用例中才會發現一個甚至是0個問題,測試投入產出不成比例。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而CPR則爲上述問題提供了較好的解決方案。它通過基於穩定版本的輸出,對待發布版本的輸出進行比較,同時還將校驗兩個版本對下游請求的差異。根據比對結果評判待發布版本是否正確,可以大大降低迴歸的工作量。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"二、CPR目標"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大量真實流量確保覆蓋率將錄製的流量作爲用例管理起來進行自動化迴歸流量回放支持子調用自動化mock,避免回放產生髒數據流量回放支持子調用結果的驗證減少人力資源"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"三、目標實現基本過程"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/79\/795501a96144bf7c21e83a949b88e42c.jpeg","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)首先將部署了穩定代碼的服務器作爲流量採集源。測試人員在進行功能、接口測試時,實現測試執行過程中主調用以及子調用的入參和返回值的錄製。通過功能和接口測試實現對應用功能的全量覆蓋,使得應用中請求流量都會被錄製到。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)將錄製到的請求流量複製到console平臺,由測試人員分析有效的流量歸納爲用例。後續即可採用這些有效用例來對待發布版本進行回放和差異比對。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)回放完成告知到測試人員,由測試人員對回放的差異結果進行確認,快速發現被測系統bug並解決。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"四、CPR原理及實現"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.1. CPR錄製的數據抓取點"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/f6\/f66dbd61049004f7f9ef5fc0ff9baba5.jpeg","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖所示,CPR採集的數據主要爲兩方面:待測應用接收到的客戶端請求及響應;待測應用接收請求後向外部服務的子調用請求及響應。CPR目前支持的請求類型如上圖橙色框中所示;但CPR本身設計時對各類請求採用插件式設計,使其具有較好的擴展性,對於後續其他需要捕獲的類型能很好的進行支持。當前CPR系統支持的報文協議爲JSON和PB(ProtoBuf)。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.2. CPR結構簡介"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ad\/ad97d4545da90078d10a0a09664d7417.jpeg","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CPR分爲兩大組件:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)CPR (CtripPaymentRepeater) 組件,該組件基於開源的jvm-sandbox開發,用於錄製和回放流量。此組件核心爲兩部分:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CPRRecord:目標是在穩定代碼環境中錄製請求調用的入參和返回值,並上送到存儲服務。使得CPR Replay具備回放流量的數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CPRReplay:功能爲接收回放服務提供的回放流量,在待測代碼環境中進行回放,並將回放結果上送至對比服務,讓其實現正常系統和待測系統返回結果比對差異的能力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)CPRConsole組件,該組件主要錄製\/回放的配置管理;數據存儲\/數據對比等具備多種能力。主要包含三部分:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"存儲服務:對接收到的錄製流量數據,將其持久化保存,待後續用戶篩選有效流量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回放服務:功能爲將錄製流量進行還原,然後對入口調用做一次流量的發起,使得CPR Replay。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對比服務:對接收到的回放數據與錄製數據進行差異比對。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.3 CPR處理流程"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/64\/643adfdc228216621242a24a80da8532.jpeg","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如上圖所示,通過這兩大組件的協同工作將穩定代碼的流量自動捕獲並持久化存儲,然後由測試人員分析流量收藏爲有效用例,對於後續待正式發佈的代碼,可以用有效用例來進行回放和差異比對,根據差異比對結果發現待測系統存在的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該系統在處理比對時有兩個與其他方案較大不同的特性:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)對子調用MOCK處理:本方案採用的是記錄下原始子調用內容及子調用返回內容;在系統內部完成對子調用返回的處理,不再需要實際的子系統或mock系統進行測試支撐。以此減小了環境、數據測試處理的難度和準確性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)子調用比對數據的處理:本方案採用的是比對子調用請求。例如一個DB操作,若請求內容是無誤的,則認定請求正常;不再去比對具體的數據庫存儲值。這樣的處理方式同樣簡化了測試環境搭建、測試數據準備,同時還解決了測試環境中數據共享變更可能帶來的數據差異問題。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.4 流量複製實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在整個錄製過程中,CPR進行錄製的處理流程主要是在DefaultEventListener類中實現。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)DefaultEventListener類"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DefaultEventListener是用來處理觸發事件的類。不同的插件 (soa,mysql,http,redis等) 可以自定義自己的EventListener來進行各自特殊的事件處理。目前插件默認使用DefaultEventListener。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在DefaultEventListener中,承接的是所有事件的處理,也就是說錄製\/回放操作都是集中在這個類中實現的,只是根據不同的條件來區分是錄製流量還是回放流量,從而判斷該執行錄製還是該執行回放。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當事件第一次過濾時,會進行一次初始化跟蹤器。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/45\/457f44427a88c30184582f39bcf3102e.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中traceId實際上是這個跟蹤器的獨立標識。所以無論是錄製和回放都會有其獨立的traceId。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)Before事件處理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對應的方法是DefaultEventListener.doBefore。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/12\/12a0c4ef478432005d5ea8a0a5c85a34.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據traceID判斷當前流量是否爲回放流量,若爲回放流量則調用processor.doMock方法執行Mock,執行完成後直接返回,不再執行後續操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"若當前流量爲錄製流量,則基於當前獲取到的信息初始化Invocation實例,其中會調用插件中processor#assembleIdentity、processor#assembleRequest、processor#assembleResponse、processor#assembleThrowable四個方法,分別處理調用標識、請求參數、返回結果、拋出異常。最後將當前事件的Invocation信息存放到錄製緩存中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)Return事件處理\/ Throw事件處理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Return\/ Throw事件的處理邏輯基本一致。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ee\/ee21548c425dcbd57e72221bb71f2f65.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"判斷當前流量是否爲回放流量,若爲回放流量則直接返回,不執行後續操作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"若錄製流量則從錄製緩存中獲取對應的Invocation實例,調用插件調用處理器的assembleResponse\/assembleThrowable方法,並將reponst\/throwable結果設置到Invocation實例中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回調調用監聽器InvocationListener#onInvocation方法,判斷Invocation是入口調用還是子調用,如果是子調用則保存到錄製緩存中,如果不是則調用消息投遞器的broadcastRecord方法將錄製記錄序列化後上傳給cpr-console。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.5 流量回放實現"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回放流程,是由測試人員選擇有效用例,在待測系統中執行回放。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回放服務接受到回放記錄,對記錄進行反序列化,還原到原來錄製的對象,保證classloader與錄製時候一致。CPR在執行回放任務的過程中,首先會根據錄製記錄的信息,構造相同的請求,對被掛載的任務進行請求,並跟蹤回放請求的處理流程,以便記錄回放結果以及執行mock動作。整個回放過程比較複雜,主要包括以下幾個個流程:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)回放請求的處理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏以http回放入口爲例,RepeaterModule#repeat方法是回放請求最初處理入口。添加@Command註解,使得外部可以使用http協議方式調用入口。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/de\/de49b2b114bb958a4c152c0ea115174c.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該方法在接收外部的回放請求後,對請求參數進行校驗,校驗通過後會將參數實例化,形成一個回放事件RepeatEvent,發佈到EventBusInner中被RepeatSubscribeSupporter#onSubscribe方法進行處理。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/75\/757cf9fb40b3cb7eb979692b2078693b.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"處理過程爲首先會將RepeatEvent的參數進行反序列化,獲取回放相關的錄製記錄的信息,然後通過這些信息從prepeater-console拉取對應的錄製記錄詳情(RecordModel),最後用默認流量分發器DefaultFlowDispatcher進行分發。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)回放任務的分發"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回放任務的分發由DefaultFlowDispatcher#dispatch方法實現。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/a6\/a6e2f65b3ffc338fe50a11b70b1f6568.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"主要是針對回放任務的信息進行校驗,校驗失敗會拋出錯誤到上層。通過校驗的回放任務,則會初始化回放上下文信息,並存放到回放緩存中。根據回放任務的入口插件類型,獲取對應插件的repeater進行回放處理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)回放結果執行與記錄"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"回放任務執行、回放結果記錄的實現邏輯在AbstractRepeater#repeat中實現。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/66\/6624b999f20958b4e2b6e3c72b688e35.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過executeRepeat方法交由各個插件的回放器實例來調用。當插件開始執行回放任務時,會根據回放上下文以及當前repeater的應用信息初始化回放結果記錄RepeaterModel的實例。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"初始化回放結果記錄後,初始化回放線程跟蹤並觸發回放請求並獲取回放請求返回的結果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獲取回放請求返回的結果後,停止線程跟蹤,將回放結果以及當前的一些狀態信息保存到回放結果記錄的實例中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後通過調用消息投遞器的AbstractBroadcaster#broadcastRepeat方法將回放結果記錄序列化後上傳到crp-console保存。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4)回放過程中的Mock處理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在回放過程中,直接在DefaultEventListner#Before事件處理中執行。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/12\/12a0c4ef478432005d5ea8a0a5c85a34.png","alt":"圖片","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"processor#doMock方法,交給插件調用處理器執行mock,並且只對子調用事件執行mock。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\npublic void doMock(BeforeEvent event, Boolean entrance, InvokeType type) throws ProcessControlException {\n \/*\n * 獲取回放上下文\n *\/\n RepeatContext context = RepeatCache.getRepeatContext(Tracer.getTraceId());\n \/*\n * mock執行條件\n *\/\n if (!skipMock(event, entrance, context) && context != null && context.getMeta().isMock()) {\n try {\n \/*\n * 構建mock請求\n *\/\n final MockRequest request = MockRequest.builder()\n .argumentArray(this.assembleRequest(event))\n .event(event)\n .identity(this.assembleIdentity(event))\n .meta(context.getMeta())\n .recordModel(context.getRecordModel())\n .traceId(context.getTraceId())\n .repeatId(context.getMeta().getRepeatId())\n .index(SequenceGenerator.generate(context.getTraceId()))\n .type(type)\n .build();\n \/*\n * 執行mock動作\n *\/\n\n final MockResponse mr = StrategyProvider.instance().provide(context.getMeta().getStrategyType()).execute(request);\n \/*\n * 處理策略推薦結果\n *\/\n switch (mr.action) {\n case SKIP_IMMEDIATELY:\n break;\n case THROWS_IMMEDIATELY:\n ProcessControlException.throwThrowsImmediately(mr.throwable);\n break;\n case RETURN_IMMEDIATELY:\n \/\/ if(!type.equals(InvokeType.MYSQL) &&! type.equals(InvokeType.MYBATIS)){\n ProcessControlException.throwReturnImmediately(assembleMockResponse(event, mr.invocation));\n \/\/ }\n break;\n default:\n ProcessControlException.throwThrowsImmediately(new RepeatException(\"invalid action\"));\n break;\n }\n\n } catch (ProcessControlException pce) {\n throw pce;\n } catch (Throwable throwable) {\n ProcessControlException.throwThrowsImmediately(new RepeatException(\"unexpected code snippet here.\", throwable));\n }\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AbstractInvocationProcessor#doMock方法的實現,與AbstractRepeater#repeat相似,實際調用時是通過插件中各自的Processor的實例進行調用的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當開始執行回放mock時,會先獲取回放上下文的信息。根據回放上下文的信息,判斷是否跳過mock的執行。當需要執行mock時,會根據回放上下文中的信息初始化MockRequest實例,通過mock策略計算獲取MockResponse。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據MockResponse返回狀態進行不同的操作:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"當MockResponse執行結果是返回正常內容時,就會拋出一個終止當前調用操作並返回當前的返回結果,用ProcessControlException異常來阻擋需要mock的方法的實際調用。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"當MockResponse執行結果是返回一個異常時,就會拋出一個終止當前調用操作並拋出ProcessControlException異常,此時需要mock的方法不會被真實調用。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"只有當MockResponse返回狀態是skip時,則不對這個調用事件做任何處理,調用會真實發生,其他任何狀態都會通過拋出ProcessControlException異常來阻擋這個事件的實際調用。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.6. 缺陷判定簡析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1)對於讀操作,我們主要關注在相同請求下正常系統和待測系統的返回結果的差異,讀接口也提倡對所有對外請求進行mock,這樣回放時能保持當時的一個現場環境,保證驗證的準確性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2)對於寫操作,只驗證接口返回結果是不夠的,需要驗證它具體寫入的數據是否正確。例如創建支付訂單會調用PPI的寫訂單PPI.Bill.CreatePaymentBill接口,那麼我們需要驗證回放時調用PPI的參數和錄製時調用PPI的參數是否一致。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3)對比默認是全對象字段逐一對比。由於錄製環境和回放環境所處環境不同,有一些必然不一致的信息,例如隨機數、時間,以及系統ip等等,這些內容系統做了默認不比對處理。系統對於子調用是默認對比所有子調用,也可以通過平臺配置排除一些不關心的子調用的對比。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4.7 數據安全保護"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"系統錄製與回放過程中基於對通訊、數據的安全保護要求,系統實現了對公司神盾安全系統的接入,使得傳輸過程、落地數據及日誌等涉及安全要求的數據內容都符合了信息安全要求。也確保了使用者不會接觸到敏感數據等限制性內容。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"五、小結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文系統的介紹了CtripPaymentRepeater的目標以及實現原理以及流量複製、流量回放的技術方案,希望給因頻繁進行迴歸測試而困擾的朋友一些幫助和借鑑。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在系統成熟的前提下,可以擴展本平臺進行生產數據的採集比對。當這一步實現後,可以想見迴歸將真正不再是一件難事,尤其是生產迴歸需要面對的數據污染等問題都將不再是問題。同時該平臺還可演化爲MOCK平臺,這樣的MOCK將不再依賴任何系統,只需在待測應用的服務器上增加一個Agent即可完成。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"作者簡介:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Sedro,攜程資深測試工程師,專注於測試技術探索及測試工具研發。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文轉載自:攜程技術(ID:ctriptech)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/STHmQjDToqEq4nqogaxcyg","title":"xxx","type":null},"content":[{"type":"text","text":"乾貨 | 質量保障新手段,攜程迴歸測試平臺實踐"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章