1. 程式人生 > 其它 >自動化測試必會—資料驅動DDT

自動化測試必會—資料驅動DDT

技術標籤:自動化測試

引言

  你是否有過這種感受,在做自動化測試過程中,不論是API 自動化測試還是UI 自動化測試,我們寫測試指令碼有很大一部分時間都是在準備資料(setUp)、清理資料(tearDown)。因為資料是做自動化測試的至關重要的一個環節。如此看來資料驅動真的十分重要。接下來分享的內容是:Unittest測試框架中常用的資料驅動框架:DDT 。

資料驅動

1、資料驅動是什麼?

  資料驅動,指在自動化測試中處理測試資料的方式。

  通常測試資料與功能函式分離,儲存在功能函式的外部位置。在自動化測試執行時,資料驅動框架會讀取資料來源中的資料,把資料作為引數傳遞到功能函式中,並會根據資料的條數多次運行同一個功能函式

  資料驅動的資料來源可以是函式外的資料集合、CSV 檔案、Excel 表格、TXT 檔案,以及資料庫等。

2、資料驅動的優點?

  (1)、減少重複程式碼
  通過以下例項來看下資料驅動是如何減少重複程式碼的。

def test_001(user, pwd, num):
    # 實際函式邏輯
    pass

# 如果沒有資料驅動,你的程式碼可能會是這樣的:
test_001('one', 'test_one', 1)
test_001('two', 'test_two', 2)
test_001('three', 'test_three', 3)

  如果不使用資料驅動時,並且同一個功能函式存在多個測試資料,你只能多次呼叫這個功能函式;另外一旦某一個測試資料有更改/刪除,你需要在函式呼叫裡去更改相應的測試資料,非常不方便。

  如果使用測試驅動時,你的程式碼可能會是這樣的:

# origin_data指向一個檔案,這個檔案裡儲存有你所有的測試資料。
origin_data = './tests/data/testdata.csv'

# dataDrivenDecorator是你實現資料驅動的裝飾器

@dataDrivenDecorator(origin_data)
def test_001(user, pwd, num):
    # 實際函式邏輯
    pass

  這種情況下, 你無須進行多次呼叫,而且當你的測試資料發生改變時, 你僅需要更改資料來源檔案的資料就可以了。

  (2)、資料所屬的測試用例失敗,不會影響到其他測試資料對應的測試用例


  通過以下例項來看下是怎麼不會影響到其他測試資料對應的測試用例的。
  如果不使用資料驅動之前,假設有以下一個函式:

test_data = [0, 1, 0, 1]
def test_001(data):
    for x in data:
        assert x > 0

test_001(test_data)

  

執行結果,如下圖所示:
圖片

  由執行結果可以看出,因為test_data 的第一個值是0, 它不大於0。所以斷言失敗,所有 test_data測試資料集中0後面的測試資料都沒有執行。

  如果有了資料驅動,則資料驅動會把這一個測試按照測試資料分解成多個測試,所以第一個測試資料失敗也不會影響到後面的測試結果。

3、Python 中使用廣泛的資料驅動框架有哪些?

  • DDT(Data-Driven Tests),通常結合Unittest 使用

  • parameterized,是Pytest 實現資料驅動的常用框架

DDT 包含哪些裝飾器

1 個類裝飾器

  ddt 這個類裝飾器必須裝飾在TestCase 的子類上,TestCase 是Unittest 框架中的一個基類,它實現了Test Runner 驅動測試執行所需的介面(interface)。

2 個方法裝飾器

  分別是:data 和 file_data。
  data 裝飾器,直接提供測試資料;
  file_data 裝飾器則從 JSON 或 YAML 檔案載入測試資料。

DDT 的使用步驟如下:

  • 使用@ddt 裝飾你的測試類;

  • 使用@data 或者@file_data 裝飾你需要資料驅動的測試方法;

  • 如一組測試資料有多個引數,則需unpack,使用@unpack 裝飾你的測試方法。

Python 安裝DDT :

  安裝命令:pip install ddt或python -m pip install ddt

DDT 的使用

(1)、ddt 直接提供資料

from ddt import ddt, data, file_data, unpack
from selenium import webdriver
import unittest
import time

# ddt一定是裝飾在TestCase的子類上

@ddt
class Baidu(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(30)
        self.base_url = "http://www.baidu.com/"

    # data表示測試資料是直接提供的。
    # unpack表示,對於每一組資料,如果它的值是list或者tuple,那麼就分拆成獨立的引數。

    @data(['Testing', 'Testing'], ['hello_world.com','Testing'])
    @unpack
    def test_baidu_search(self, search_string, expect_string):
        driver = self.driver
        driver.get(self.base_url + "/")
        driver.find_element_by_id("kw").send_keys(search_string)
        driver.find_element_by_id("su").click()
        time.sleep(2)
        search_results = driver.find_element_by_xpath('//*[@id="1"]/h3/a').get_attribute('innerHTML')
        print(search_results)
        self.assertEqual(expect_string in search_results, True)

    def tearDown(self):
        self.driver.quit()

if __name__ == "__main__":
    unittest.main(verbosity=2)

  在這個例子中,我直接使用了@data 裝飾器。在這個裝飾器中,我給出了測試的2 組資料,分別是 ['Testing', 'Testing'] 和 ['hello_world.com', 'Testing'];然後我使用 @unpack 裝飾器把每一組資料的資料unpack 成一個個的引數傳給我的函式 test_baidu_search。

  當你執行上面程式碼,從結果中會發現雖然我們只有一個測試用例test_baidu_search。但在生成的測試報告裡,顯示“Run 2 tests in XX”,也就是test_baidu_search 運行了 2 次,這就是DDT 在起作用。

  這是多組引數,每組多個數據的情況,如果每組僅有一個數據呢?你僅需要更改如下:

# 如僅有一個引數,那麼直接在data裡寫引數就好。
# 僅有一個引數的情況下,無須再用@unpack裝飾測試方法。
@data('data1', 'data2')

(2)、ddt 使用函式提供資料
  ddt 直接提供資料,除去上述的直接把資料寫在@data() 的引數中外,還有一個情況,即資料先從函式獲取,然後再寫入@data() 的引數中。

from ddt import ddt, data, file_data, unpack
from selenium import webdriver
import unittest
import time

def get_test_data():
    # 這裡寫你獲取測試資料的業務邏輯。

    # 獲取到後,把資料返回即可。

    # 注意,如果多組資料,需要返回類似([資料1-引數1, 資料1-引數2],[資料2-引數1, 資料2-引數2])這樣的格式,方便ddt.data()解析

    # 解析後返回的資料格式如下:
    results = (['Testing', 'Testing'], ['hello_world.com', 'Testing'])
    return results

# ddt一定是裝飾在TestCase的子類上

@ddt
class Baidu(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(30)
        self.base_url = "http://www.baidu.com/"

    # data表示data是直接提供的。注意data裡的引數我寫了函式get_test_data()的返回值,並且以*為字首,代表返回的是可變引數。

    # unpack表示,對於每一組資料,如果它的值是list或者tuple,那麼就分拆成獨立的引數。

    @data(*get_test_data())
    @unpack
    def test_baidu_search(self, search_string, expect_string):
        driver = self.driver
        driver.get(self.base_url + "/")
        driver.find_element_by_id("kw").send_keys(search_string)
        driver.find_element_by_id("su").click()
        time.sleep(2)
        search_results = driver.find_element_by_xpath('//*[@id="1"]/h3/a').get_attribute('innerHTML')
        print(search_results)
        self.assertEqual(expect_string in search_results, True)

    def tearDown(self):
        self.driver.quit()

if __name__ == "__main__":
    unittest.main(verbosity=2)

  在上述示例中,我建立了一個函式get_test_data() 用於獲取我的測試資料。這個函式可以帶引數,也可以不帶引數,具體需要根據你的業務邏輯來。

  注意:get_test_data() 的返回值,一定需要遵守ddt.data() 可接受的資料格式。

即:一組資料,每個資料為單個的值;多組資料,每組資料為一個列表或者一個字典。

(3)、ddt 使用檔案提供資料:JSON 和 YAML
  除了使用 @ddt 直接提供資料,DDT 還支援通過檔案載入資料。

  不過預設只支援兩種格式 YAML 和 JSON,只有以“.yml” 或者“.yaml” 結尾的會被認作 YAML 檔案,其他格式都將被認為是 JSON 檔案。

  如果把上述用例改成使用 JSON 檔案,則我們的用例看起來是這樣的:
圖片

  

首先,我們建立一個跟 test_baidu.py 同名的檔案 test_baidu.json,內容如下:

{ "case1": {

  "search_string": "testing",

  "expect_string": "Testing"

  },

  "case2": {

  "search_string": "testing",

  "expect_string": "Testing"

  }

}

然後更新 test_baidu.py,更新後的程式碼如下所示:

from ddt import ddt, data, file_data, unpack
from selenium import webdriver
import unittest
import time

@ddt
class Baidu(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(30)
        self.base_url = "http://www.baidu.com/"

    # 此處測試資料從檔案讀取,使用@file_data裝飾器

    # 檔案路徑是相對於Baidu這個測試類的相對路徑

    # 使用外部檔案方式Load資料無須使用unpack

    @file_data('test_baidu.json')
    def test_baidu_search(self, search_string, expect_string):
        driver = self.driver
        driver.get(self.base_url + "/")
        driver.find_element_by_id("kw").send_keys(search_string)
        driver.find_element_by_id("su").click()
        time.sleep(2)
        search_results = driver.find_element_by_xpath('//*[@id="1"]/h3/a').get_attribute('innerHTML')
        print(search_results)
        self.assertEqual(expect_string in search_results, True)

    def tearDown(self):
        self.driver.quit()

if __name__ == "__main__":
    unittest.main(verbosity=2)

可以看到,使用 @file_data 這個裝飾器,與使用 @data 的裝飾器有一點不同:

  • @file_data 這個裝飾器裡,檔案的路徑是相對於這個測試類本身來說的。在本例中為 Baidu 這個測試類所處的檔案的相對位置;

  • 使用 @file_data 無須使用 unpack,即使同一組資料的引數有多個。

如果把上述示例使用YAML 檔案,則需要注意以下幾點:

  • 如果想在 python 中使用 yaml 檔案,則需要安裝 PyYAML。

  • 安裝命令:pip install pyyaml 或python -m pip install pyyaml

安裝好後,我們在test_baidu.json 的同級目錄下,建立一個檔案test_baidu.yaml 檔案,內容如下:

"case1":

  "search_string": "testing"

  "expect_string": "Testing"

"case2": 

  "search_string": "testing"

  "expect_string": "Testing"

然後,我們更改 test_baidu.py,更改後的內容如下:

from ddt import ddt, data, file_data, unpack
from selenium import webdriver
import unittest
import time

# 使用yaml檔案前先嚐試匯入,匯入失敗則將skip使用yaml資料驅動的測試用例

try:
    import yaml
except ImportError:
    have_yaml_support = False
else:
    have_yaml_support = True
    needs_yaml = unittest.skipUnless(
    have_yaml_support, "Need YAML to run this test"
)

@ddt
class Baidu(unittest.TestCase):
    def setUp(self):
        self.driver = webdriver.Chrome()
        self.driver.implicitly_wait(30)
        self.base_url = "http://www.baidu.com/"

    # 使用yaml檔案必須使用@needs_yaml裝飾

    @needs_yaml
    @file_data('test_baidu.yaml')
    def test_baidu_search(self, search_string, expect_string):
        driver = self.driver
        driver.get(self.base_url + "/")
        driver.find_element_by_id("kw").send_keys(search_string)
        driver.find_element_by_id("su").click()
        time.sleep(2)
        search_results = driver.find_element_by_xpath('//*[@id="1"]/h3/a').get_attribute('innerHTML')
        print(search_results)
        self.assertEqual(expect_string in search_results, True)

    def tearDown(self):
        self.driver.quit()

if __name__ == "__main__":
    unittest.main(verbosity=2)

  你可以看到,與使用 JSON 檔案不同, 使用 YAML 檔案必須要先安裝 PyYaml。然後為了防止 yaml 匯入失敗,我定義了 needs_yaml 這個裝飾器,用來給我的程式加個安全判斷。

如果匯入失敗,則所有以 needs_yaml 裝飾的測試用例將不會執行。

(4)、ddt 使用檔案提供資料:其他格式資料檔案
  因為 ddt 預設只支援 JSON 和 YAML 格式的資料。但是我想使用其他資料格式怎麼辦?

  常用的方式有如下兩種:

  • 先讀取其他格式的檔案(例如 Excel 格式),然後建立 ddt 支援的 JSON 或者 YAML 檔案,最後把獲取到的資料寫入這個檔案,再使用 @file_data() 即可;

  • 建立一個函式,在函式中讀取其他格式的檔案並獲取資料,將資料直接返回為 @ddt.data() 支援的格式呼叫即可。

歡迎關注【無量測試之道】公眾號,回覆【領取資源】,
Python程式設計學習資源乾貨、
Python+Appium框架APP的UI自動化、
Python+Selenium框架Web的UI自動化、
Python+Unittest框架API自動化、

資源和程式碼 免費送啦~
文章下方有公眾號二維碼,可直接微信掃一掃關注即可。

備註:我的個人公眾號已正式開通,致力於測試技術的分享,包含:大資料測試、功能測試,測試開發,API介面自動化、測試運維、UI自動化測試等,微信搜尋公眾號:“無量測試之道”,或掃描下方二維碼:

新增關注,讓我們一起共同成長!