天天看點

pytest架構進階自學系列 | 運作的失敗管理

作者:熱愛程式設計的通信人

書籍來源:房荔枝 梁麗麗《pytest架構與自動化測試應用》

一邊學習一邊整理老師的課程内容及實驗筆記,并與大家分享,侵權即删,謝謝支援!

附上彙總貼:pytest架構進階自學系列 | 彙總_熱愛程式設計的通信人的部落格-CSDN部落格

最多允許失敗的測試用例數

當達到最大上限時,退出執行。如未配置,則沒有上限。

指令pytest -x遇到第一個失敗時,退出執行。

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2> pytest -x
========================================================================================================= test session starts =========================================================================================================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2, inifile: pytest.ini
plugins: allure-pytest-2.13.2
collected 32 items                                                                                                                                                                                                                     

test_assert_1.py F

============================================================================================================== FAILURES =============================================================================================================== 
______________________________________________________________________________________________________ test_long_str_comparison _______________________________________________________________________________________________________ 

    def test_long_str_comparison():
        str3 = 'abcdef'
        str4 = 'adcdef'
>       assert str3 == str4
E       AssertionError: assert 'abcdef' == 'adcdef'
E         - adcdef
E         ?  ^
E         + abcdef
E         ?  ^

test_assert_1.py:4: AssertionError
========================================================================================================== warnings summary =========================================================================================================== 
c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\terminal.py:289
  c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\terminal.py:289: PytestDeprecationWarning: TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk.     
  See https://docs.pytest.org/en/latest/deprecations.html#terminalreporter-writer for more information.
    "TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk.\n"

-- Docs: https://docs.pytest.org/en/latest/warnings.html
======================================================================================================= short test summary info ======================================================================================================= 
FAILED test_assert_1.py::test_long_str_comparison - AssertionError: assert 'abcdef' == 'adcdef'
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
==================================================================================================== 1 failed, 1 warning in 10.42s ==================================================================================================== 
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2> 
           

指令pytest --maxfail=2遇到第2個失敗時,退出執行。同理,指令pytest --maxfail=3遇到第3個失敗時退出執行。

執行結果如下:

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2> pytest -x
========================================================================================================= test session starts =========================================================================================================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2, inifile: pytest.ini
plugins: allure-pytest-2.13.2
collected 32 items                                                                                                                                                                                                                     

test_assert_1.py F

============================================================================================================== FAILURES =============================================================================================================== 
______________________________________________________________________________________________________ test_long_str_comparison _______________________________________________________________________________________________________ 

    def test_long_str_comparison():
        str3 = 'abcdef'
        str4 = 'adcdef'
>       assert str3 == str4
E       AssertionError: assert 'abcdef' == 'adcdef'
E         - adcdef
E         ?  ^
E         + abcdef
E         ?  ^

test_assert_1.py:4: AssertionError
========================================================================================================== warnings summary =========================================================================================================== 
c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\terminal.py:289
  c:\users\guoliang\appdata\local\programs\python\python37\lib\site-packages\_pytest\terminal.py:289: PytestDeprecationWarning: TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk.     
  See https://docs.pytest.org/en/latest/deprecations.html#terminalreporter-writer for more information.
    "TerminalReporter.writer attribute is deprecated, use TerminalReporter._tw instead at your own risk.\n"

-- Docs: https://docs.pytest.org/en/latest/warnings.html
======================================================================================================= short test summary info ======================================================================================================= 
FAILED test_assert_1.py::test_long_str_comparison - AssertionError: assert 'abcdef' == 'adcdef'
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 
==================================================================================================== 1 failed, 1 warning in 10.42s ==================================================================================================== 
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-2> 
           

失敗運作管理的原理

用例運作失敗後,通常希望再次執行失敗的用例,看是環境問題還是腳本問題,是以測試方法運作失敗需被記錄。

這部分内容與第12章緩存目錄設定配置内容相一緻。cacheprovider插件将執行狀态寫入緩存檔案夾,pytest會将本輪測試的執行狀态寫入.pytest_cache檔案夾,這個行為是由自帶的cacheprovider插件實作的。

pytest預設将測試執行的狀态寫入根目錄中的.pytest_cache檔案夾,也可以通過在pytest.ini中配置cache_dir選項來自定義緩存的目錄,它可以是相對路徑,也可以是絕對路徑,相對路徑指的是相對于pytest.ini檔案所在的目錄。

例如,我們想把第12章執行的狀态的緩存和源碼放在一起,該如何實作呢?實作步驟如下。

在src/chapter12/pytest.ini中添加如下配置:

[pytest]
cache_dir = .pytest-cache           

這樣,即使在項目的根目錄下執行src/chapter12/中的用例,也隻會在pytest_book/src/chapter12/.pytest_cache中生成緩存,而不是pytest_book/.pytest_cache中。

(1)在chapter12中複制chapter-2中的test_assert_2.py并改名為test_assert_12.py,需要将内容進行适當修改。

(2)在src路徑下執行pytest src/chapter12。

(3)執行狀态未寫在根目錄下,而是寫在測試檔案所在的目錄中(.pytest_cache)。

執行結果如圖所示。

pytest架構進階自學系列 | 運作的失敗管理

cacheprovider插件實作失敗管理的準備:

(1)在chapter12下建立pytest.ini檔案,代碼如下:

[pytest]
cache_dir = .pytest-cache           

(2)在chapter12下建立test_failed.py檔案,代碼如下:

import pytest

@pytest.mark.parametrize('num', [1,2])
def test_failed(num):
    assert num == 1           

(3)在chapter12下建立test_pass.py檔案,代碼如下:

def test_pass():
    assert 1           

(4)如果大家是從前到後進行實踐,則需要删除test_assert_12.py和.pytest_cache。

(5)再在根目錄下執行pytest src/chapter12。

(6)chapter12下會自動建立緩存檔案夾及檔案.pytest_cache。

執行結果如下,可以看到一共收集到3個測試用例,其中有一個失敗,另外兩個成功,并且兩個執行成功的用例分屬不同的測試子產品。

執行結果如下:

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> pytest .\chapter-12\
========================================================================================================= test session starts =========================================================================================================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1
cachedir: .pytest-cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-12, inifile: pytest.ini
plugins: allure-pytest-2.13.2
collected 3 items                                                                                                                                                                                                                      

chapter-12\test_failed.py .F                                                                                                                                                                                                     [ 66%]
chapter-12\test_pass.py .                                                                                                                                                                                                        [100%] 

============================================================================================================== FAILURES =============================================================================================================== 
___________________________________________________________________________________________________________ test_failed[2] ____________________________________________________________________________________________________________ 

num = 2

    @pytest.mark.parametrize('num', [1,2])
    def test_failed(num):
>       assert num == 1
E       assert 2 == 1

chapter-12\test_failed.py:5: AssertionError
======================================================================================================= short test summary info =======================================================================================================
FAILED chapter-12\test_failed.py::test_failed[2] - assert 2 == 1
===================================================================================================== 1 failed, 2 passed in 0.30s ===================================================================================================== 
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> 
           

同時,pytest也在src/chapter12/目錄下生成緩存檔案夾(.pytest_cache)。

具體介紹一下cacheprovider插件的功能。

--lf,--last-failed:隻執行上一輪失敗的用例。

緩存中的lastfailed檔案記錄了上次失敗的用例ID,可以通過--cache-show指令檢視它的内容。

--cache-show指令是cacheprovider提供的新功能,它不會導緻任何用例的執行。

執行結果如下:

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> pytest .\chapter-12\ -q --cache-show 'lastfailed'
cachedir: D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-12\.pytest-cache
---------------------------------------------------------------------------------------------------- cache values for 'lastfailed' ---------------------------------------------------------------------------------------------------- 
cache\lastfailed contains:
  {'test_failed.py::test_failed[2]': True}

no tests ran in 0.00s
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> 
           

可以看到,它記錄了一個用例,此用例為上次失敗的測試用例的ID:test_failed.py::test_failed[2]。

下次執行,當使用--lf選項時,pytest在收集階段隻會選擇這個失敗的用例,而忽略其他的用例。指令pytest --lf --collect-only src/chapter12/中的collect-only選項隻收集用例而不執行。

執行結果如下:

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> pytest --lf --collect-only .\chapter-12\
========================================================================================================= test session starts =========================================================================================================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1
cachedir: .pytest-cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-12, inifile: pytest.ini
plugins: allure-pytest-2.13.2
collected 1 item                                                                                                                                                                                                                        
<Module test_failed.py>
  <Function test_failed[2]>
run-last-failure: rerun previous 1 failure (skipped 1 file)

======================================================================================================== no tests ran in 0.02s ========================================================================================================
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> 
           

仔細觀察一下上面的回顯,collecting...<Module test_failed.py>隻收集到test_fail.py檔案中<Function test_failed[2]>,表示第二個執行用例失敗了,需要重新執行(跳過5個檔案)。1deselected(沒有選擇),此處的“沒有選擇”是<Module test_failed.py>中執行通過的第一個用例。跳過5個檔案包括pytest.ini和test_pass.py在内的5個檔案。

實際上,--lf複寫了用例收集階段的兩個鈎子方法:pytest_ignore_collect(path,config)和pytest_collection_modifyitems(session,config,items)。

--ff,--failed-first:先執行上一輪失敗的用例,再執行其他的用例。

先通過實踐看一看這個指令的效果,再去分析它的實作,指令如下:

pytest架構進階自學系列 | 運作的失敗管理

執行結果如下:

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> pytest --collect-only -s --ff .\chapter-12\
========================================================================================================= test session starts =========================================================================================================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1
cachedir: .pytest-cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-12, inifile: pytest.ini
plugins: allure-pytest-2.13.2
collected 3 items                                                                                                                                                                                                                       
<Module test_failed.py>
  <Function test_failed[2]>
  <Function test_failed[1]>
<Module test_pass.py>
  <Function test_pass>
run-last-failure: rerun previous 1 failure first

======================================================================================================== no tests ran in 0.02s ======================================================================================================== 
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> 
           

可以看到一共收集到3個測試用例,和正常所收集到的測試用例的順序相比,上一輪失敗的test_failed.py::test_failed[2]用例在最前面,将優先執行。

實際上,-ff隻複寫了鈎子方法:pytest_collection_modifyitems(session,config,items),它可以過濾或者重新排序所收集到的用例。

--nf,--new-first:先執行新加的或修改的用例,再執行其他的用例。

緩存中的nodeids檔案記錄了上一輪執行的所有用例,指令如下:

pytest架構進階自學系列 | 運作的失敗管理

執行結果如下:

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> pytest .\chapter-12\ --cache-show 'nodeids'
========================================================================================================= test session starts =========================================================================================================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1
cachedir: .pytest-cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-12, inifile: pytest.ini
plugins: allure-pytest-2.13.2
cachedir: D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-12\.pytest-cache
----------------------------------------------------------------------------------------------------- cache values for 'nodeids' ------------------------------------------------------------------------------------------------------ 
cache\nodeids contains:
  ['test_failed.py::test_failed[1]',
   'test_failed.py::test_failed[2]',
   'test_pass.py::test_pass']

======================================================================================================== no tests ran in 0.01s ======================================================================================================== 
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> 
           

從執行結果可以看到上一輪共執行了3個測試用例。

現在可以在test_pass.py中新加一個用例,并修改一下test_failed.py檔案中的用例(但是不添加新用例)。

代碼如下:

def test_pass():
    assert 1
    
def test_new_pass():
    assert 1           

再來執行一下收集指令,指令如下:

pytest架構進階自學系列 | 運作的失敗管理

執行結果如下:

PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> pytest --collect-only -s --nf .\chapter-12\
========================================================================================================= test session starts =========================================================================================================
platform win32 -- Python 3.7.7, pytest-5.4.1, py-1.11.0, pluggy-0.13.1
cachedir: .pytest-cache
rootdir: D:\SynologyDrive\CodeLearning\WIN\pytest-book\src\chapter-12, inifile: pytest.ini
plugins: allure-pytest-2.13.2
collected 4 items                                                                                                                                                                                                                      
<Module test_pass.py>
  <Function test_new_pass>
  <Function test_pass>
<Module test_failed.py>
  <Function test_failed[1]>
  <Function test_failed[2]>

======================================================================================================== no tests ran in 0.48s ======================================================================================================== 
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> 
           

可以看到,新加的用例排在最前面,其次修改過的測試用例緊接其後,最後才是舊的用例,這個行為在源碼中有所展現。

--cache-clear:先清除所有緩存,再執行用例。

如果上一輪沒有失敗的用例,則需要先清除緩存,再執行test_pass.py子產品(它的用例都是能測試成功的)。

執行及結果如下:

======================================================================================================== no tests ran in 0.48s ======================================================================================================== 
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> pytest --cache-clear -q -s .\chapter-12\test_pass.py
..
2 passed in 0.01s
PS D:\SynologyDrive\CodeLearning\WIN\pytest-book\src> 
           

其結果是不是少了點什麼呢?對!因為沒有失敗的用例,是以不會生成lastfailed檔案,那麼這個時候再使用--lf和--ff會發生什麼呢?我們來試試。

注意:如果我們觀察得足夠仔細,就會發現現在的緩存目錄和之前相比不僅少了lastfailed檔案,還少了CACHEDIR.TAG、.gitignore和README.md這3個檔案。這是一個Bug,現在pytest的版本是5.2.1,預計會在之後的版本得到修複。

繼續閱讀