Python的pytest測試框架使用詳解
說到 pytest,大家總不免要拿來和 unittest 來比一下,但是 unittest 畢竟是標(biāo)準(zhǔn)庫,兼容性方面肯定沒得說,但要論簡潔和方便的話,pytest 也是不落下風(fēng)的。
簡單測試示例
def func(x): return x + 1 def test_answer(): assert func(3) == 5
Testing started at 15:57 ...
Launching pytest with arguments test.py::test_answer --no-header --no-summary -q in D:\Projects\insight-tools-rest
============================= test session starts =============================
collecting ... collected 1 item
test.py::test_answer FAILED [100%]
test.py:4 (test_answer)
4 != 5
Expected :5
Actual :4
<Click to see difference>
def test_answer():
> assert func(3) == 5
E assert 4 == 5
test.py:6: AssertionError
============================== 1 failed in 0.13s ==============================
Process finished with exit code 1
斷言某類異常
import pytest def f(): raise SystemExit(1) def test_mytest(): with pytest.raises(SystemExit): f()
[root@master ~]# pytest test.py
============================================================================= test session starts ==============================================================================
platform linux -- Python 3.6.8, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /root
collected 1 item
test.py . [100%]
============================================================================== 1 passed in 0.01s ===============================================================================
[root@master ~]# pytest -q test.py
. [100%]
1 passed in 0.00s
將多個測試分組到類
class TestClass: def test_one(self): x = "this" assert "h" in x def test_two(self): x = "hello" assert hasattr(x, "check")
[root@master ~]# pytest -q test.py
.F [100%]
=================================================================================== FAILURES ===================================================================================
______________________________________________________________________________ TestClass.test_two ______________________________________________________________________________
self = <test.TestClass object at 0x7ff2dec24390>
def test_two(self):
x = "hello"
> assert hasattr(x, "check")
E AssertionError: assert False
E + where False = hasattr('hello', 'check')
test.py:8: AssertionError
=========================================================================== short test summary info ============================================================================
FAILED test.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.02s
在類中對測試分組時需要注意的是,每個測試都有一個唯一的類實(shí)例。讓每個測試共享同一個類實(shí)例將非常不利于測試隔離(添加到類層級的屬性會被所有 test 共享)。
class TestClassDemoInstance: value = 0 def test_one(self): self.value = 1 assert self.value == 1 def test_two(self): assert self.value == 1
[root@master ~]# pytest -q test.py
.F [100%]
=================================================================================== FAILURES ===================================================================================
________________________________________________________________________ TestClassDemoInstance.test_two ________________________________________________________________________
self = <test.TestClassDemoInstance object at 0x7f22110f44e0>
def test_two(self):
> assert self.value == 1
E assert 0 == 1
E + where 0 = <test.TestClassDemoInstance object at 0x7f22110f44e0>.value
test.py:9: AssertionError
=========================================================================== short test summary info ============================================================================
FAILED test.py::TestClassDemoInstance::test_two - assert 0 == 1
1 failed, 1 passed in 0.02s
指定測試
在模塊中運(yùn)行測試
pytest test.py
[root@master ~]# pytest -q test.py
.F [100%]
=================================================================================== FAILURES ===================================================================================
________________________________________________________________________ TestClassDemoInstance.test_two ________________________________________________________________________
self = <test.TestClassDemoInstance object at 0x7f3395b78470>
def test_two(self):
> assert self.value == 1
E assert 0 == 1
E + where 0 = <test.TestClassDemoInstance object at 0x7f3395b78470>.value
test.py:9: AssertionError
=========================================================================== short test summary info ============================================================================
FAILED test.py::TestClassDemoInstance::test_two - assert 0 == 1
1 failed, 1 passed in 0.02s
在模塊中運(yùn)行特定測試
pytest test.py::TestClassDemoInstance::test_one
[root@master ~]# pytest -q test.py::TestClassDemoInstance::test_one
. [100%]
1 passed in 0.01s
在目錄中運(yùn)行測試
pytest testing/
按關(guān)鍵字表達(dá)式運(yùn)行測試
pytest -k "MyClass and not method"
[root@master ~]# pytest -q test.py -k 'one'
. [100%]
1 passed, 1 deselected in 0.01s
[root@master ~]# pytest -q test.py -k 'two'
F [100%]
=================================================================================== FAILURES ===================================================================================
________________________________________________________________________ TestClassDemoInstance.test_two ________________________________________________________________________
self = <test.TestClassDemoInstance object at 0x7fbbe853e908>
def test_two(self):
> assert self.value == 1
E assert 0 == 1
E + where 0 = <test.TestClassDemoInstance object at 0x7fbbe853e908>.value
test.py:9: AssertionError
=========================================================================== short test summary info ============================================================================
FAILED test.py::TestClassDemoInstance::test_two - assert 0 == 1
1 failed, 1 deselected in 0.02s
關(guān)于預(yù)期異常的斷言
import pytest def test_zero_division(): with pytest.raises(ZeroDivisionError): 1 / 0
root@master ~# pytest test.py
============================================================================= test session starts ==============================================================================
platform linux -- Python 3.6.8, pytest-6.2.5, py-1.11.0, pluggy-1.0.0
rootdir: /root
collected 1 item
test.py . [100%]
============================================================================== 1 passed in 0.02s ===============================================================================
通過 match
上下文管理器的關(guān)鍵字參數(shù),用于測試正則表達(dá)式是否匹配異常的字符串表示形式(如果能正常匹配,則可以通過測試):
import pytest def myfunc(): raise ValueError("Exception 123 raised") def test_match(): #with pytest.raises(ValueError, match=r".* 123 .*"): with pytest.raises(ValueError, match=r".* 124 .*"): myfunc()
root@master ~# pytest -q test.py
F [100%]
=================================================================================== FAILURES ===================================================================================
__________________________________________________________________________________ test_match __________________________________________________________________________________
def test_match():
#with pytest.raises(ValueError, match=r".* 123 .*"):
with pytest.raises(ValueError, match=r".* 124 .*"):
> myfunc()
test.py:11:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
def myfunc():
> raise ValueError("Exception 123 raised")
E ValueError: Exception 123 raised
test.py:5: ValueError
During handling of the above exception, another exception occurred:
def test_match():
#with pytest.raises(ValueError, match=r".* 123 .*"):
with pytest.raises(ValueError, match=r".* 124 .*"):
> myfunc()
E AssertionError: Regex pattern '.* 124 .*' does not match 'Exception 123 raised'.
test.py:11: AssertionError
=========================================================================== short test summary info ============================================================================
FAILED test.py::test_match - AssertionError: Regex pattern '.* 124 .*' does not match 'Exception 123 raised'.
1 failed in 0.02s
def test_set_comparison(): set1 = set("1308") set2 = set("8035") assert set1 == set2
root@master ~# pytest -q test.py
F [100%]
=================================================================================== FAILURES ===================================================================================
_____________________________________________________________________________ test_set_comparison ______________________________________________________________________________
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
> assert set1 == set2
E AssertionError: assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'}
E Extra items in the left set:
E '1'
E Extra items in the right set:
E '5'
E Full diff:
E - {'3', '8', '0', '5'}
E + {'8', '3', '1', '0'}
test.py:4: AssertionError
=========================================================================== short test summary info ============================================================================
FAILED test.py::test_set_comparison - AssertionError: assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'}
1 failed in 0.02s
使用 pytest.raises 斷言給定的異常
import pytest @pytest.mark.xfail(raises=IndexError) def test_f(): a = [1, 2] print(a[0])
root@master ~# pytest -q test.py
X [100%]
1 xpassed in 0.01s
import pytest @pytest.mark.xfail(raises=IndexError) def test_f(): a = [1, 2] print(a[2])
root@master ~# pytest -q test.py
x [100%]
1 xfailed in 0.02s
也可以使用 pytest.warns 檢查代碼是否引發(fā)了特定的警告
固定裝置 @pytest.fixture
fixture 是 pytest 的特色,這個我就不多說了。不過,要怎么理解這個 @pytest.fixture 裝飾的函數(shù)呢?
正常來說,像下面的例子,如果函數(shù) test_string 直接把輸入 order 當(dāng)成一個普通的參數(shù)的話,肯定是會報錯的(畢竟,誰也不知道你這個 order 是什么東東)。但使用了 @pytest.fixture 裝飾 order 以后,就完全不一樣了,這時候,test_string 的輸入?yún)?shù) order 其實(shí)是可以看成函數(shù) order 執(zhí)行返回后的結(jié)果重新賦值給了 order 參數(shù)(這也很符合裝飾器的特點(diǎn))。因此,@pytest.fixture 裝飾的測試函數(shù)的參數(shù)相當(dāng)于是一個已定義函數(shù)執(zhí)行后的結(jié)果。
import pytest # Arrange @pytest.fixture def first_entry(): return "a" # Arrange @pytest.fixture def order(first_entry): return [first_entry] def test_string(order): # Act order.append("b") # Assert assert order == ["a", "b"]
def first_entry(): return "a" def order(first_entry): return [first_entry] def test_string(order): # Act order.append("b") # Assert assert order == ["a", "b"] entry = first_entry() the_list = order(first_entry=entry) test_string(order=the_list)
固定裝置有很多特點(diǎn),比如裝置和使用其他裝置,也可以重復(fù)使用,測試函數(shù)和裝置也可以請求一次安裝多個裝置。
固定裝置也可以在同一測試期間多次執(zhí)行,pytest不會為該測試再次執(zhí)行它們(而是使用第一次執(zhí)行后的緩存結(jié)果),比如下面的例子:
import pytest # Arrange @pytest.fixture def first_entry(): return "a" # Arrange @pytest.fixture def order(): return [] # Act @pytest.fixture def append_first(order, first_entry): return order.append(first_entry) def test_string1(append_first, order, first_entry): # Assert assert order == [first_entry] def test_string2(order, first_entry): # Assert assert order == []
test_string1 和 test_string2 哪個會通過測試呢?答案是:兩個都會通過測試。
root@master ~# pytest -q test.py
.. [100%]
2 passed in 0.01s
但是為什么呢?因?yàn)閷τ?test_string1 而言,append_first 使用了固定裝置 order 后, order 已經(jīng)不再是空列表了,即使 test_string1 也有使用 order,但是這個 order 只是第一次 order 被執(zhí)行后的結(jié)果的引用,而不會真正去執(zhí)行一遍 order 固定裝置。test_string2 的話就好理解一些了。
到此這篇關(guān)于Python的pytest測試框架使用詳解的文章就介紹到這了,更多相關(guān)Python的pytest內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python Flask 請求數(shù)據(jù)獲取響應(yīng)詳解
這篇文章主要介紹了Python Flask請求數(shù)據(jù)獲取響應(yīng)的實(shí)現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-10-10python實(shí)現(xiàn)簡單學(xué)生信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了python簡單的學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-04-04關(guān)于numpy強(qiáng)制類型轉(zhuǎn)換的問題
這篇文章主要介紹了關(guān)于numpy強(qiáng)制類型轉(zhuǎn)換的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05通過Python編程將CSV文件導(dǎo)出為PDF文件的方法
CSV文件通常用于存儲大量的數(shù)據(jù),而PDF文件則是一種通用的文檔格式,便于與他人共享和打印,將CSV文件轉(zhuǎn)換成PDF文件可以幫助我們更好地管理和展示數(shù)據(jù),本文將介紹如何通過Python編程將CSV文件導(dǎo)出為PDF文件,需要的朋友可以參考下2024-06-06Python中關(guān)于matplotlib圖片的灰度處理方式
這篇文章主要介紹了Python中關(guān)于matplotlib圖片的灰度處理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08