一、前言
接觸httprunner框架有一段時間了,也一直探索如何更好的落地到專案上,本篇主要講述如何應用到實際的專案中,達到提升測試效率的目的。
1、專案難題
這個月開始忙起來了,接了個大專案,苦不堪言,以下3個問題應該大部分測試人員都能感同身受,並且也是經常會遇到的問題
- 測試時間被壓縮
- 測試資源被砍
- 業務流程長
2、解決方案
針對前面2點,除了猛加班,還能有更好的解決方案嗎?只要你夠肝就能完美應對前面2點問題
針對第3點,只能英勇獻身,主動寫指令碼幫助團隊造資料,以前是用jmeter做介面自動化,但在接觸了httprunner後,發現該測試框架比jmeter方便很多,因為它有.har直接轉換成用例的功能,所以就採用了httprunner框架。
二、設計自動化測試用例結構
1、專案系統流程圖
圖1:專案系統流程圖
2、用例分層
2.1 簡要說明
該專案範圍是訂單模組,根據系統流程圖可以知道,訂單的資料需要走2個系統的互動,共涉及3個大模組。應專案要求,將需求單錄入->bom單的流程實現介面自動化用例,對應功能步驟為需求單錄入->商品確認成功。
2.2 官網用例分層示例圖
分層思想如下:
- 測試用例(testcase)應該是完整且獨立的,每條測試用例應該是都可以獨立執行的
- 測試用例是測試步驟(teststep)的有序集合
- 測試用例集(testsuite)是測試用例的無序集合,集合中的測試用例應該都是相互獨立,不存在先後依賴關係的;如果確實存在先後依賴關係,那就需要在測試用例中完成依賴的處理
圖2:官網分層示例圖
3)專案用例分層架構
該專案用例分層架構圖是根據自己的理解做的用例分層,如果各位大佬有其他好的用例拆分設計可以賜教下。
如圖3,共有5條用例,用例跟用例之間有呼叫關係,因在測試用例中已經完成了用例依賴處理,所以testsuite的每條用例都是可以直接執行的,不存在先後順序。
本專案存在個問題,應當都會存在這種問題,即當case3呼叫case2,若case2用例存在問題,那case3自然就會執行失敗。
圖3:專案用例分層架構圖
三、專案原始碼解析
1、.env檔案說明
env主要是存放環境配置的資訊,比如url或者使用者名稱密碼之類的
username=李白 password=123456 url=https://xxx.com
2、登入Case1:login_test.py
- 知識點1:結合實際情況,若有多個測試環境,url是會變的,使用者名稱和密碼也可能會頻繁更換,所以可以考慮將url和使用者名稱密碼放在.env檔案中,達到可配置化
- 知識點2:.env檔案的變數取用:${ENV(檔案內的鍵名)},詳見下面標黃部分
# NOTE: Generated By HttpRunner v3.1.5 # FROM: har\login.har from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase class TestCaseLogin(HttpRunner): config = Config("登入").verify(False) teststeps = [ Step( RunRequest("登入介面") .post("https://xx.com/login") .with_headers( **{ "content-length": "183",
"referer": "${ENV(url)}", #引用.env檔案的url變數 } ) .with_json( { "employeeName": "${ENV(username)}", #引用.env檔案的username變數
"password": "${ENV(password)}", #引用.env檔案的password變數 } ) .extract() .with_jmespath("body.data.userId", "userId") #提取userid,userNo,userName供後續用例使用 .with_jmespath("body.data.userNo", "userNo") .with_jmespath("body.data.userName", "userName") .validate() .assert_equal("status_code", 200) ), ]
if __name__ == "__main__": TestCaseLogin().test_start()
3、需求單Case2:demand_test.py
- from testcases import login_test:該用例需要先呼叫登入用例,如果想要引用別的檔案的用例,需要先import進來
- RunTestCase:前面的文章講過,這是直接呼叫別的用例,引用方式為.call(檔案的類方法)
- headers:該用例共有3個step,請求頭是一樣的,所以可以用一個變數存起來,便於後面step引用
- .with_jmespath("body.data.list[0].styleCode","styleCode"):類似jmter的jsonpath寫法,可以提取想要的響應欄位,用於後面用例的引用
# NOTE: Generated By HttpRunner v3.1.5 # FROM: har\需求單.har from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from testcases import login_test class TestCase需求單(HttpRunner): config = Config("需求單錄入-需求單列表-設計單").verify(False).base_url("https://xxx.cn") headers = {"Connection": "keep-alive", "Content-Length": "1480", } teststeps = [ Step( RunTestCase("呼叫登入用例").call(login_test.TestCaseLogin) ), Step( RunRequest("需求單錄入") .put("/demand-task") .with_headers( **headers ) .with_cookies( **{ "experimentation_subject_id": "ImY3ZG" } ) .with_json( {"deliveryTypePeriod": "2", "remark": "需求單", ...... } ) .extract() .with_jmespath("body.data.demandDetailId","demandDetailId") #提取detaildid,taskid供後續用例使用 .with_jmespath("body.data.demandTaskId","demandTaskId") .validate() .assert_equal("status_code", 200) ), Step( RunRequest("需求列表介面") .post("/list") .with_headers( **headers ) .with_json( { "demandTaskType": "", } ) .extract() .with_jmespath("body.data.list[0].styleCode","styleCode") .validate() .assert_equal("status_code", 200) ), Step( RunRequest("設計單介面") .put( "/task/${demandDetailId}" #引用前面提取的detaildid ) .with_headers( **headers ) ) .with_json( { "current": "178", } ) .validate() .assert_equal("status_code", 200) ), ] if __name__ == "__main__": TestCase需求單().test_start()
4、設計拆單Case3:design_test.py
- export(*["styleCode","demandTaskId"]):這裡需要用到case2提取出來的引數,所以要用.export匯出引數變數,供case3引用,引用方式為 ${引數名}
- teardown_hook("${sleep(2)}"):case2和case3所屬的模組歸屬於不同的服務,case2服務完成設計單推送到case3服務時會存在回撥時間差的情況,
- 所以需要使用teardown_hook,讓用例執行完後,等待幾秒再執行後面的step。(原理是呼叫debugtakl.py的sleep函式,達到延遲執行的目的)
# NOTE: Generated By HttpRunner v3.1.5 # FROM: har\設計拆單.har from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from testcases import demand_test class TestCase設計拆單(HttpRunner): config = Config("設計拆單列表-提交拆單").verify(False).base_url("https:xxx.cn") headers = {"Connection": "keep-alive", } teststeps = [ Step( RunTestCase("呼叫需求單用例Case2").call(demand_test.TestCase需求單).export(*["styleCode","demandTaskId"]).teardown_hook("${sleep(2)}") ), Step( RunRequest("設計拆單列表介面") .post("type/list") .with_headers( **headers ) .with_json( { "prototypeStatus": "1", } ) .extract() .with_jmespath("body.data.list[0].prototypeId","prototypeId") .with_jmespath("body.data.list[0].designCode","designCode") .validate() .assert_equal("status_code", 200) ), Step( RunRequest("提交拆單介面") .put("/save") .with_headers( **headers ) ) .with_json( { "prototypeId": "${prototypeId}","styleCode": "${styleCode}", "demandTaskId": "${demandTaskId}", "designCode": "${designCode}", } ) .validate() .assert_equal("status_code", 200) ) ] if __name__ == "__main__": TestCase設計拆單().test_start()
5、商品跟進確認Case4:goods_test.py
# NOTE: Generated By HttpRunner v3.1.5 # FROM: har\商品跟進確認.har from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from testcases import design_test,confirm_test class TestCase商品跟進確認(HttpRunner): config = Config("商品跟進列表-獲取匹配結果-商品確認-商品跟進提交").verify(False).base_url("https://xxx.cn") teststeps = [ Step( RunTestCase("呼叫設計拆單用例Case3").call(design_test.TestCase設計拆單).teardown_hook("${sleep(2)}").export(*["designCode"]) ), Step(
#因case5和case4屬於不同的系統,即不同服務,所以匹配成功後,存在推送時間差問題,故執行完用例後需等待幾秒 RunTestCase("呼叫匹配用例Case5").call(confirm_test.TestCase匹配).teardown_hook("${sleep(3)}") ), Step( RunRequest("商品跟進列表介面") .post("/goods-track/list") .with_headers( **{"Connection": "keep-alive", } ) .with_json( {"designCode": "${designCode}","pageNum": 1, "pageSize": 20, } ) .extract() .with_jmespath("body.data.list[0].materialTrackId","materialTrackId") .validate() .assert_equal("status_code", 200) ), Step( RunRequest("獲取匹配結果") .get( "/details/${materialTrackId}" ) .with_params( **{"workerId": "178", } ) .with_headers( **{"Connection": "keep-alive", } ) .extract() .with_jmespath("body.data.demandList[0].matchResultList[0].matchResultId","matchResultId") .validate() .assert_equal("status_code", 200) ), Step( RunRequest("商品確認介面") .post( "/result/confirm" ) .with_headers( **{"Connection": "keep-alive", } ) .with_json( { "materialTrackId": "${materialTrackId}", "matchResultId": "${matchResultId}", } ) .validate() .assert_equal("status_code", 200) ), Step( RunRequest("商品跟進提交介面") .post( "/goods/submit" ) .with_headers( **{"Connection": "keep-alive", } ) .with_json( { "materialTrackId": "${materialTrackId}", "updateList": [ { "materialTrackId": "${materialTrackId}", "matchResultId": "${matchResultId}", } ], } ) .validate() .assert_equal("status_code", 200) ), ] if __name__ == "__main__": TestCase商品跟進確認().test_start()
6、匹配Case5:confirm_test.py
# NOTE: Generated By HttpRunner v3.1.5 # FROM: har\匹配.har from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase class TestCase匹配(HttpRunner): config = Config("匹配列表-匹配結果").verify(False).base_url("https://xxx.cn") teststeps = [ Step( RunRequest("匹配列表介面") .post("/list") .with_headers( **{"Connection": "keep-alive", } ) .with_json( {"pageNum": 1, "pageSize": 10, } ) .extract() .with_jmespath("body.data.list[0].demandId","demandId") .validate() .assert_equal("status_code", 200) ), Step( RunRequest("匹配結果介面") .post("/match/create") .with_headers( **{"Connection": "keep-alive", "Content-Length": "577", } ) .with_json( { "bottomCloth": "","colorNumber": "1.0", "commodityCode": "PURE", } ) .validate() .assert_equal("status_code", 200) ), ] if __name__ == "__main__": TestCase匹配().test_start()
四、結束語
以上就是本專案運用httprunner實現介面自動化的全過程,因涉及到公司安全,所以原始碼中刪減了部分資訊,但這並不會影響整個思路的梳理哈,本次專案涉及到的知識點比較少,後續新專案如果有其他知識點運用,會繼續更新進來,還有因為想要的引數 介面都有返回,所以這次沒有用到資料庫,如果對資料庫連線感興趣的,可以看我前面的部落格,以及因專案原因,本次的斷言資訊基本是用它自動生成的斷言,沒有增加新的斷言,但一般介面自動化中是需要跟落表資訊或者關鍵id資訊進行斷言的。