你們都知道,不論是 Web 系統、仍是移動 APP,各自在與內部、外部系統之間進行數據交互時,大多數狀況下都是依賴接口。在基於接口約定開發的模式下,依賴接口的產出時間若是延遲,將直接影響了整個研發調試的效率;若是不能對接口進行及早測試,那發現問題的時間就要被推遲了。既然雙方約定了接口格式,爲什麼不按照這個規範直接測試,何須在意依賴接口何時產出,優先作到及早自測,後續只要替換接口聯調經過便可。下面主要講解基於 HTTP 協議的 API 接口模擬,從手工 Mock 到平臺的演變過程。html
曾經遇到的困擾:在研發過程當中接口調試對接難的問題:前端
場景一:java
【需求階段】Portal 前、後端約定基於接口開發linux
【開發階段】前端開發完畢,後端接口還沒有開發完畢,前端只能硬編碼數據進行測試,形成接口對接調試延後,並且每次進行更多場景的數據調試,須要頻繁重啓服務、本地部署;ios
研發自測階段沒法及早開展,依賴接口約束大。git
場景二:github
【需求階段】新功能開發,Portal 依賴計費的接口,雙方約定基於接口開發(內部、外部依賴接口場景均通用)web
【開發階段】Portal 在開發進行中,計費還沒有開發完畢,Portal 遲遲不能與計費對接調試(也有可能版本迭代步伐不一致的狀況),測試階段一直被推遲;面試
另外,即便計費接口開發完畢,Portal 須要修改計費約定的接口數據進行調試,當發現沒有對方接口權限或者計費沒有過多人力資源來配合時,也沒法進入更豐富的數據細節調試;正則表達式
【測試階段】測試人員沒法及早介入到調試階段進行接口測試,形成發現缺陷的最佳時期被推遲;
場景三:
【需求階段】移動 APP 項目依賴後端獲取帶寬數據的接口
【開發階段】移動 APP 端經過後端系統 API 獲取帶寬數據,繪製帶寬圖,APP 端繪圖工具開發完畢,後端 API 帶寬接口還沒有開發完畢,移動 APP 端只能硬編碼數據進行測試,形成對接延後,每次進行更豐富的數據調試,須要頻繁重啓服務、本地部署;
研發自測階段沒法及早開展,依賴接口約束大。
總而言之,如圖所示:
依賴接口開發完畢,纔可以進入到接口聯調測試階段,即便 Portal 的功能開發已經完成,也沒法進行自測聯調,消耗的等待時間代價是不可估量的,效率低,。
圖 -1- 傳統的接口對接調試流程
要解決在研發過程當中接口對接調試難的問題,無非是所需即全部,減小等待時間,增長研發自測環節,同時也讓測試及早參與進來,所以須要可以把依賴接口模擬出來(白盒方面的 Mock 有許多解決方案,這裏主要講的是基於 HTTP 請求的 API Server Mock),以便提升生產效率,改進流程如圖所示:
圖 -2- 改進的接口對接調試流程
當前最簡單的想法是要解決:基於 HTTP 請求、固定 url、可以正則匹配,在這個需求的驅動下,經過 Nginx 的反向代理可以解決問題。
匹配具體路徑下某 html 文件
location ~ ^/live/(.*)\.html$ { root /home/htmlfile/ms; } location ~ ^/live/([A-Z0-9]+)$ { }
定義具體返回碼
location ~ ^/schedule/.*\.(json)$ { error_page 404 /404.html; }
定義其它狀態碼也是一樣道理:
error_page 403 /error/403.html; error_page 500 501 502 503 504 /error/500.html;</pre>
俗話說:術業有專攻,Nginx 並不擅長作 Mock API 的工具,在管理配置文件即便能夠經過 svn 進行管理,依然是維護比較困難,對於不熟悉 Nginx 的測試工程師,也有必定的學習成本。
拿來主義:不重複造輪子 - 開源 WireMock
經歷了 Nginx 的配置繁瑣,決定另尋新路,有開源的 WireMock(http://wiremock.org/):
Ø WireMock 是一個靈活的庫,用於 Web 服務測試,和其餘測試工具不一樣的是:WireMock 建立一個實際的 HTTP 服務器來運行你的 Web 服務以方便測試;
Ø 支持 HTTP 響應存根、請求驗證、代理 / 攔截、記錄和回放;
建立一個基於 WireMock 的 JavaProject(運行在 tomcat 下管理):
圖 -3-ServerMock Project
web.xml 配置以下:
<?xml version="1.0" encoding="UTF-8"?> <web-app id="WebApp_9" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <listener> <display-name>wiremock-startup-listener</display-name> <listener-class>com.github.tomakehurst.wiremock.servlet. WireMockWebContextListener</listener-class> <description>Loads WireMock and populates the servlet context with its services</description> </listener> <context-param> <param-name>WireMockFileSourceRoot</param-name> <param-value>/WEB-INF/wiremock</param-value> </context-param> //若是對軟件測試、接口測試、自動化測試、性能測試、LR腳本開發、面試經驗交流。 <context-param> //感興趣能夠175317069,羣內會有不按期的發放免費的資料連接,這些資料 <param-name>verboseLoggingEnabled</param-name> //都是從各個技術網站蒐集、整理出來的 <param-value>false</param-value> //若是你有好的學習資料能夠私聊發我,我會註明出處以後 </context-param> //分享給你們。 <servlet> <servlet-name>wiremock-mock-service-handler-servlet</servlet-name> <servlet-class>com.github.tomakehurst.wiremock.jetty6. Jetty6HandlerDispatchingServlet</servlet-class> <init-param> <param-name>RequestHandlerClass</param-name> <param-value>com.github.tomakehurst.wiremock.http. StubRequestHandler</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>wiremock-mock-service-handler-servlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> <servlet> <servlet-name>wiremock-admin-handler-servlet</servlet-name> <servlet-class>com.github.tomakehurst.wiremock.jetty6\. Jetty6HandlerDispatchingServlet</servlet-class> <init-param> <param-name>RequestHandlerClass</param-name> <param-value>com.github.tomakehurst.wiremock.http. AdminRequestHandler</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>wiremock-admin-handler-servlet</servlet-name> <url-pattern>/__admin/*</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.json</welcome-file> <welcome-file>index.xml</welcome-file> <welcome-file>index.html</welcome-file> <welcome-file>index.txt</welcome-file> </welcome-file-list> <mime-mapping> <extension>json</extension> <mime-type>application/json</mime-type> </mime-mapping> <mime-mapping> <extension>xml</extension> <mime-type>application/xml</mime-type> </mime-mapping> <mime-mapping> <extension>html</extension> <mime-type>text/html</mime-type> </mime-mapping> <mime-mapping> <extension>txt</extension> <mime-type>text/plain</mime-type> </mime-mapping> </web-app>
web.xml 的這項配置能夠改變源文件位置
<context-param> <param-name>WireMockFileSourceRoot</param-name> <param-value>/WEB-INF/wiremock</param-value> </context-param>
使用 Maven 管理依賴,配置以下:
<dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock</artifactId> <version>1.53</version> <!-- Include everything below here if you have dependency conflicts --> <classifier>standalone</classifier> <exclusions> <exclusion> <groupId>org.mortbay.jetty</groupId> <artifactId>jetty</artifactId> </exclusion> <exclusion> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> </exclusion> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> </exclusion> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> </exclusion> <exclusion> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </exclusion> <exclusion> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </exclusion> <exclusion> <groupId>org.skyscreamer</groupId> <artifactId>jsonassert</artifactId> </exclusion> <exclusion> <groupId>xmlunit</groupId> <artifactId>xmlunit</artifactId> </exclusion> <exclusion> <groupId>com.jayway.jsonpath</groupId> <artifactId>json-path</artifactId> </exclusion> <exclusion> <groupId>net.sf.jopt-simple</groupId> <artifactId>jopt-simple</artifactId> </exclusion> </exclusions></dependency>
具體的部署這裏就不介紹了,說說 WireMock 的配置:
Ø WireMock 的文件目錄
如圖所示:
mappings: 存放映射描述的文件
__files: 存放映射匹配結果的文件
圖 -4-WireMock 的文件目錄
WireMock 的匹配規則示例
分兩種:完整 Url 匹配和正則 UrlPattern
Url:徹底匹配
mappings:cities-mapping.json
{ "request": { "method": "GET", "url": "/cities" }, "response": { "status": 200, "bodyFileName": "/cities.json", "headers": { "Content-Type": "application/json", "Cache-Control": "max-age=86400" } } }
__files:cities.json
{ "cityName": "公司操做間", "shortname": "WS", "provinceName": "北京", "provinceNameEn": "BeiJing City", "code": "0001", "cityNameEn": "Workshop" }
UrlPattern:正則匹配任何 6 位數的,例如:/customer/123456/
mappings:cities-mapping.json
{ "request": { "method": "GET", "urlPattern": "/customer/[0-9]{6}/" }, "response": { "status": 200, "bodyFileName": "/customer.json", "headers": { "Content-Type": "application/json", "Cache-Control": "max-age=86400" } } }
__files:customer.json
{ "channels": [], "code": "781", "companyName": "", "enable": true, "name": "163", "password": "CC@ne.com", "userState": "COMMERCIAL" }
使用 WireMock 經過 mappings 和 __files 文件夾能夠有效管理映射和返回內容文件,可是全部文件的有部分可抽取未固定模板,而這些部分目前是手動編輯,關注這些部分會分散業務的精力,若是能夠作成平臺化管理,全部接口經過建立完成,文件命名規則所有由系統進行管理,將節省的時間更多投入業務關注和及早進行自測,這樣子的收益將會更大。
那怎麼樣的平臺纔算可以知足當前需求呢?
圖 -4-ServerMock-v1.0- 架構圖
根據架構圖,作了整體規劃以下:
圖 -5-ServerMock-v1.0 規劃
若是對軟件測試、接口測試、自動化測試、性能測試、LR腳本開發、面試經驗交流。感興趣能夠175317069,羣內會有不按期的發放免費的資料連接,這些資料都是從各個技術網站蒐集、整理出來的,若是你有好的學習資料能夠私聊發我,我會註明出處以後分享給你們。
技術選型
因爲原來的測試平臺使用 Python 編寫,爲了保持風格一致,從界面錄入到文件生成處理依然採用 Python,後臺工具使用 WireMock 的 standalone 模式,經過 shell 腳本進行一鍵啓停管理,以及實時刷新 url、mapping 映射;
HTTP API Mock 項目管理 Web 前臺
使用 Python+Django+MySQL 進行開發,分爲項目配置和接口配置兩大部分。
項目配置頁
介紹:配置協議、進行 mock 服務器的重啓、從新加載(有新的接口文件生成系統會自動 reset 便可,固然手工 reset 也能夠,即時加載無須重啓服務等待)。
圖 -6- 項目配置頁
接口列表頁
介紹:展現列表,列出相關 URL、方法、是否正則、返回碼、返回類型。
圖 -7- 接口列表頁
接口配置頁
介紹:選擇方法、URL 類型,填寫 URL(若是選擇 URL 類型爲 UrlPattern,則填寫正則表達式),填寫狀態碼、返回接口,以及返回頭,就能夠完成一個 mock 接口的建立。
圖 -8- 接口配置頁
接口配置有三種輸入形式:
直接輸入返回結果
圖 -9- 手工輸入
通常場景在返回結果 500k 之內的內容,能夠直接輸入,保存進入數據庫;
經過 url 抓取返回結果
圖 -10-url 抓取
通常場景在返回結果超過 500k 以上內容,目標 Mock 接口已經存在,能夠直接抓取生成文件;
經過文件上傳返回結果
圖 -11- 上傳文件
通常場景在返回結果比較大|目標 Mock 接口還未開發完成,手工上傳返回內容的文件便可。
以上三種靈活的保存返回內容方式,最終保存的接口會按照如下格式生成 mapping 和 __files 所需文件:
圖 -12-mapping 和 __files 文件格式
Mock 項目管理 Server 後臺
使用 Java-WireMock 進行後臺服務,在項目配置頁經過按鈕:重啓、從新加載,調用後臺腳本:wiremock_controller.sh,僅供參考:
#!/bin/bash if [ "$#" = 0 ];then echo "Usage: $0 (start|stop|restart|reset)" exit 1 fi dirWiremock=`pwd` getCount=`ps -ef | grep "wiremock-1.53-standalone" | grep -v "grep" |wc -l` wiremock_jar=${dirWiremock}/wiremock-1.53-standalone.jar port=9999 wiremock_url=http://localhost:${port} stop(){ count=${getCount} if [ 1==${count} ];then curl -d log=aaa ${wiremock_url}/__admin/shutdown echo "Stop success!......" else echo "Already stop" fi } start(){ count=${getCount} if [ 0==${count} ];then nohup java -jar ${wiremock_jar} --verbose=true --port=${port} & echo "Start success!......" else echo "Already start" fi } if [ "$1" = "restart" ];then count=${getCount} if [ 1==${count} ];then echo "Wiremock is running,wait for restarting! ...." stop echo "Start wiremock......" start else start fi elif [ "$1" = "start" ];then echo "Start wiremock......" start elif [ "$1" = "stop" ];then echo "Stop wiremock......" stop elif [ "$1" = "reset" ];then count=${getCount} if [ 0==${count} ];then echo "Wiremock must be running before reset,wait for starting! ...." start fi curl -d log=aaa ${wiremock_url}/__admin/mappings/reset echo "Reset success!......" fi
其中:
「nohup java -jar {port} &」:在 linux 系統後臺運行 WireMock;
「curl -d log=aaa ${wiremock_url}/__admin/mappings/reset」:是經過發送 POST 請求,從新加載新生成的配置文件,在 WireMock 的源碼中能夠看到:reset 的做用:
public interface Admin { void addStubMapping(StubMapping stubMapping); ListStubMappingsResult listAllStubMappings(); void saveMappings(); void resetMappings(); void resetScenarios(); void resetToDefaultMappings(); VerificationResult countRequestsMatching(RequestPattern requestPattern); FindRequestsResult findRequestsMatching(RequestPattern requestPattern); void updateGlobalSettings(GlobalSettings settings); void addSocketAcceptDelay(RequestDelaySpec spec); void shutdownServer(); }
經過一系列源碼追溯,能夠找到重置:
@Override public void reset() { mappings.clear(); scenarioMap.clear(); }
能夠推測映射文件是存放到列表的:
public class SortedConcurrentMappingSet implements Iterable<StubMapping>{ private AtomicLong insertionCount; private ConcurrentSkipListSet<StubMapping> mappingSet; ...... }
當 WireMock 啓動,日誌有如下描述:
2015-02-12 11:38:37.844 Verbose logging enabled 2015-02-12 11:38:38.657:INFO::Logging to STDERR via wiremock.org.mortbay.log.StdErrLog 2015-02-12 11:38:38.664 Verbose logging enabled /$ /$ /$ /$ /$ /$ | $ /$ | $|__/ | $$ /$$ | $ | $ /$$| $ /$ /$$$ /$$$ | $$ /$$ /$$$ /$$$$| $ /$ | $/$ $ $| $ /$__ $ /$__ $| $ $/$ $ /$__ $ /$_____/| $ /$/ | $$_ $$| $| $ \__/| $$$$| $ $$| $| $ \ $| $ | $$$/ | $$/ \ $$| $| $ | $_____/| $\ $ | $| $ | $| $ | $_ $ | $/ \ $| $| $ | $$$$| $ \/ | $| $$$/| $$$$| $ \ $ |__/ \__/|__/|__/ \_______/|__/ |__/ \______/ \_______/|__/ \__/ port: 9999 enable-browser-proxying: false no-request-journal: false verbose: true
若是對軟件測試、接口測試、自動化測試、性能測試、LR腳本開發、面試經驗交流。感興趣能夠175317069,羣內會有不按期的發放免費的資料連接,這些資料都是從各個技術網站蒐集、整理出來的,若是你有好的學習資料能夠私聊發我,我會註明出處以後分享給你們。
圖 -13-WireMock 啓動
成功處理請求的日誌:
2015-02-12 11:41:10.320 Received request: GET /test/today/dkfDF123/1234/ HTTP/1.1 Host: 192.168.32.55:9999 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:33.0) Gecko/20100101 Firefox/33.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate Cookie: csrftoken=alXbvCtMyTBI1wnSnRoljguTaBnTDbPo; sessionid=tvoi9rzs66umnt1a26wsj36eqry2e2lo Connection: keep-alive
HTTP API 接口測試痛點是什麼?不少公司劃分不一樣研發組,各組系統之間的數據交互經過接口來實現,那不少時候就是集中在接口開發不一樣步,測試沒法及早參與,對接調試難的問題。或許不少團隊遇到這種問題,就是選擇同步開發或者等待。當你選擇等待的時候,你的產品質量就得不到及時驗證,由於根本沒有測試過,在當前快速迭代的開發模式中,時間是最致命的要素,若是不能及時交付,交付的質量又得不到保證,那是至關被動的局面,最後返工的成本比你當時願意追加測試的成本會來的更高。
遇到這類問題是想辦法解決,而不是迴避,咱們可使用 Mockito 對依賴進行 Mock,那一樣道理,使用 Mock 技術也能夠對 HTTP API 進行 Mock,按照這個思路探索下去,看看有沒有開源解決方案,是否可以解決當前問題,若是能夠就不用重複寫一套解決方案;若是不行,那可否基於開源的作二次開發呢?當團隊經歷過測試痛點,調研收集了必定的數據,這些問題的答案就會浮出水面了。
或許有人要問,使用以後可以提升多少效率呢?看回《圖 -2- 改進的接口對接調試流程》,根據咱們的經驗,要統計當前迭代中有多少 API 須要對接調試,若是對比舊的模式來講,API 接口調試效率提高至少有 10%;可想而知,迭代中全是依賴 API 接口開發的話,那提高的效率就至關難得了。