1. 程式人生 > >Pytest自動化測試 - 簡易教程

Pytest自動化測試 - 簡易教程

簡介

pytest是動態程式語言Python專用的測試框架,它具有易於上手、功能強大、可擴充套件性好、相容性強、效率高、第三方外掛豐富等特點。

功能特徵:

  • 完整的文件,包括安裝,教程和PDF文件
  • 簡單而又詳細的斷言模式(使用純assert語句)
  • 自動發現測試模組和功能(以test為標識)
  • 可以執行unittest和nose框架的測試用例
  • 靈活的韌體,用於管理小型或引數化的長期測試資源
  • 豐富的外掛架構,擁有三百多個外部外掛和豐富的社群

編寫規則:

  • 測試檔案以test_開頭(以_test結尾也可以)
  • 測試類以Test開頭,並且不能帶有 init 方法
  • 測試函式以test_開頭
  • 斷言使用基本的assert即可

自動發現規則:

如果未指定任何引數,則從testpaths(如果已配置)或當前目錄開始收集。
另外,命令列引數可以在目錄、檔名或節點ID的任何組合中使用。
在這些目錄中,搜尋包含 test_*.py 或 *_test.py 的測試檔案。
從這些檔案中,收集以test字首的測試方法,或者在Test字首的測試類(無__init__方法)中的以test字首的測試方法。

官方文件:https://docs.pytest.org/en/latest/contents.html

 


安裝

開啟bash命令列,執行以下命令:

pip install -U pytest

 

檢查是否安裝了正確的版本:

$ pytest --version
pytest 6.1.2

 


示例

建立一個簡單的測試函式:

# test_sample.py
# 被測功能
def func(x):
    return x + 1

# 測試成功
def test_pass():
    assert func(3) == 4

# 測試失敗
def test_fail():
    assert func(3) == 5

 

現在開始執行測試功能:

E:\workspace-py\Pytest>pytest
========================================================================== test session starts ==========================================================================
platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
rootdir: E:\workspace-py\Pytest
plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
collected 2 items                                                                                                                                                        

test_sample.py .F                                                                                                                                                  [100%]

=============================================================================== FAILURES ================================================================================
_______________________________________________________________________________ test_fail _______________________________________________________________________________

    def test_fail():
>       assert func(3) == 5
E       assert 4 == 5
E        +  where 4 = func(3)

test_sample.py:16: AssertionError
======================================================================== short test summary info ========================================================================
FAILED test_sample.py::test_fail - assert 4 == 5
====================================================================== 1 failed, 1 passed in 0.16s ======================================================================

這裡未指定測試用例,pytest將依據自動發現規則檢索並執行測試,等同於 pytest ./test_sample.py

  •   pytest 使用 . 標識測試成功(PASSED)
  •   pytest 使用 F 標識測試失敗(FAILED)
  •   可以使用 -v 選項,顯示測試的詳細資訊
  •   可以使用 -h 檢視 pytest 的所有選項

 


標記

預設情況下,pytest 會遞迴查詢當前目錄下所有以 test 開始或結尾的 Python 指令碼,並執行檔案內的所有以 test 開始或結束的函式和方法。

1、如果你想指定執行測試用例,可以通過 :: 顯式標記(檔名:: 類名::方法名)。

E:\workspace-py\Pytest>pytest test_sample.py::test_pass
========================================================================== test session starts ==========================================================================
platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
rootdir: E:\workspace-py\Pytest
plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
collected 1 item                                                                                                                                                         

test_sample.py .                                                                                                                                                   [100%]

=========================================================================== 1 passed in 0.05s ===========================================================================

 

2、如果你想選擇一些測試用例,可以使用 -k 模糊匹配。

E:\workspace-py\Pytest>pytest -k pass test_sample.py
========================================================================== test session starts ==========================================================================
platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
rootdir: E:\workspace-py\Pytest
plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
collected 2 items / 1 deselected / 1 selected                                                                                                                            

test_sample.py .                                                                                                                                                   [100%]

==================================================================== 1 passed, 1 deselected in 0.02s ====================================================================

 

3、如果你想跳過個別測試用例,可以使用 pytest.mark.skip(),或者 pytest.mark.skipif(條件表示式)。

# 測試失敗
@pytest.mark.skip()
def test_fail():
    assert func(3) == 5
E:\workspace-py\Pytest>pytest -v test_sample.py
========================================================================== test session starts ==========================================================================
platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 -- c:\python37\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.7.3', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'pytest': '6.0.2', 'py': '1.9.0', 'pluggy': '0.13.0'}, 'Plugins': {'allure-pytest': '2.8.
18', 'cov': '2.10.1', 'html': '2.1.1', 'metadata': '1.8.0', 'rerunfailures': '9.1', 'xdist': '2.1.0'}}
rootdir: E:\workspace-py\Pytest
plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
collected 2 items                                                                                                                                                        

test_sample.py::test_pass PASSED                                                                                                                                   [ 50%]
test_sample.py::test_fail SKIPPED                                                                                                                                  [100%]

===================================================================== 1 passed, 1 skipped in 0.07s ======================================================================

 

4、如果你想捕捉一些異常,可以使用pytest.raises()。

# test_raises.py

def test_raises():
    with pytest.raises(TypeError) as e:
        connect('localhost', '6379')
    exec_msg = e.value.args[0]
    assert exec_msg == 'port type must be int'

 

5、如果你事先知道測試函式會執行失敗,但又不想直接跳過,而是希望顯示的提示,可以使用pytest.mark.xfail()。

# 測試失敗
@pytest.mark.xfail()
def test_fail():
    assert func(3) == 5
E:\workspace-py\Pytest>pytest -k fail test_sample.py
========================================================================== test session starts ==========================================================================
platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
rootdir: E:\workspace-py\Pytest
plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
collected 2 items / 1 deselected / 1 selected                                                                                                                            

test_sample.py x                                                                                                                                                   [100%]

=================================================================== 1 deselected, 1 xfailed in 0.05s ====================================================================

 

6、如果你想對某個測試點進行多組資料測試,可以使用 pytest.mark.parametrize(argnames, argvalues) 引數化測試,即每組引數都獨立執行一次測試。

注意:以往我們可以把這些引數寫在測試函式內部進行遍歷,但是當某組引數導致斷言失敗,測試則就終止了。

# 測試成功
@pytest.mark.parametrize('data', [1, 2, 3])
def test_pass(data):
    assert func(data) == 4
E:\workspace-py\Pytest>pytest -k pass test_sample.py
========================================================================== test session starts ==========================================================================
platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
rootdir: E:\workspace-py\Pytest
plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
collected 4 items / 1 deselected / 3 selected                                                                                                                            

test_sample.py FF.                                                                                                                                                 [100%]

=============================================================================== FAILURES ================================================================================
_____________________________________________________________________________ test_pass[1] ______________________________________________________________________________

data = 1

    @pytest.mark.parametrize('data', [1, 2, 3])
    def test_pass(data):
>       assert func(data) == 4
E       assert 2 == 4
E        +  where 2 = func(1)

test_sample.py:11: AssertionError
_____________________________________________________________________________ test_pass[2] ______________________________________________________________________________

data = 2

    @pytest.mark.parametrize('data', [1, 2, 3])
    def test_pass(data):
>       assert func(data) == 4
E       assert 3 == 4
E        +  where 3 = func(2)

test_sample.py:11: AssertionError
======================================================================== short test summary info ========================================================================
FAILED test_sample.py::test_pass[1] - assert 2 == 4
FAILED test_sample.py::test_pass[2] - assert 3 == 4
=============================================================== 2 failed, 1 passed, 1 deselected in 0.17s ===============================================================

 


韌體

韌體(Fixture)是一些函式,pytest 會在執行測試函式之前(或之後)載入執行它們。

我們可以利用韌體做任何事情,其中最常見的可能就是資料庫的初始連線和最後關閉操作。

1、Pytest使用pytest.fixture()定義韌體,為了在實際工程中可以更大程度上覆用,我們更多的是使用檔案conftest.py集中管理韌體(pytest會自動呼叫)。

# conftest.py
import pytest

@pytest.fixture()
def data():
    return 3
# 測試成功
def test_pass(data):
    assert func(data) == 4
E:\workspace-py\Pytest>pytest -k pass test_sample.py
========================================================================== test session starts ==========================================================================
platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
rootdir: E:\workspace-py\Pytest
plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
collected 2 items / 1 deselected / 1 selected                                                                                                                            

test_sample.py .                                                                                                                                                   [100%]

==================================================================== 1 passed, 1 deselected in 0.05s ====================================================================

 

 

2、Pytest 使用 yield 關鍵詞將韌體分為兩部分,yield 之前的程式碼屬於預處理,會在測試前執行;yield 之後的程式碼屬於後處理,將在測試完成後執行。

# conftest.py
import pytest

@pytest.fixture()
def db():
    print('opened')
    yield
    print('closed')
# 測試成功
def test_pass(db):
    assert func(3) == 4
E:\workspace-py\Pytest>pytest -s -k pass test_sample.py
========================================================================== test session starts ==========================================================================
platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
rootdir: E:\workspace-py\Pytest
plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
collected 2 items / 1 deselected / 1 selected                                                                                                                            

test_sample.py opened
.closed


==================================================================== 1 passed, 1 deselected in 0.02s ====================================================================

 

 

3、為了更精細化控制韌體,pytest使用作用域來進行指定韌體的使用範圍。

在定義韌體時,通過 scope 引數宣告作用域,可選項有:

  • function: 函式級,每個測試函式都會執行一次韌體(預設值);
  • class: 類級別,每個測試類執行一次,所有方法都可以使用;
  • module: 模組級,每個模組執行一次,模組內函式和方法都可使用;
  • session: 會話級,一次測試只執行一次,所有被找到的函式和方法都可用。
# conftest.py
import pytest

@pytest.fixture(scope='function', autouse=True)
def func_scope():
    pass

@pytest.fixture(scope='module', autouse=True)
def mod_scope():
    pass

@pytest.fixture(scope='session')
def sess_scope():
    pass

@pytest.fixture(scope='class')
def class_scope():
    pass

  # 測試成功
  @pytest.mark.usefixtures('sess_scope')
  def test_pass(class_scope):
      assert func(3) == 4

E:\workspace-py\Pytest>pytest --setup-show -k pass test_sample.py
========================================================================== test session starts ==========================================================================
platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
rootdir: E:\workspace-py\Pytest
plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
collected 2 items / 1 deselected / 1 selected                                                                                                                            

test_sample.py
SETUP    S sess_scope
    SETUP    M mod_scope
      SETUP    C class_scope
        SETUP    F func_scope
        test_sample.py::test_pass (fixtures used: class_scope, func_scope, mod_scope, sess_scope).
        TEARDOWN F func_scope
      TEARDOWN C class_scope
    TEARDOWN M mod_scope
TEARDOWN S sess_scope

==================================================================== 1 passed, 1 deselected in 0.02s ====================================================================

 

我們可以把韌體作為入參,還可以使用@pytest.mark.usefixtures('class_scope'),或者指定autouse引數讓韌體自動執行。

並且還可以指定params引數來實現韌體的引數化,以及指定name引數來修改韌體名。

  • 可以使用 -s 選項,顯示print列印資訊
  • 可以使用 --setuo-show 選項,顯示詳細的韌體資訊

 

4、內建韌體:

tmpdir & tmpdir_factory:用於臨時檔案和目錄管理,預設會在測試結束時刪除。
pytestconfig:用於讀取命令列引數和配置檔案
capsys:用於捕獲 stdout 和 stderr 的內容,並臨時關閉系統輸出。
monkeypath:用於執行時動態修改類或模組。
recwarn:用於捕獲程式中 warnings 產生的警告。

 

 Pytest學習手冊:https://learning-pytest.readthedocs.io/zh/latest/index.html

 

 

作者:Leozhanggg

出處:https://www.cnblogs.com/leozhanggg/p/14035202.html

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。

&n