Pytest簡單介紹
(pytest是python的一個測試架構,主要是用來進行一些小的測試)
- 安裝:pip install -U pytest
- 檢視是否安裝成功:pytest --version
- 運作:在目前檔案所在目錄下執行pytest,會尋找目前目錄以及子目錄下以test開頭的py檔案或者以test結尾的py檔案,找到檔案後,在檔案中找到以test開頭函數并執行。(或者執行pytest 檔案名--這樣可以指定某個檔案進行pytest的測試 或者 python -m pytest xxx.py)
在執行pytest xxx.py時,若增加參數:pytest -v -s xxx.py (-v:顯示運作的函數;-s:顯示内部的列印資訊)
- 編寫pytest測試樣例的規則:
- 測試檔案以test_開頭
- 測試類以Test開頭,并且不能帶有init方法
- 測試函數以test_開頭
- 斷言使用基本的assert即可
- 斷言--正常:
assert value == 0
- 斷言--異常(pytest.raise方法):
1 #斷言1除以0,将産生一個ZeroDivisionError類型的異常。
2 import pytest
3 def test_zero_division():
4 with pytest.raises(ZeroDivisionError):
5 1 / 0
6
7 #有的時候,我們可能需要在測試中用到産生的異常中的某些資訊,比如異常的類型type,異常的值value等等。下面我們修改下上面的測試:
8 import pytest
9 def test_recursion_depth():
10 with pytest.raises(ZeroDivisionError) as excinfo:
11 1/0
12 assert excinfo.type == 'RuntimeError'
13 #因為該測試斷言産生的異常類型是RuntimeError,而實際上産生的異常類型是ZeroDivisionError,是以測試失敗了。在測試結果中,可以看到assert子表達式excinfo.type的值。
- 在test前,可以通過增加skip或xfail進行跳過(失敗)測試案例
1 import pytest
2
3 # @pytest.mark.skipif() 跳過
4 # @pytest.mark.skip() 跳過
5 # @pytest.mark.xfail() 若出錯跳過,pass也跳過,但會顯示pass
6 def test_123():
7 # pytest.skip() 也可以實作'跳過'目的
8 assert
- 簡單例子:
1 # coding:utf-8
2 import pytest
3
4 def fun(x):
5 return x + 1
6
7 def test_fun():
8 assert
結果:
(venvX) F:\test_jiaxin>pytest test_para1.py
============================= test session starts =============================
platform win32 -- Python 2.7.9, pytest-3.7.1, py-1.5.3, pluggy-0.7.1
rootdir: F:\test_jiaxin, inifile:
plugins: allure-adaptor-1.7.10
collected 1 item
test_para1.py . [100%]
========================== 1 passed in 0.09 seconds ===========================
pytest之feature-scope
- function:每個test都運作,預設是function的scope.
- class:每個class的所有test隻運作一次.
- module:每個module的所有test隻運作一次.
- session:每個session隻運作一次.
比如你的所有test都需要連接配接同一個資料庫,那可以設定為module,隻需要連接配接一次資料庫,對于module内的所有test,這樣可以極大的提高運作效率。
代碼:
1 @pytest.fixture(scope="module")
2 def hero_backup_policy(self, acmp_cfg):
3 return AcloudBackupPolicy(acmp_cfg)
4
5 @pytest.fixture(scope="function")
6 def hero_acloud_backup_policy(self, acmp_cfg):
7 return
pytest的參數化方式
- pytest.fixture()方式進行參數化,fixture裝飾的函數可以作為參數傳入其他函數
- conftest.py 檔案中存放參數化函數,可作用于子產品内的所有測試用例
- pytest.mark.parametrize()方式進行參數化
待測試代碼片段:(is_leap_year.py)
1 # coding:utf-8
2 def is_leap_year(year):
3 # 先判斷year是不是整型
4 if isinstance(year, int) is not True:
5 raise TypeError("傳入的參數不是整數")
6 elif year == 0:
7 raise ValueError("公元元年是從公元一年開始!!")
8 elif abs(year) != year:
9 raise ValueError("傳入的參數不是正整數")
10 elif (year % 4 == 0 and year % 100 != 0) or year % 400 == 0:
11 print"%d年是閏年" % year
12 return True
13 else:
14 print"%d年不是閏年" % year
15 return
pytest.fixture()
- pytest.fixture()中傳入的參數為list,用例執行時,周遊list中的值,每傳入一次值,則相當于執行一次用例。
- @pytest.fixture()裝飾的函數中,傳入了一個參數為request。
- 這裡的測試資料是直接存在list中的,能否存入json檔案或者xml檔案再進行讀取轉換為list呢?
代碼:
1 # coding:utf-8
2 import is_leap_year
3 import pytest
4
5 class TestPara():
6 is_leap = [4, 40, 400, 800, 1996, 2996]
7 is_not_leap = [1, 100, 500, 1000, 1999, 3000]
8 is_valueerror = [0, -4, -100, -400, -1996, -2000]
9 is_typeerror = ['-4', '4', '100', 'ins', '**', '中文']
10
11 @pytest.fixture(params=is_leap)
12 def is_leap(self, request):
13 return request.param
14
15 @pytest.fixture(params=is_typeerror)
16 def is_typeerror(self, request):
17 return request.param
18
19 def test_is_leap(self, is_leap):
20 assert is_leap_year.is_leap_year(is_leap) == True
21
22 def test_is_typeerror(self, is_typeerror):
23 with pytest.raises(TypeError):
24
conftest.py 檔案 -- 測試資料與用例分離
- 采用conftest.py檔案存儲參數化資料和函數,子產品下的用例執行時,會自動讀取conftest.py檔案中的資料。
代碼:
conftest.py檔案
1 # coding:utf-8
2 import pytest
3
4 is_leap = [4, 40, 400, 800, 1996, 2996]
5 is_not_leap = [1, 100, 500, 1000, 1999, 3000]
6 is_valueerror = [0, -4, -100, -400, -1996, -2000]
7 is_typeerror = ['-4', '4', '100', 'ins', '**', '中文']
8
9 @pytest.fixture(params=is_leap)
10 def is_leap(request):
11 return request.param
12
13 @pytest.fixture(params=is_typeerror)
14 def is_typeerror(request):
15 return
test_para.py檔案
1 # coding:utf-8
2
3 import is_leap_year
4 import pytest
5
6 class TestPara():
7 def test_is_leap(self, is_leap):
8 assert is_leap_year.is_leap_year(is_leap) == True
9
10 def test_is_typeerror(self, is_typeerror):
11 with pytest.raises(TypeError):
12
pytest.mark.parametrize() -- 進行參數化
- 采用标記函數參數化,傳入單個參數,pytest.mark.parametrize("參數名",lists)。
- 采用标記函數傳入多個參數,如pytest.mark.parametrize("para1, para2", [(p1_data_0, p2_data_0), (p1_data_1, p2_data_1),...]。
- 這裡:測試用例中傳入2個參數,year和期望結果,使輸入資料與預期結果對應,構造了2組會失敗的資料,在執行結果中,可以看到失敗原因。
代碼:
1 # coding:utf-8
2 import is_leap_year
3 import pytest
4
5 class TestPara():
6 # 參數傳入year中
7 @pytest.mark.parametrize('year, expected', [(1, False), (4,True), (100, False), (400, True), (500, True)])
8 def test_is_leap(self, year, expected):
9 assert is_leap_year.is_leap_year(year) == expected
10
11 @pytest.mark.parametrize('year, expected', [(0, ValueError),('-4', TypeError), (-4, ValueError), ('ss', TypeError), (100.0, ValueError)])
12 def test_is_typeerror(self, year, expected):
13 if expected == ValueError:
14 with pytest.raises(ValueError) as excinfo:
15 is_leap_year.is_leap_year(year)
16 assert excinfo.type == expected
17 else:
18 with pytest.raises(TypeError) as excinfo:
19 is_leap_year.is_leap_year(year)
20 assert
pytest-fixture擴充内容
1. 把一個函數定義為Fixture很簡單,隻能在函數聲明之前加上“@pytest.fixture”。其他函數要來調用這個Fixture,隻用把它當做一個輸入的參數即可。
1 import pytest
2
3 @pytest.fixture()
4 def before():
5 print '\nbefore each test'
6
7 def test_1(before):
8 print 'test_1()'
9
10 def test_2(before):
11 print 'test_2()'
result:
(venvX) F:\test_jiaxin>pytest -v -s test_compute.py
============================= test session starts =============================
platform win32 -- Python 2.7.9, pytest-3.7.1, py-1.5.3, pluggy-0.7.1 -- f:\projects\sandom\venvx\scripts\python.exe
cachedir: .pytest_cache
rootdir: F:\test_jiaxin, inifile:
plugins: allure-adaptor-1.7.10
collected 2 items
test_compute.py::test_1
before each test
test_1()
PASSED
test_compute.py::test_2
before each test
test_2()
PASSED
========================== 2 passed in 0.17 seconds ===========================
2. 進行封裝
1 import pytest
2
3 @pytest.fixture()
4 def before():
5 print '\nbefore each test'
6
7 # 每個函數前聲明
8 @pytest.mark.usefixtures("before")
9 def test_1():
10 print 'test_1()'
11 @pytest.mark.usefixtures("before")
12 def test_2():
13 print 'test_2()'
14
15 #封裝在類裡,類裡的每個成員函數聲明
16 class Test1:
17 @pytest.mark.usefixtures("before")
18 def test_3(self):
19 print 'test_1()'
20 @pytest.mark.usefixtures("before")
21 def test_4(self):
22 print 'test_2()'
23
24 #封裝在類裡在前聲明
25 @pytest.mark.usefixtures("before")
26 class Test2:
27 def test_5(self):
28 print 'test_1()'
29 def test_6(self):
30 print 'test_2()'
result:
(venvX) F:\test_jiaxin>pytest -v -s test_compute.py
============================= test session starts =============================
platform win32 -- Python 2.7.9, pytest-3.7.1, py-1.5.3, pluggy-0.7.1 -- f:\projects\sandom\venvx\scripts\python.exe
cachedir: .pytest_cache
rootdir: F:\test_jiaxin, inifile:
plugins: allure-adaptor-1.7.10
collected 6 items
test_compute.py::test_1
before each test
test_1()
PASSED
test_compute.py::test_2
before each test
test_2()
PASSED
test_compute.py::Test1::test_3
before each test
test_1()
PASSED
test_compute.py::Test1::test_4
before each test
test_2()
PASSED
test_compute.py::Test2::test_5
before each test
test_1()
PASSED
test_compute.py::Test2::test_6
before each test
test_2()
PASSED
========================== 6 passed in 0.11 seconds ===========================
3. fixture還可以帶參數,可以把參數指派給params,預設是None。對于param裡面的每個值,fixture都會去調用執行一次,就像執行for循環一樣把params裡的值周遊一次。
1 import pytest
2
3 @pytest.fixture(params=[1, 2, 3])
4 def test_data(request):
5 return request.param
6
7 def test_not_2(test_data):
8 print('test_data: %s' % test_data)
9 assert
result:
(venvX) F:\test_jiaxin>pytest -v -s test_compute.py
============================= test session starts =============================
platform win32 -- Python 2.7.9, pytest-3.7.1, py-1.5.3, pluggy-0.7.1 -- f:\projects\sandom\venvx\scripts\python.exe
cachedir: .pytest_cache
rootdir: F:\test_jiaxin, inifile:
plugins: allure-adaptor-1.7.10
collected 3 items
test_compute.py::test_not_2[1] test_data: 1
PASSED
test_compute.py::test_not_2[2] test_data: 2
FAILED
test_compute.py::test_not_2[3] test_data: 3
PASSED
================================== FAILURES ===================================
________________________________ test_not_2[2] ________________________________
test_data = 2
def test_not_2(test_data):
print('test_data: %s' % test_data)
> assert test_data != 2
E assert 2 != 2
test_compute.py:64: AssertionError
===================== 1 failed, 2 passed in 0.12 seconds ======================