ApiTestEngine 演進之路(2)探索優雅的測試用例描述方式

在《ApiTestEngine 演進之路(1)搭建基礎框架》一文中,我們完成了ApiTestEngine基礎框架的搭建,並實現了簡單介面的測試功能。

接下來,我們就針對複雜類型的介面(例如包含簽名校驗等機制),通過對介面的業務參數和技術細節進行分離,實現簡潔優雅的介面測試用例描述。

傳統的測試用例編寫方式

對於在自動化測試中將測試數據與代碼實現進行分離的好處,我之前已經講過多次,這裡不再重複。

測試數據與代碼實現分離後,簡單的介面還好,測試用例編寫不會有什麼問題;但是當面對複雜一點的介面(例如包含簽名校驗等機制)時,我們編寫自動化測試用例還是會比較繁瑣。

我們從一個最常見的案例入手,看下編寫自動化測試用例的過程,相信大家看完後就會對上面那段話有很深的感受。

以API介面服務(Mock Server)的創建新用戶功能為例,該介面描述如下:

請求數據:nUrl: 127.0.0.1:5000/api/usernMethod: POSTnHeaders: {"content-type": "application/json", "Random": "A2dEx", "Authorization": "47f135c33e858f2e3f55156ae9f78ee1"}nBody: {"name": "user1", "password": "123456"}n######n預期的正常響應數據:nStatus_Code: 201nHeaders: {Date: Fri, 23 Jun 2017 07:05:41 GMT, Content-Length: 54, Content-Type: application/json, Server: Werkzeug/0.12.2 Python/2.7.13}nBody: {"msg": "user created successfully.", "success": true, "uuid": "JsdfwerL"}

其中,請求Headers中的Random欄位是一個5位長的隨機字元串,Authorization欄位是一個簽名值,簽名方式為TOKEN+RequestBody+Random拼接字元串的MD5值。更具體的,RequestBody要求字典的Key值按照由小到大的排序方式。介面請求成功後,返回的是一個JSON結構,裡面的success欄位標識請求成功與否的狀態,如果成功,uuid欄位標識新創建用戶的唯一ID。

相信只要是接觸過介面測試的同學對此應該都會很熟悉,這也是後台系統普遍採用的簽名校驗方式。在具體的系統中,可能字元串拼接方式或簽名演算法存在差異,但是模式基本上都是類似的。

那麼面對這樣一個介面,我們會怎樣編寫介面測試用例呢?

首先,請求的數據是要有的,我們會先準備一個可用的賬號,例如{"password": "123456", "name": "user1"}。

然後,由於介面存在簽名校驗機制,因此我們除了要知道伺服器端使用的TOKEN(假設為debugtalk)外,還要準備好Random欄位和Authorization欄位。Random欄位好說,我們隨便生成一個,例如A2dEx;Authorization欄位就會複雜不少,需要我們按照規定先將RequestBody根據字典的Key值進行排序,得到{"name": "user1", "password": "123456"},然後與TOKEN和Random欄位拼接字元串得到debugtalk{"password": "123456", "name": "user1"}A2dEx,接著再找一個MD5工具,計算得到簽名值a83de0ff8d2e896dbd8efb81ba14e17d。

最後,我們才可以完成測試用例的編寫。假如我們採用YAML編寫測試用例,那麼用例寫好後應該就是如下樣子。

-n name: create user which does not existn request:n url: http://127.0.0.1:5000/api/users/1000n method: POSTn headers:n Content-Type: application/jsonn authorization: a83de0ff8d2e896dbd8efb81ba14e17dn random: A2dExn data:n name: user1n password: 123456n response:n status_code: 201n headers:n Content-Type: application/jsonn body:n success: truen msg: user created successfully.n uuid: JsdfwerLn

該測試用例可以在ApiTestEngine中正常運行,我們也可以採用同樣的方式,對系統的所有介面編寫測試用例,以此實現項目的介面自動化測試覆蓋。

但問題在於,每個介面通常會對應多條測試用例,差異只是在於請求的數據會略有不同,而測試用例量越大,我們人工去準備測試數據的工作量也就越大。更令人抓狂的是,我們的系統介面不是一直不變的,有時候會根據業務需求的變化進行一些調整,相應地,我們的測試數據也需要進行同步更新,這樣一來,所有相關的測試用例數據就又得重新計算一遍(任意欄位數據產生變化,簽名值就會大不相同)。

可以看出,如果是採用這種方式編寫維護介面測試用例,人力和時間成本都會非常高,最終的結果必然是介面自動化測試難以在實際項目中得以開展。

理想的用例描述方式

在上面案例中,編寫介面測試用例時之所以會很繁瑣,主要是因為介面存在簽名校驗機制,導致我們在準備測試數據時耗費了太多時間在這上面。

然而,對於測試人員來說,介面的業務功能才是需要關注的,至於介面採用什麼簽名校驗機制這類技術細節,的確不應耗費過多時間和精力。所以,我們的介面測試框架應該設法將介面的技術細節實現和業務參數進行拆分,並能自動處理與技術細節相關的部分,從而讓業務測試人員只需要關注業務參數部分。

那要怎麼實現呢?

在開始實現之前,我們不妨借鑒BDD(行為驅動開發)的思想,先想下如何編寫介面測試用例的體驗最友好,換句話說,就是讓業務測試人員寫用例寫得最爽。

還是上面案例的介面測試用例,可以看出,最耗時的地方主要是計算簽名校驗值部分。按理說,簽名校驗演算法我們是已知的,要是可以在測試用例中直接調用簽名演算法函數就好了。

事實上,這也是各種模板語言普遍採用的方式,例如Jinja2模板語言,可以在{% %}中執行函數語句,在{{ }}中可以調用變數參數。之前我在設計[AppiumBooster][AppiumBooster]時也採用了類似的思想,可以通過${config.TestEnvAccount.UserName}的方式在測試用例中引用預定義的全局變數。

基於該思路,假設我們已經實現了gen_random_string這樣一個生成指定位數的隨機字元串的函數,以及gen_md5這樣一個計算簽名校驗值的函數,那麼我們就可以嘗試採用如下方式來描述我們的測試用例:

- test:n name: create user which does not existn variable_binds:n - TOKEN: debugtalkn - random: ${gen_random_string(5)}n - json: {"name": "user", "password": "123456"}n - authorization: ${gen_md5($TOKEN, $json, $random)}n request:n url: http://127.0.0.1:5000/api/users/1000n method: POSTn headers:n Content-Type: application/jsonn authorization: $authorizationn random: $randomn json: $jsonn extract_binds:n user_uuid: content.uuidn validators:n - {"check": "status_code", "comparator": "eq", "expected": 201}n - {"check": "content.success", "comparator": "eq", "expected": true}n

在如上用例中,用到了兩種轉義符:

  • $作為變數轉義符,$var將不再代表的是普遍的字元串,而是var變數的值;
  • ${}作為函數的轉義符,${}內可以直接填寫函數名稱及調用參數,甚至可以包含變數。

為什麼會選擇採用這種描述方式?(Why?)

其實這也是我經過大量思考和實踐之後,才最終確定的描述方式。如果真要講述這個思路歷程。。。還是不細說了,此處可省下一萬字。(主要的思路無非就是要實現轉義的效果,並且表達要簡潔清晰,因此必然會用到特殊字元;而特殊字元在YAML中大多都已經有了特定的含義,排除掉不可用的之後,剩下的真沒幾個了,然後再借鑒其它框架常用的符號,所以說最終選擇$和${}也算是必然。)

可以確定的是,這種描述方式的好處非常明顯,不僅可以實現複雜計算邏輯的函數調用,還可以實現變數的定義和引用。

除了轉義符,由於介面測試中經常需要對結果中的特定欄位進行提取,作為後續介面請求的參數,因此我們實現了extract_binds這樣一個結果提取器,只要返回結果是JSON類型,就可以將其中的任意欄位進行提取,並保存到一個變數中,方便後續介面請求進行引用。

另外,為了更好地實現對介面響應結果的校驗,我們廢棄了先前的方式,實現了獨立的結果校驗器validators。這是因為,很多時候在比較響應結果時,並不能簡單地按照欄位值是否相等來進行校驗,除此之外,我們可能還需要檢查某個欄位的長度是否為指定位數,元素列表個數是否大於某個數值,甚至某個字元串是否滿足正則匹配等等。

相信你們肯定會想,以上這些描述方式的確是很簡潔,但更多地感覺是在臆想,就像開始說的gen_random_string和gen_md5函數,我們只是假設已經定義好了。就算描述得再優雅再完美,終究也還只是YAML/JSON文本格式而已,要怎樣才能轉換為執行的代碼呢?

這就要解決How?的問題了。

嗯,這就是用例模板引擎的核心了,也算是ApiTestEngine最核心的功能特性。

更具體的,從技術實現角度,主要分為三大塊:

  • 如何在用例描述(YAML/JSON)中實現函數的定義和調用
  • 如何在用例描述中實現參數的定義和引用,包括用例內部和用例集之間
  • 如何在用例描述中實現預期結果的描述和測試結果的校驗

這三大塊內容涉及到較多的技術實現細節,我們將在後續的文章中結合代碼逐個深入進行講解。

閱讀更多

  • 《介面自動化測試的最佳工程實踐(ApiTestEngine)》
  • 《ApiTestEngine 演化之路(0)開發未動,測試先行》
  • 《ApiTestEngine 演進之路(1)搭建基礎框架》
  • ApiTestEngine GitHub源碼

推薦閱讀:

本周熱門開發工具一覽
快速創建軸網
介紹幾款常用的在線API管理工具
有沒有開源的api管理系統?
api文檔在線自動生成工具Swagger推薦(含Spring Cloud集成版)

TAG:API | 开源项目 | Python框架 |