HttpRunner 通過 skip 機制實現對測試用例的分組執行控制

背景介紹

近期,某位同學對HttpRunner提了一個需求點:

能否支持類似unittest中的skip註解,方便靈活剔除某些用例,不執行。

目前在介面測試日常構建中,會遇到一些介面開發暫時屏蔽了或者降級,導致用例執行失敗;所以想當遇到這些情況的時候,能夠臨時剔除掉某些用例不執行;等後續恢復後,再去掉,然後恢復執行。

針對這種情況,HttpRunner的確沒有直接支持。之所以說是沒有直接支持,是因為在HttpRunner中存在times關鍵字,可以指定某個test的運行次數。

例如,如下test中指定了times為3,那麼該test就會運行3次。

- test: name: demo times: 3 request: {...} validate: [...]

假如要實現臨時屏蔽掉某些test,那麼就可以將對應testtimes設置為0。

這雖然也能勉強實現需求,但是這跟直接將臨時不運行的test注釋掉沒什麼區別,都需要對測試用例內容進行改動,使用上很是不方便。

考慮到該需求的普遍性,HttpRunner的確應該增加對該種情況的支持。

在這方面,unittest已經有了清晰的定義,有三種常用的裝飾器可以控制單元測試用例是否被執行:

  • a href="testerhome.com/unittest">@unittest.skip(reason):無條件跳過當前測試用例
  • a href="testerhome.com/unittest">@unittest.skipIf(condition, reason):當條件表達式的值為true時跳過當前測試用例
  • a href="testerhome.com/unittest">@unittest.skipUnless(condition, reason):當條件表達式的值為false時跳過當前測試用例

該功能完全滿足我們的需求,因此,我們可以直接復用其概念,嘗試實現同樣的功能。

實現方式

目標明確了,那需要怎麼實現呢?

首先,我們先看下unittest中這三個函數是怎麼實現的;這三個函數定義在unittest/case.py中。

class SkipTest(Exception): """ Raise this exception in a test to skip it. Usually you can use TestCase.skipTest() or one of the skipping decorators instead of raising this directly. """ passdef skip(reason): """ Unconditionally skip a test. """ def decorator(test_item): if not isinstance(test_item, (type, types.ClassType)): @functools.wraps(test_item) def skip_wrapper(*args, **kwargs): raise SkipTest(reason) test_item = skip_wrapper test_item.__unittest_skip__ = True test_item.__unittest_skip_why__ = reason return test_item return decoratordef skipIf(condition, reason): """ Skip a test if the condition is true. """ if condition: return skip(reason) return _iddef skipUnless(condition, reason): """ Skip a test unless the condition is true. """ if not condition: return skip(reason) return _id

不難看出,核心有兩點:

  • 對於skip,只需要在該測試用例中raise SkipTest(reason),而SkipTestunittest/case.py中定義的一個異常類;
  • 對於skipIfskipUnless,相比於skip,主要是需要指定一個條件表達式(condition),然後根據該表達式的實際值來決定是否skip當前測試用例。

明確了這兩點之後,我們要如何在HttpRunner中實現同樣的功能,思路應該就比較清晰了。

因為HttpRunner同樣也是採用unittest來組織和驅動測試用例執行的,而具體的執行控制部分都是在httprunner/runner.py_run_test方法中;同時,在_run_test方法中會傳入testcase_dict,也就是具體測試用例的全部信息。

那麼,最簡單的做法,就是在YAML/JSON測試用例中,新增skip/skipIf/skipUnless參數,然後在_run_test方法中根據參數內容來決定是否執行raise SkipTest(reason)

例如,在YAML測試用例中,我們可以按照如下形式新增skip欄位,其中對應的值部分就是我們需要的reason

- test: name: demo skip: "skip this test unconditionally" request: {...} validate: [...]

接下來在_run_test方法,要處理就十分簡單,只需要判斷testcase_dict中是否包含skip欄位,假如包含,則執行raise SkipTest(reason)即可。

def _run_test(self, testcase_dict): ... if "skip" in testcase_dict: skip_reason = testcase_dict["skip"] raise SkipTest(skip_reason) ...

這對於skip機制來做,完全滿足需求;但對於skipIf/skipUnless,可能就會麻煩些,因為我們的用例是在YAML/JSON文本格式的文件中,沒法像在unittest中執行condition那樣的Python表達式。

嗯?誰說在YAML/JSON中就不能執行函數表達式的?在HttpRunner中,我們已經實現了該功能,即:

  • debugtalk.py中定義函數,例如func(a, b)
  • YAML/JSON中通過${func(a,b)}對函數進行調用

在此基礎上,我們要實現skipIf/skipUnless就很簡單了;很自然地,我們可以想到採用如下形式來進行描述。

- test: name: create user which existed (skip if condition) skipIf: ${skip_test_in_production_env()} request: {...} validate: [...]

其中,skip_test_in_production_env定義在debugtalk.py文件中。

def skip_test_in_production_env(): """ skip this test in production environment """ return os.environ["TEST_ENV"] == "PRODUCTION"

然後,在_run_test方法中,我們只需要判斷testcase_dict中是否包含skipIf欄位,假如包含,則將其對應的函數表達式取出,運行得到其結果,最後再根據運算結果來判斷是否執行raise SkipTest(reason)。對函數表達式進行解析的方法在httprunner/context.pyexec_content_functions函數中,具體實現方式可閱讀之前的文章。

def _run_test(self, testcase_dict): ... if "skip" in testcase_dict: skip_reason = testcase_dict["skip"] raise SkipTest(skip_reason) elif "skipIf" in testcase_dict: skip_if_condition = testcase_dict["skipIf"] if self.context.exec_content_functions(skip_if_condition): skip_reason = "{} evaluate to True".format(skip_if_condition) raise SkipTest(skip_reason) ...

skipUnlessskipIf類似,不再重複。

通過該種方式,我們就可以實現在不對測試用例文件做任何修改的情況下,通過外部方式(例如設定環境變數的值)就可以控制是否執行某些測試用例。

效果展示

skip/skipIf/skipUnless機制實現後,我們對測試用例的執行控制就更加靈活方便了。

例如,我們可以很容易地實現如下常見的測試場景:

  • 對測試用例進行分組,P0/P1/P2等,然後根據實際需求選擇執行哪些用例
  • 通過環境變數來控制是否執行某些用例

更重要的是,我們無需對測試用例文件進行任何修改。

HttpRunner項目中存在一個示例文件,httprunner/tests/data/demo_testset_cli.yml,大家可以此作為參考。

在運行該測試集後,生成的測試報告如下所示。

最後做個預告,HttpRunner在中文使用文檔方面一直比較缺失,近期將集中時間進行梳理,爭取儘快給大家一個比較系統的文檔手冊。


推薦閱讀:

【Python3網路爬蟲開發實戰】 1.3.2-Beautiful Soup的安裝
利用內存破壞實現python沙盒逃逸
從零開始寫Python爬蟲 --- 2.3 爬蟲實踐:天氣預報&數據存儲
Python-NumPy模塊的轉置軸對換(2)

TAG:軟體測試 | 開源項目 | Python |