HttpRunner3.X - 全面講解如何落地專案實戰

一加一發表於2021-10-04

一、前言

  接觸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資訊進行斷言的。

 

相關文章