HttpRunner 的測試用例分層機制

debugtalk發表於2017-12-24

背景描述

HttpRunner中,測試用例引擎最大的特色就是支援YAML/JSON格式的用例描述形式。

採用YAML/JSON格式編寫維護測試用例,優勢還是很明顯的:

  • 相比於表格形式,具有更加強大的靈活性和更豐富的資訊承載能力;
  • 相比於程式碼形式,減少了不必要的程式語言語法重複,並最大化地統一了用例描述形式,提高了用例的可維護性。

以最常見的登入登出為例,我們的測試用例通常會描述為如下形式:

- config:
    name: demo-login-logoff
    variable_binds:
        - UserName: test001
        - Password: 123456
    request:
        base_url: http://xxx.debugtalk.com
        headers:
            Accept: application/json
            User-Agent: iOS/10.3

- test:
    name: Login
    request:
        url: /api/v1/Account/Login
        method: POST
        json:
            UserName: $UserName
            Pwd: $Password
            VerCode: ""
    validators:
        - eq: ["status_code", 200]
        - eq: ["content.IsSuccess", True]
        - eq: ["content.Code", 200]

- test:
    name: Logoff
    request:
        url: /api/v1/Account/LoginOff
        method: GET
    validators:
        - eq: ["status_code", 200]
        - eq: ["content.IsSuccess", True]
        - eq: ["content.Code", 200]
複製程式碼

相信大家已經對該種用例描述形式十分熟悉了。不過,該種描述形式的問題在於,介面通常會出現在多個測試場景中,而每次都需要對介面進行定義描述,包括請求的URL、Header、Body、以及預期響應值等,這就會產生大量的重複。

例如,在某個專案中存在三個測試場景:

  • 場景A:註冊新賬號(API_1/2)、登入新註冊的賬號(API_3/4/5)、檢視登入狀態(API_6);
  • 場景B:登入已有賬號(API_3/4/5)、登出登入(API_7/8);
  • 場景C:登出登入(API_7/8)、檢視登入狀態(API_6)、註冊新賬號(API_1/2)。

按照常規的介面測試用例編寫方式,我們需要建立3個場景檔案,然後在各個檔案中分別描述三個測試場景相關的介面資訊。示意圖如下所示。

HttpRunner 的測試用例分層機制

在本例中,介面(API_1/2/6)在場景A和場景C中都進行了定義;介面(API_3/4/5)在場景A和場景B中都進行了定義;介面(API_7/8)在場景B和場景C中都進行了定義。可以預見,當測試場景增多以後,介面定義描述的維護就會變得非常困難和繁瑣。

介面的分層定義描述

那要如何進行優化呢?

其實也很簡單,在程式語言中,如果出現重複程式碼塊,我們通常會將其封裝為類或方法,然後在需要時進行呼叫,以此來消除重複。同樣地,我們也可以將專案的API進行統一定義,裡面包含API的請求和預期響應描述,然後在測試場景中進行引用即可。

示意圖如下所示。

HttpRunner 的測試用例分層機制

具體地,我們可以約定將專案的所有API介面定義放置在api目錄下,並在api目錄中按照專案的系統模組來組織介面的定義;同時,將測試場景放置到testcases目錄中。

此時測試用例檔案的目錄結構如下所示:

✗ tree tests
tests
├── api
│   └── v1
│       ├── Account.yml
│       ├── BusinessTrip.yml
│       ├── Common.yml
│       └── Leave.yml
├── debugtalk.py
└── testcases
    ├── scenario_A.yml
    ├── scenario_B.yml
    └── scenario_C.yml
複製程式碼

而對於API介面的定義,與之前的描述方式基本一致,只做了兩點調整:

  • 介面定義塊(block)的標識為api
  • 介面定義塊中包含def欄位,形式為api_name(*args),作為介面的唯一標識ID;需要注意的是,即使api沒有引數,也需要帶上括號,api_name();這和程式語言中定義函式是一樣的。
- api:
    def: api_v1_Account_Login_POST($UserName, $Password)
    request:
        url: /api/v1/Account/Login
        method: POST
        json:
            UserName: $UserName
            Pwd: $Password
            VerCode: ""
    validators:
        - eq: ["status_code", 200]
        - eq: ["content.IsSuccess", True]
        - eq: ["content.Code", 200]

- api:
    def: api_v1_Account_LoginOff_GET()
    request:
        url: /api/v1/Account/LoginOff
        method: GET
    validators:
        - eq: ["status_code", 200]
        - eq: ["content.IsSuccess", True]
        - eq: ["content.Code", 200]
複製程式碼

有了介面的定義描述後,我們編寫測試場景時就可以直接引用介面定義了。

同樣是背景描述中的登入登出場景,測試用例就描述為變為如下形式。

- config:
    name: demo
    variable_binds:
        - UserName: test001
        - Password: 123456
    request:
        base_url: http://xxx.debugtalk.com
        headers:
            Accept: application/json
            User-Agent: iOS/10.3

- test:
    name: Login
    api: api_v1_Account_Login_POST($UserName, $Password)

- test:
    name: Logoff
    api: api_v1_Account_LoginOff_GET()
複製程式碼

不難看出,對API介面進行分層定義後,我們在測試用例場景中引用介面定義時,與程式語言裡面呼叫函式的形式基本完全一樣,只需要指定介面的名稱,以及所需傳遞的引數值;同樣的,即使沒有引數,也需要帶上括號。

實現介面的分層定義描述後,我們就可以避免介面的重複定義。但是,我們回過頭來看之前的案例,發現仍然會存在一定的重複。

HttpRunner 的測試用例分層機制

如上圖所示,場景A和場景C都包含了註冊新賬號(API_1/2)和檢視登入狀態(API_6),場景A和場景B都包含了登入已有賬號(API_3/4/5),場景B和場景C都包含了登出登入(API_7/8)。

雖然我們已經將介面的定義描述抽離出來,避免了重複的定義;但是在實際業務場景中,某些功能(例如登入、登出)會在多個場景中重複出現,而該功能又涉及到多個介面的組合呼叫,這同樣也會出現大量的重複。

介面的模組化封裝

玩過積木的同學可能就會想到,我們也可以將系統的常用功能封裝為模組(suite),只需要在模組中定義一次,然後就可以在測試場景中重複進行引用,從而避免了模組功能的重複描述。

HttpRunner 的測試用例分層機制

具體地,我們可以約定將專案的所有模組定義放置在suite目錄下,並在suite目錄中按照專案的功能來組織模組的定義。

後續,我們在testcases目錄中描述測試場景時,就可同時引用介面定義和模組定義了;模組和介面的混合呼叫,必將為我們編寫測試場景帶來極大的靈活性。

此時測試用例檔案的目錄結構如下所示:

✗ tree tests
tests
├── api
│   └── v1
│       ├── Account.yml
│       ├── BusinessTrip.yml
│       ├── Common.yml
│       └── Leave.yml
├── debugtalk.py
├── suite
│   ├── BusinessTravelApplication
│   │   ├── approve-application.yml
│   │   ├── executive-application.yml
│   │   ├── reject-application.yml
│   │   └── submit-application.yml
│   └── LeaveApplication
│       ├── approve.yml
│       ├── cancel.yml
│       └── submit-application.yml
└── testcases
    ├── scenario_A.yml
    ├── scenario_B.yml
    └── scenario_C.yml
複製程式碼

需要注意的是,我們在組織測試用例描述的檔案目錄結構時,遵循約定大於配置的原則:

  • API介面定義必須放置在api目錄下
  • 模組定義必須放置在suite目錄下
  • 測試場景檔案必須放置在testcases目錄下
  • 相關的函式定義放置在debugtalk.py

至此,我們實現了測試用例的介面-模組-場景分層,從而徹底避免了重複定義描述。

腳手架工具

得益於約定大於配置的原則,在HttpRunner中實現了一個腳手架工具,可以快速建立專案的目錄結構。該想法來源於Djangodjango-admin.py startproject project_name

使用方式也與Django類似,只需要通過--startproject指定新專案的名稱即可。

$ hrun --startproject helloworld
INFO:root: Start to create new project: /Users/Leo/MyProjects/helloworld
INFO:root:      created folder: /Users/Leo/MyProjects/helloworld
INFO:root:      created folder: /Users/Leo/MyProjects/helloworld/tests
INFO:root:      created folder: /Users/Leo/MyProjects/helloworld/tests/api
INFO:root:      created folder: /Users/Leo/MyProjects/helloworld/tests/suite
INFO:root:      created folder: /Users/Leo/MyProjects/helloworld/tests/testcases
INFO:root:      created file: /Users/Leo/MyProjects/helloworld/tests/debugtalk.py
複製程式碼

執行之後,就會在指定的目錄中生成新專案的目錄結構,接下來,我們就可以按照測試用例的介面-模組-場景分層原則往裡面新增用例描述資訊了。

總結

如果看到這裡你還不明白測試用例分層的必要性,那也沒關係,測試用例分層不是必須的,你還是可以按照之前的方式組織測試用例。不過當你某一天發現需要進行分層管理時,你會發現它就在那裡,很實用。

最後,在HttpRunner專案的examples/HelloWorld目錄中,包含了一份完整的分層測試用例示例,相信會對大家有所幫助。

相關文章