接口自動化測試的最佳工程實踐(ApiTestEngine)

背景

當前市面上存在的接口測試工具已經很是多,常見的如PostmanJMeterRobotFramework等,相信大多數測試人員都有使用過,至少從接觸到的大多數簡歷的描述上看是這樣的。除了這些成熟的工具,也有不少有必定技術能力的測試(開發)人員自行開發了一些接口測試框架,質量也是良莠不齊。python

可是,當我打算在項目組中推行接口自動化測試時,蒐羅了一圈,也沒有找到一款特別滿意的工具或框架,老是與理想中的構想存在必定的差距。git

那麼理想中的接口自動化測試框架應該是怎樣的呢?github

測試工具(框架)脫離業務使用場景都是耍流氓!因此咱們不妨先來看下平常工做中的一些常見場景。數據庫

  • 測試或開發人員在定位問題的時候,想調用某個接口查看其是否響應正常;
  • 測試人員在手工測試某個功能點的時候,須要一個訂單號,而這個訂單號能夠經過順序調用多個接口實現下單流程;
  • 測試人員在開始版本功能測試以前,能夠先檢測下系統的全部接口是否工做正常,確保接口正常後再開始手工測試;
  • 開發人員在提交代碼前須要檢測下新代碼是否對系統的已有接口產生影響;
  • 項目組須要天天定時檢測下測試環境全部接口的工做狀況,確保當天的提交代碼沒有對主幹分支的代碼形成破壞;
  • 項目組須要定時(30分鐘)檢測下生產環境全部接口的工做狀況,以便及時發現生產環境服務不可用的狀況;
  • 項目組須要不按期對核心業務場景進行性能測試,指望能減小人力投入,直接複用接口測試中的工做成果。

能夠看到,以上羅列的場景你們應該都很熟悉,這都是咱們在平常工做中常常須要去作的事情。可是在沒有一款合適工具的狀況下,效率每每十分低下,或者就是某些重要工做壓根就沒有開展,例如接口迴歸測試、線上接口監控等。編程

先說下最簡單的手工調用接口測試。可能有人會說,Postman就能夠知足需求啊。的確,Postman做爲一款通用的接口測試工具,它能夠構造接口請求,查看接口響應,從這個層面上來講,它是知足了接口測試的功能需求。可是在具體的項目中,使用Postman並非那麼高效。json

不妨舉個最多見的例子。網絡

某個接口的請求參數很是多,而且接口請求要求有MD5簽名校驗;簽名的方式爲在Headers中包含一個sign參數,該參數值經過對URLMethodBody的拼接字符串進行MD5計算後獲得。session

回想下咱們要對這個接口進行測試時是怎麼作的。首先,咱們須要先參照接口文檔的描述,手工填寫完全部接口參數;而後,按照簽名校驗方式,對全部參數值進行拼接獲得一個字符串,在另外一個MD5計算工具計算獲得其MD5值,將簽名值填入sign參數;最後,纔是發起接口請求,查看接口響應,並人工檢測響應是否正常。最坑爹的是,咱們每次須要調用這個接口的時候,以上工做就得從新來一遍。這樣的實際結果是,面對參數較多或者須要簽名驗證的接口時,測試人員可能會選擇忽略不進行接口測試。數據結構

除了單個接口的調用,不少時候咱們也須要組合多個接口進行調用。例如測試人員在測試物流系統時,常常須要一個特定組合條件下生成的訂單號。而因爲訂單號關聯的業務較多,很難直接在數據庫中生成,所以當前業務測試人員廣泛採起的作法,就是每次須要訂單號時模擬下單流程,順序調用多個相應的接口來生成須要的訂單號。能夠想象,在手工調用單個接口都如此麻煩的狀況下,每次都要手工調用多個接口會有多麼的費時費力。併發

再說下接口自動化調用測試。這一起大多接口測試框架都支持,廣泛的作法就是經過代碼編寫接口測試用例,或者採用數據驅動的方式,而後在支持命令行(CLI)調用的狀況下,就能夠結合Jenkins或者crontab實現持續集成,或者定時接口監控的功能。

思路是沒有問題的,問題在於實際項目中的推進落實狀況。要說自動化測試用例最靠譜的維護方式,仍是直接經過代碼編寫測試用例,可靠且不失靈活性,這也是不少經歷過慘痛教訓的老手的感悟,甚至網絡上還出現了一些反測試框架的言論。但問題在於項目中的測試人員並非都會寫代碼,也不是對其強制要求就能立刻學會的。這種狀況下,要想在具體項目中推進接口自動化測試就很難,就算我能夠幫忙寫一部分,可是不少時候接口測試用例也是要結合業務邏輯場景的,我也的確是無法在這方面投入太多時間,畢竟對接的項目實在太多。因此也是基於這類緣由,不少測試框架提倡採用數據驅動的方式,將業務測試用例和執行代碼分離。不過因爲不少時候業務場景比較複雜,大多數框架測試用例模板引擎的表達能力不足,很難採用簡潔的方式對測試場景進行描述,從而也無法很好地獲得推廣使用。

能夠列舉的問題還有不少,這些也的確都是在互聯網企業的平常測試工做中真實存在的痛點。

基於以上背景,我產生了開發ApiTestEngine的想法。

對於ApiTestEngine的定位,與其說它是一個工具或框架,它更多的應該是一套接口自動化測試的最佳工程實踐,而簡潔優雅實用應該是它最核心的特色。

固然,每位工程師對最佳工程實踐的理念或多或少都會存在一些差別,也但願你們能多多交流,在思惟的碰撞中共同進步。

核心特性

ApiTestEngine的核心特性概述以下:

  • 支持API接口的多種請求方法,包括 GET/POST/HEAD/PUT/DELETE 等
  • 測試用例與代碼分離,測試用例維護方式簡潔優雅,支持YAML
  • 測試用例描述方式具備表現力,可採用簡潔的方式描述輸入參數和預期輸出結果
  • 接口測試用例具備可複用性,便於建立複雜測試場景
  • 測試執行方式簡單靈活,支持單接口調用測試、批量接口調用測試、定時任務執行測試
  • 測試結果統計報告簡潔清晰,附帶詳盡日誌記錄,包括接口請求耗時、請求響應數據等
  • 身兼多職,同時實現接口管理、接口自動化測試、接口性能測試(結合Locust)
  • 具備可擴展性,便於擴展實現Web平臺化

特性拆解介紹

支持API接口的多種請求方法,包括 GET/POST/HEAD/PUT/DELETE 等

我的偏好,編程語言選擇Python。而採用Python實現HTTP請求,最好的方式就是採用Requests庫了,簡潔優雅,功能強大。

測試用例與代碼分離,測試用例維護方式簡潔優雅,支持YAML

要實現測試用例與代碼的分離,最好的作法就是作一個測試用例加載引擎和一個測試用例執行引擎,這也是以前在作AppiumBooster框架的時候總結出來的最優雅的實現方式。固然,這裏須要事先對測試用例制定一個標準的數據結構規範,做爲測試用例加載引擎和測試用例執行引擎的橋樑。

須要說明的是,測試用例數據結構必須包含接口測試用例完備的信息要素,包括接口請求的信息內容(URL、Headers、Method等參數),以及預期的接口請求響應結果(StatusCode、ResponseHeaders、ResponseContent)。

這樣作的好處在於,無論測試用例採用什麼形式進行描述(YAML、JSON、CSV、Excel、XML等),也無論測試用例是否採用了業務分層的組織思想,只要在測試用例加載引擎中實現對應的轉換器,均可以將業務測試用例轉換爲標準的測試用例數據結構。而對於測試用例執行引擎而言,它無需關注測試用例的具體描述形式,只須要從標準的測試用例數據結構中獲取到測試用例信息要素,包括接口請求信息和預期接口響應信息,而後構造併發起HTTP請求,再將HTTP請求的響應結果與預期結果進行對比判斷便可。

至於爲何明確說明支持YAML,這是由於我的認爲這是最佳的測試用例描述方式,表達簡潔不累贅,同時也能包含很是豐富的信息。固然,這只是我的喜愛,若是喜歡採用別的方式,只須要擴展實現對應的轉換器便可。

測試用例描述方式具備表現力,可採用簡潔的方式描述輸入參數和預期輸出結果

測試用例與框架代碼分離之後,對業務邏輯測試場景的描述重任就落在測試用例上了。好比咱們選擇採用YAML來描述測試用例,那麼咱們就應該能在YAML中描述各類複雜的業務場景。

那麼怎麼理解這個「表現力」呢?

簡單的參數值傳參應該都容易理解,咱們舉幾個相對複雜但又比較常見的例子。

  • 接口請求參數中要包含當前的時間戳;
  • 接口請求參數中要包含一個16位的隨機字符串;
  • 接口請求參數中包含簽名校驗,須要對多個請求參數進行拼接後取md5值;
  • 接口響應頭(Headers)中要包含一個X-ATE-V頭域,而且須要判斷該值是否大於100;
  • 接口響應結果中包含一個字符串,須要校驗字符串中是否包含10位長度的訂單號;
  • 接口響應結果爲一個多層嵌套的json結構體,須要判斷某一層的某一個元素值是否爲True。

能夠看出,以上幾個例子都是無法直接在測試用例裏面描述參數值的。若是是採用Python腳原本編寫測試用例還好解決,只須要經過Python函數實現便可。可是如今測試用例和框架代碼分離了,咱們無法在YAML裏面執行Python函數,這該怎麼辦呢?

答案就是,定義函數轉義符,實現自定義模板。

這種作法其實也不難理解,也算是模板語言通用的方式。例如,咱們將${}定義爲轉義符,那麼在{}內的內容就再也不當作是普通的字符串,而應該轉義爲變量值,或者執行函數獲得實際結果。固然,這個須要咱們在測試用例執行引擎進行適配實現,最簡單方式就是提取出${}中的字符串,經過eval計算獲得表達式的值。若是要實現更復雜的功能,咱們也能夠將接口測試中經常使用的一些功能封裝爲一套關鍵字,而後在編寫測試用例的時候使用這些關鍵字。

接口測試用例具備可複用性,便於建立複雜測試場景

不少狀況下,系統的接口都是有業務邏輯關聯的。例如,要請求調用登陸接口,須要先請求獲取驗證碼的接口,而後在登陸請求中帶上獲取到的驗證碼;而要請求數據查詢的接口,又要在請求參數中包含登陸接口返回的session值。這個時候,咱們若是針對每個要測的業務邏輯,都單獨描述要請求的接口,那麼就會形成大量的重複描述,測試用例的維護也十分臃腫。

比較好的作法是,將每個接口調用單獨封裝爲一條測試用例,而後在描述業務測試場景時,選擇對應的接口,按照順序拼接爲業務場景測試用例,就像搭積木通常。若是你以前讀過AppiumBooster的介紹,應該還會聯想到,咱們能夠將經常使用的功能組成模塊用例集,而後就能夠在更高的層面對模塊用例集進行組裝,實現更復雜的測試場景。

不過,這裏有一個很是關鍵的問題須要解決,就是如何在接口測試用例以前傳參的問題。其實實現起來也不復雜,咱們能夠在接口請求響應結果中指定一個變量名,而後將接口返回關鍵值提取出來後賦值給那個變量;而後在其它接口請求參數中,傳入這個${變量名}便可。

測試執行方式簡單靈活,支持單接口調用測試、批量接口調用測試、定時任務執行測試

經過背景中的例子能夠看出,須要使用接口測試工具的場景不少,除了定時地對全部接口進行自動化測試檢測外,不少時候在手工測試的時候也須要採用接口測試工具進行輔助,也就是半手工+半自動化的模式。

而業務測試人員在使用測試工具的時候,遇到的最大問題在於除了須要關注業務功能自己,還須要花費不少時間去處理技術實現細節上的東西,例如簽名校驗這類狀況,並且每每後者在重複操做中佔用的時間更多。

這個問題的確是無法避免的,畢竟不一樣系統的接口千差萬別,不可能存在一款工具能夠自動處理全部狀況。可是咱們能夠嘗試將接口的技術細節實現和業務參數進行拆分,讓業務測試人員只須要關注業務參數部分。

具體地,咱們能夠針對每個接口配置一個模板,將其中與業務功能無關的參數以及技術細節封裝起來,例如簽名校驗、時間戳、隨機值等,而與業務功能相關的參數配置爲可傳參的模式。

這樣作的好處在於,與業務功能無關的參數以及技術細節咱們只須要封裝配置一次,並且這個工做能夠由開發人員或者測試開發人員來實現,減輕業務測試人員的壓力;接口模板配置好後,測試人員只須要關注與業務相關的參數便可,結合業務測試用例,就能夠在接口模板的基礎上很方便地配置生成多個接口測試用例。

測試結果統計報告簡潔清晰,附帶詳盡日誌記錄,包括接口請求耗時、請求響應數據等

測試結果統計報告,應該遵循簡潔而不簡單的原則。「簡潔」,是由於大多數時候咱們只須要在最短的時間內判斷全部接口是否運行正常便可。而「不簡單」,是由於當存在執行失敗的測試用例時,咱們指望能得到接口測試時儘量詳細的數據,包括測試時間、請求參數、響應內容、接口響應耗時等。

以前在讀locust源碼時,其對HTTP客戶端的封裝方式給我留下了深入的印象。它採用的作法是,繼承requests.Session類,在子類HttpSession中重寫覆蓋了request方法,而後在request方法中對requests.Session.request進行了一層封裝。

request_meta = {}

# set up pre_request hook for attaching meta data to the request object
request_meta["method"] = method
request_meta["start_time"] = time.time()

response = self._send_request_safe_mode(method, url, **kwargs)

# record the consumed time
request_meta["response_time"] = int((time.time() - request_meta["start_time"]) * 1000)

request_meta["content_size"] = int(response.headers.get("content-length") or 0)複製代碼

HttpLocust的每個虛擬用戶(client)都是一個HttpSession實例,這樣每次在執行HTTP請求的時候,既可充分利用Requests庫的強大功能,同時也能將請求的響應時間、響應體大小等原始性能數據進行保存,實現可謂十分優雅。

受到該處啓發,要保存接口的詳細請求響應數據也可採用一樣的方式。例如,要保存ResponseHeadersBody只須要增長以下兩行代碼:

request_meta["response_headers"] = response.headers
request_meta["response_content"] = response.content複製代碼

身兼多職,同時實現接口管理、接口自動化測試、接口性能測試(結合Locust)

其實像接口性能測試這樣的需求,不該該算到接口自動化測試框架的職責範圍以內。可是在實際項目中需求就是這樣,又要作接口自動化測試,又要作接口性能測試,並且還不想同時維護兩套代碼。

多虧有了locust性能測試框架,接口自動化和性能測試腳本還真能合二爲一。

前面也講了,HttpLocust的每個虛擬用戶(client)都是一個HttpSession實例,而HttpSession又繼承自requests.Session類,因此HttpLocust的每個虛擬用戶(client)也是requests.Session類的實例。

一樣的,咱們在用Requests庫作接口測試時,請求客戶端其實也是requests.Session類的實例,只是咱們一般用的是requests的簡化用法。

如下兩種用法是等價的。

resp = requests.get('http://debugtalk.com')

# 等價於
client = requests.Session()
resp = client.get('http://debugtalk.com')複製代碼

有了這一層關係之後,要在接口自動化測試和性能測試之間切換就很容易了。在接口測試框架內,能夠經過以下方式初始化HTTP客戶端。

def __init__(self, origin, kwargs, http_client_session=None):
   self.http_client_session = http_client_session or requests.Session()複製代碼

默認狀況下,http_client_sessionrequests.Session的實例,用於進行接口測試;當須要進行性能測試時,只須要傳入locustHttpSession實例便可。

具備可擴展性,便於擴展實現Web平臺化

當要將測試平臺推廣至更廣闊的用戶羣體(例如產品經理、運營人員)時,對框架實現Web化就在所不免了。在Web平臺上查看接口測試用例運行狀況、對接口模塊進行配置、對接口測試用例進行管理,的確會便捷不少。

不過對於接口測試框架來講,Web平臺只能算做錦上添花的功能。咱們在初期能夠優先實現命令行(CLI)調用方式,規範好數據存儲結構,後期再結合Web框架(如Flask)增長實現Web平臺功能。

寫在後面

以上即是我對ApiTestEngine特性的詳細介紹,也算是我我的對接口自動化測試最佳工程實踐的理念闡述。

當前,ApiTestEngine還處於開發過程當中,代碼也開源託管在GitHub上,歡迎Star關注。

GitHub項目地址:github.com/debugtalk/A…

參考

相關文章
相關標籤/搜索