前言
unittest單元測試框架使用DDT進行數據驅動測試,那么身為功能更加強大且更加靈活的Pytest框架怎么可能沒有數據驅動的概念呢?其實Pytest是使用@pytest.mark.parametrize裝飾器來實現數據驅動測試的,那么今天我們就簡單來說說在它是如何進行數據驅動測試的
裝飾測試類
""" ------------------------------------ @Time : 2019/7/25 19:18 @Auth : linux超 @File : test_parametrize.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ import pytest data_1 = [ (1, 2, 3), (4, 5, 9) ] def add(a, b): return a + b @pytest.mark.parametrize('a, b, expect', data_1) class TestParametrize(object): def test_parametrize_1(self, a, b, expect): print('\n測試函數1測試數據為\n{}-{}'.format(a, b)) assert add(a, b) == expect def test_parametrize_2(self, a, b, expect): print('\n測試函數2數據為\n{}-{}'.format(a, b)) assert add(a, b) == expect if __name__ == '__main__': pytest.main(['-sv'])
輸出
collecting ... collected 4 items test_parametrize.py::TestParametrize::test_parametrize_1[1-2-3] 測試函數1測試數據為 1-2 PASSED test_parametrize.py::TestParametrize::test_parametrize_1[4-5-9] 測試函數1測試數據為 4-5 PASSED test_parametrize.py::TestParametrize::test_parametrize_2[1-2-3] 測試函數2數據為 1-2 PASSED test_parametrize.py::TestParametrize::test_parametrize_2[4-5-9] 測試函數2數據為 4-5 PASSED ========================== 4 passed in 0.21 seconds =========================== Process finished with exit code 0
說明
當裝飾器裝飾測試類時,給數據集合會被傳遞給給類的所有方法
裝飾測試函數
單個數據
""" ------------------------------------ @Time : 2019/7/25 19:18 @Auth : linux超 @File : test_parametrize.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ import pytest data = [1, 2] @pytest.mark.parametrize('a', data) def test_parametrize(a): print('\n被加載測試數據為\n{}'.format(a)) if __name__ == '__main__': pytest.main(['-s'])
輸出
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-4.3.1, py-1.8.0, pluggy-0.9.0 rootdir: E:\CnblogCode\pytest_parametrize, inifile: plugins: rerunfailures-7.0, metadata-1.8.0, html-1.20.0 collected 2 items test_parametrize.py 被加載測試數據為 1 . 被加載測試數據為 2 . ========================== 2 passed in 0.16 seconds =========================== Process finished with exit code 0
說明
當測試用例只需要一個參數時,我們存放數據的列表無序嵌套序列,@pytest.mark.parametrize('a', data)裝飾器的第一個參數也只需要一個變量接收列表中的每個元素,第二個參數傳遞存儲數據的列表,那么測試用例需要使用同名的字符串接收測試數據(實例中的a)且列表有多少個元素就會生成並執行多少個測試用例
一組數據
""" ------------------------------------ @Time : 2019/7/25 19:18 @Auth : linux超 @File : test_parametrize.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ import pytest data = [ [1, 2, 3], [4, 5, 9] ] # 列表嵌套列表 # data_tuple = [ # (1, 2, 3), # (4, 5, 9) # ] # 列表嵌套元組 @pytest.mark.parametrize('a, b, expect', data) def test_parametrize_1(a, b, expect): # 一個參數接收一個數據 print('\n測試數據為\n{},{},{}'.format(a, b, expect)) actual = a + b assert actual == expect @pytest.mark.parametrize('value', data) def test_parametrize_2(value): # 一個參數接收一組數據 print('\n測試數據為\n{}'.format(value)) actual = value[0] + value[1] assert actual == value[2] if __name__ == '__main__': pytest.main(['-s'])
輸出
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-4.3.1, py-1.8.0, pluggy-0.9.0 rootdir: E:\CnblogCode\pytest_parametrize, inifile: plugins: rerunfailures-7.0, metadata-1.8.0, html-1.20.0 collected 4 items test_parametrize.py 測試數據為 1,2,3 . 測試數據為 4,5,9 . 測試數據為 [1, 2, 3] . 測試數據為 [4, 5, 9] . ========================== 4 passed in 0.17 seconds =========================== Process finished with exit code 0
說明
當測試用例需要多個數據時,我們可以使用嵌套序列(嵌套元組&嵌套列表)的列表來存放測試數據
裝飾器@pytest.mark.parametrize()可以使用單個變量接收數據,也可以使用多個變量接收,同樣,測試用例函數也需要與其保持一致
當使用單個變量接收時,測試數據傳遞到測試函數內部時為列表中的每一個元素或者小列表,需要使用索引的方式取得每個數據
當使用多個變量接收數據時,那么每個變量分別接收小列表或元組中的每個元素
列表嵌套多少個多組小列表或元組,測生成多少條測試用例
圖解對應關系
組合數據
""" ------------------------------------ @Time : 2019/7/25 19:18 @Auth : linux超 @File : test_parametrize.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ import pytest data_1 = [1, 2] data_2 = [3, 4] @pytest.mark.parametrize('a', data_1) @pytest.mark.parametrize('b', data_2) def test_parametrize_1(a, b): print('\n測試數據為\n{},{}'.format(a, b)) if __name__ == '__main__': pytest.main(['-s'])
輸出
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-4.3.1, py-1.8.0, pluggy-0.9.0 rootdir: E:\CnblogCode\pytest_parametrize, inifile: plugins: rerunfailures-7.0, metadata-1.8.0, html-1.20.0 collected 4 items test_parametrize.py 測試數據為 1,3 . 測試數據為 2,3 . 測試數據為 1,4 . 測試數據為 2,4 . ========================== 4 passed in 0.24 seconds =========================== Process finished with exit code 0
說明
通過測試結果,我們不難分析,一個測試函數還可以同時被多個參數化裝飾器裝飾,那么多個裝飾器中的數據會進行交叉組合的方式傳遞給測試函數,進而生成n*n個測試用例,這也為我們的測試設計時提供了方便
標記用例
可以直接標記測試用例,參數化裝飾器也可以識別(標記用例失敗或跳過)
標記為無條件跳過(標記為失敗為xfail,自己嘗試)
""" ------------------------------------ @Time : 2019/7/25 19:18 @Auth : linux超 @File : test_parametrize.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ import pytest data_1 = [ [1, 2, 3], pytest.param(3, 4, 8, marks=pytest.mark.skip) ] def add(a, b): return a + b @pytest.mark.parametrize('a, b, expect', data_1) def test_parametrize_1(a, b, expect): print('\n測試數據為\n{},{}'.format(a, b)) assert add(a, b) == expect if __name__ == '__main__': pytest.main(['-vs'])
輸出
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-4.3.1, py-1.8.0, pluggy-0.9.0 -- C:\Programs\Python\Python37-32\python.exe cachedir: .pytest_cache metadata: {'Python': '3.7.2', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'pytest': '4.3.1', 'py': '1.8.0', 'pluggy': '0.9.0'}, 'Plugins': {'rerunfailures': '7.0', 'metadata': '1.8.0', 'html': '1.20.0'}, 'JAVA_HOME': 'D:\\JDK'} rootdir: E:\CnblogCode\pytest_parametrize, inifile: plugins: rerunfailures-7.0, metadata-1.8.0, html-1.20.0 collecting ... collected 2 items test_parametrize.py::test_parametrize_1[1-2-3] 測試數據為 1,2 PASSED test_parametrize.py::test_parametrize_1[3-4-8] SKIPPED ===================== 1 passed, 1 skipped in 0.17 seconds ===================== Process finished with exit code 0
說明
輸出結果顯示收集到2個用例,一個通過,一個被跳過,當我們不想執行某組測試數據時,我們可以標記skip或skipif;當我們預期某組數據會執行失敗時,我們可以標記為xfail等
嵌套字典
""" ------------------------------------ @Time : 2019/7/25 19:18 @Auth : linux超 @File : test_parametrize.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ import pytest data_1 = ( { 'user': 1, 'pwd': 2 }, { 'user': 3, 'pwd': 4 } ) @pytest.mark.parametrize('dic', data_1) def test_parametrize_1(dic): print('\n測試數據為\n{}'.format(dic)) if __name__ == '__main__': pytest.main(['-s'])
輸出
============================= test session starts ============================= platform win32 -- Python 3.7.2, pytest-4.3.1, py-1.8.0, pluggy-0.9.0 rootdir: E:\CnblogCode\pytest_parametrize, inifile: plugins: rerunfailures-7.0, metadata-1.8.0, html-1.20.0 collected 2 items test_parametrize.py 測試數據為 {'user': 1, 'pwd': 2} . 測試數據為 {'user': 3, 'pwd': 4} . ========================== 2 passed in 0.20 seconds =========================== Process finished with exit code 0
增加可讀性
使用ids參數
參數化裝飾器有一個額外的參數ids,可以標識每一個測試用例,自定義測試數據結果的顯示,為了增加可讀性,我們可以標記每一個測試用例使用的測試數據是什么,適當的增加一些說明
在使用前你需要知道,ids參數應該是一個字符串列表,必須和數據對象列表的長度保持一致,我們可以試着使用ids,看下效果
""" ------------------------------------ @Time : 2019/7/25 19:18 @Auth : linux超 @File : test_parametrize.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ import pytest data_1 = [ (1, 2, 3), (4, 5, 9) ] ids = ["a:{} + b:{} = expect:{}".format(a, b, expect) for a, b, expect in data_1] def add(a, b): return a + b @pytest.mark.parametrize('a, b, expect', data_1, ids=ids) class TestParametrize(object): def test_parametrize_1(self, a, b, expect): print('\n測試函數1測試數據為\n{}-{}'.format(a, b)) assert add(a, b) == expect def test_parametrize_2(self, a, b, expect): print('\n測試函數2數據為\n{}-{}'.format(a, b)) assert add(a, b) == expect if __name__ == '__main__': pytest.main(['-v']) # -v : 更加詳細的輸出測試結果
輸出
裝飾器不傳遞ids參數的輸出
collecting ... collected 4 items test_parametrize.py::TestParametrize::test_parametrize_1[1-2-3] PASSED [ 25%] test_parametrize.py::TestParametrize::test_parametrize_1[4-5-9] PASSED [ 50%] test_parametrize.py::TestParametrize::test_parametrize_2[1-2-3] PASSED [ 75%] test_parametrize.py::TestParametrize::test_parametrize_2[4-5-9] PASSED [100%] ========================== 4 passed in 0.16 seconds =========================== Process finished with exit code 0
裝飾器傳遞ids參數的輸出
collecting ... collected 4 items test_parametrize.py::TestParametrize::test_parametrize_1[a:1 + b:2 = expect:3] PASSED [ 25%] test_parametrize.py::TestParametrize::test_parametrize_1[a:4 + b:5 = expect:9] PASSED [ 50%] test_parametrize.py::TestParametrize::test_parametrize_2[a:1 + b:2 = expect:3] PASSED [ 75%] test_parametrize.py::TestParametrize::test_parametrize_2[a:4 + b:5 = expect:9] PASSED [100%] ========================== 4 passed in 0.20 seconds =========================== Process finished with exit code 0
說明
執行命令我使用了-v,會更加詳細的顯示輸出結果,可以看到所有的數據結果中的用例都被一個列表明確的標記了,而且通過這種標記可以更加直觀的看出來,每個測試用例使用的數據名稱及測試內容
自定義id做標識
除了使用ids參數增加輸出可讀性外,我們還可以在參數列表的參數旁邊定義一個id值來做標識,看下面實例
""" ------------------------------------ @Time : 2019/7/25 19:18 @Auth : linux超 @File : test_parametrize.py @IDE : PyCharm @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error! @QQ : 28174043@qq.com @GROUP: 878565760 ------------------------------------ """ import pytest data_1 = [ pytest.param(1, 2, 3, id="(a+b):pass"), # id的值可以自定義, 只要方便理解每個用例是干什么的即可 pytest.param(4, 5, 10, id="(a+b):fail") ] def add(a, b): return a + b class TestParametrize(object): @pytest.mark.parametrize('a, b, expect', data_1) def test_parametrize_1(self, a, b, expect): assert add(a, b) == expect if __name__ == '__main__': pytest.main(['-v'])
輸出
test_parametrize.py::TestParametrize::test_parametrize_1[(a+b):pass] PASSED [ 50%] test_parametrize.py::TestParametrize::test_parametrize_1[(a+b):fail] FAILED [100%] ================================== FAILURES =================================== _______________ TestParametrize.test_parametrize_1[(a+b):fail] ________________ self = <pytest_parametrize.test_parametrize.TestParametrize object at 0x000001D7BFC4C748> a = 4, b = 5, expect = 10 @pytest.mark.parametrize('a, b, expect', data_1) def test_parametrize_1(self, a, b, expect): > assert add(a, b) == expect E assert 9 == 10 E -9 E +10 test_parametrize.py:28: AssertionError ===================== 1 failed, 1 passed in 0.35 seconds ====================== Process finished with exit code 0
說明
如果使用此方法來標記測試用例,一定要嚴格按照我寫的格式來使用,語法為pytest.param(value, id='somthing')
總結
Pytest中實現數據驅動就是如此了
掌握
1.裝飾器與測試用例使用單個變量接收多組數據與多個變量接收多個數據的訪問方法
2.不同測試數據形式(列表嵌套元組,列表,字典等)時,如何傳遞數據及訪問數據
3.裝飾器裝飾測試類和測試函數的不同之處:裝飾測試類時,類內所有的方法必須接送測試數據,否則會報錯,裝飾測試函數時比較靈活,如果函數不使用數據就可以不裝飾
4.為了輸出結果的可讀性,可以選擇使用ids參數,與測試數據中定義id參數值來標識測試用例
注意
1. 裝飾器的第一個參數是一個列表形式的字符串參數"a, b, c" 不能寫成"a", "b", "c"
2. ids是個字符串列表,它的長度需要與測試數據列表的長度一致