標籤:
淺談WireMock結合Mock+Proxy應用於異常測試
為什麼需要WireMock
最近在做NCE自動化介面測試,按照儘可能覆蓋邏輯的原則,寫了200+用例,但實際去實現的時候,大概能做的就100不到,完成的大多是能通過傳入參數去控制系統的邏輯走向和結果的case,即傳入指定的參數,調用發送請求工具,對結果進行校正。那麼除此之外,哪些是難以實現的呢?
從被測系統本身來說,NCE是建立在底層服務之上的一套系統,他的主要職能還是資源分派和調度,比如調雲主機的介面拿兩台機器,又去申請幾塊硬碟掛上,然後還要在機器上部署k8s的組件,調k8s建立rc和pod等,考慮到調用以非同步為主,還要加上輪詢,所以大量的調用外部的介面,以及結果判斷的邏輯。而調用外部服務,想要獲得預期的結果,並不是那麼容易,需要額外瞭解什麼情況下才會出現異常,有些情況難以類比又不能跑去把公用的服務搞壞。這不僅僅是自動化容易遇到的問題,手工測試也同樣會遇到。
這種上層服務測試所遇到的問題一般是怎麼解決的呢。就之前的經驗來說,需要藉助mock來實現,比如之前在支付,後台依賴的是財付通的服務,很多異常需要由財付通後台返回,另外由於測試環境是對接的,財付通做異常測試或是環境出現問題的時候,支付的正常的測試活動就不能自理了,所以需要mock來降低對外部環境的依賴。僅有mock還是不夠,如果一個人mock了一個服務,那麼所有人都走到了被mock的服務上,如果被mock的返回是異常,那麼所有人的測試活動又沒有辦法進行,如果被mock返回的是成功,對別人又是一種誤導。所以最後採用的是一種mock+選擇的模式,一般以帳號+介面名為維度來控制請求訪問指定的目的地,
如果要對NCE系統做異常測試,mock是比較容易找的,由於協議都是http協議而不是什麼自封裝的協議,所以選擇比較多,其中WireMock是一種較好的選擇,之前有同學已經推薦過了WireMock——輕量級HTTP Mock伺服器),自己嘗試過一下,也夠簡單易用。分發方面,可以考慮python的HTTPServer做一個轉寄伺服器,收到請求後,解析請求串轉寄給想要的url,然後把請求返回回去
但還要加上規則匹配什麼的就覺得心好累,後來發現WireMock自己就支援即時設定這種更為簡單的方式,就轉為全都依賴WireMock
首先要做的——外部存取的統一管理
目前的服務訪問外部的url都是在設定檔中,如果每次都要改url配置指向mock,用mock測完再改回來無疑是一種低效的做法,結合WireMock的proxy功能希望能對所有可能的外部存取進行管理
使用WireMock proxy前的Container Service(舉例)
使用WireMock proxy後,我們所期望的
按如下步驟讓url都通過WireMock的proxy
1.下載並啟動WireMock-xx-standalone.jar
下載的WireMock的standalone,放到某台主機上
基本的啟動方式:
# java -jar wiremock-1.55-standalone.jar --verbose
java -jar wiremock-1.55-standalone.jar –help 有一些可以使用的命令,加–verbose開啟verbose資訊輸出到螢幕方便調試 ,當伺服器使用可以按如下命令掛在後台
# nuhup java -jar wiremock-1.55-standalone.jar &
歡迎頁面就是這個鳥樣:
然後將修改配置需要代理的連結切到WireMock,由看出WireMock預設連接埠8080,當然可以自己用–port指定。比如需要將kube的url從10.180.155.13:8080變為10.180.148.30:8080(WireMock連接埠)
啟動過WireMock後目錄下面有2個檔案夾:mapping和__file。mapping裡面是收發的規則配置,mapping裡面指定了返回的檔案名稱的話,就會從__file裡面把檔案給取出來,若規則匹配,則會返回對應的內容(預定的字串或檔案內容或網路錯誤),不匹配則返回404not found。
這裡要使用proxy模式,請求通過wiremock進行代理,在mapping裡面配置proxy規則,proxy的配置和mock配置類似,簡單配置如下
建立container-mapping.json檔案,加入一下配置
{ "request": { "method": "GET", "urlPattern": "/api/v1beta2/.*" }, "response": { "proxyBaseUrl" : "http://10.180.155.13:8080/" }}
於是/api/v1beta2的http請求就從container->wiremock->kube,體驗上卻和直接container->kube一致
WireMock日誌
2015-05-30 12:09:48.36 Request received:GET /api/v1beta2/pods?labels=podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o HTTP/1.1User-Agent: curl/7.26.0Host: 10.180.148.30:8080Accept: */*2015-05-30 12:09:48.76 Proxying: GET http://10.180.155.13:8080//api/v1beta2/pods?labels=podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o
其他http連結也可以用這種方式配置好
採用WireMock做自動化異常測試
現在已經讓外部連結納入WireMock的控制了,如果想做網路異常或指定返回,可以添加另一個規則,將這個url對應的請求都返回指定值,但必然只有一個生效,比如
{ "request": { "method": "GET", "urlPattern": "/api/v1beta2/.*" }, "response": { "proxyBaseUrl" : "http://10.180.155.13:8080/" }}{ "request": { "method": "GET", "urlPattern": "/api/v1beta2/.*" }, "response": { "status": 200, "body": "mocked by hzmali!\n" }}
實際情況下,誰在前面就優先使用那個規則,是否要每次做完異常測試需要再改回配置到正常方式呢,如果是手工測試還可以接受,如果是自動化測試,那麼就會很麻煩.幸運的是,WireMock 提供了通過__admin/mappings/new介面來遠程配置規則的功能。
嘗試在mapping下建立了一個test.json
{ "request": { "url": "/get/this", "method": "GET" }, "response": { "status": 200, "body": "on disk!\n" }}
重啟stansdalone,發現規則生效
curl "http://10.180.148.30:8080/get/this"on disk!
調用__admin/mappings/new配置/get/this返回NO
# curl -X POST --data ‘{ "request": { "url": "/get/this", "method": "GET" }, "response": { "status": 200, "body": "NO!\n" }}‘ http://10.180.148.30:8080/__admin/mappings/new# curl "http://10.180.148.30:8080/get/this"NO!
再配置/get/this返回YES
# curl -X POST --data ‘{ "request": { "url": "/get/this", "method": "GET" }, "response": { "status": 200, "body": "YES!\n" }}‘ http://10.180.148.30:8080/__admin/mappings/new# curl "http://10.180.148.30:8080/get/this"YES!
重啟standalone,之前配置的結果不存在,恢複為設定檔的結果:
#curl "http://10.180.148.30:8080/get/this"on disk!
各位看官大概可以猜想到,stanalone啟動時載入了本地的mapping配置到記憶體,/__admin/mappings/new這個介面將發送過去的規則置為最新規則,根據官方文檔的說法,最新的規則會生效:
By default, WireMock will use the most recently added matching stub to satisfy the request. However, in some cases it is useful to exert more control.(exert more control 指的是使用”priority”: 1,這樣的欄位來指定優先順序來破壞最近匹配的規則,目前沒有怎麼使用)
WireMock standalone能通過介面設定規則,以及最新生效這2個特性,對自動化用例絕對是一個喜大普奔的訊息,在類比外部資源返回特定訊息或異常時,可以採取這樣通用的方式:
1.正常case:外部存取都通過WireMock的proxy,映射關係都寫在本地配置,WireMock啟動就自動載入,不影響正常功能,上層無感知
2.需要mock的case:執行前將想要的返回以及匹配規則發送到/__admin/mappings/new介面使之生效,執行完成後重設規則使用proxy方式
簡單的tesng case寫法,通過設定規則控制返回是proxy的結果還是自己所期望的mock返回
/*WebUserParameterTestData.class的DataProvider*/ @DataProvider(name = "sample") public static Object[][] sample(){ String jsonString="{\"request\": {\"method\": \"GET\",\"urlPattern\": \"/api/v1beta2/pods.*\"},\"response\": {\"status\": 200,\"body\": \"mocked by hzmali\"}}"; return new Object[][]{ {jsonString, "and"}, }; }/*case主要部分*/ @BeforeClass @Parameters({ "env" }) public void init(String env) throws IOException, InterruptedException { CommonData.init(env); nce_conn = new NceHttpBiz(CommonData.WEBHOST); mock_conn=new MockTool(); //列印當前結果 log.info("[befor test]response: "+nce_conn.getPods()); //設定規則放到test裡做 } @AfterMethod public void recoverMapping() throws IOException{ //恢複規則 String rule="{\"request\": {\"method\": \"GET\",\"urlPattern\": \"/api/v1beta2/pods.*\"},\"response\": { \"proxyBaseUrl\" : \"http://10.180.155.13:8080/\"}}";; mock_conn.setMapping(rule); //列印恢複後的結果 log.info("[after test]response: "+nce_conn.getPods()); } @Test(dataProvider = "sample", dataProviderClass =WebUserParameterTestData.class) public void sample(String rule,String expeted) throws IOException { System.out.println(rule); //設定期望的返回 mock_conn.setMapping(rule); //列印預期的返回 log.info("[testing]response: "+nce_conn.getPods()); }
列印的結果表示符合預期
[INFO ]17:32:34, [Class]NceHttpBiz, [Method]getPods, ==========call getPods=============[INFO ]17:32:34, [Class]MockCase, [Method]init, [befor test]response: { "kind": "Status", "creationTimestamp": null, "apiVersion": "v1beta2", "status": "Failure", "message": "invalid selector: ‘podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o‘; can‘t understand ‘podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o‘", "code": 500}[INFO ]17:32:34, [Class]MockTool, [Method]setMapping, ==========setMapping============={"request": {"method": "GET","urlPattern": "/api/v1beta2/pods.*"},"response": {"status": 200,"body": "mocked by hzmali"}}[INFO ]17:32:34, [Class]NceHttpBiz, [Method]getPods, ==========call getPods=============[INFO ]17:32:34, [Class]MockCase, [Method]sample, [testing]response: mocked by hzmali[INFO ]17:32:34, [Class]MockTool, [Method]setMapping, ==========setMapping=============[INFO ]17:32:34, [Class]NceHttpBiz, [Method]getPods, ==========call getThis=============[INFO ]17:32:34, [Class]MockCase, [Method]recoverMapping, [after test]response: { "kind": "Status", "creationTimestamp": null, "apiVersion": "v1beta2", "status": "Failure", "message": "invalid selector: ‘podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o‘; can‘t understand ‘podlabel%3Dapitest-d5e3e7cf9bee471f8ff642d1258e1354-go00o‘", "code": 500}
實際使用中,比如測試部署服務時,pod狀態不正確的情境,需要把查詢pod這個介面mock掉,kube其他介面都走proxy方式,實際用例會複雜一些
後記
WireMock的使用上也有一些不方便的地方:由於是proxy,所以如果有同名介面,需要使用額外的正則方式或是新的WireMock進程;目前期望的返回都是由用例來控制,可控性強,對於一些大粒度的異常情境類比較好,如泛失敗,網路錯誤等,但精確類比各種情境下正確的返回則需要花一點功夫,構造符合資料相關性的返回也是一個挑戰。目前只是初步使用,還有很多坑沒踩到,樂觀的來看這種方式能夠解決一些問題,而且代價不高,可以拿來先頂頂。
淺談WireMock結合Mock+Proxy應用於異常測試