天天看點

單元測試架構--pytest初識pytest認知pytest第一個簡單例子pytest 使用方法pytest擴充

pytest

  • pytest認知
  • pytest第一個簡單例子
  • pytest 使用方法
    • 斷言
    • Fixture
    • 參數化
    • 運作測試
      • 檢視幫助
      • 運作名稱中包含某字元串的測試用例
      • 減少測試的運作冗長
      • 如果出現一條測試用例失敗,則退出測試
      • 運作測試目錄
      • 指定特定類或方法執行
      • 通過main()方法運作測試
      • 運作所有用mark修飾的測試用例
      • 生成測試報告
  • pytest擴充
    • pytest-html
    • pytest-rerunfailures
    • pytest-parallel擴充

pytest認知

pytest是Python的一個第三方單元測試架構,提供了更加豐富的擴充,更加簡單、靈活,彌補了unittest在做web自動化測試的一些不足。

pytest支援pip安裝

pip install -U pytest
           

檢視pytest版本

pytest --version
           

對于pytest學習,可以參考:

  • 官方文檔
  • pytest中文文檔
  • pytest – 中文文檔

pytest第一個簡單例子

  1. 首先通過pytest編寫一個簡單的測試例子,test_sample.py
def inc(x):
    return x + 1

def test_answer():
    assert inc(3) == 5
           
  1. 切換到測試用例目錄下,執行【pytest】指令
(py3_heima) D:\zhenghou\python_learning>cd test_pytest

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning\test_pytest
collected 1 item                                                                                                                                                                   

test_sample.py F                                                                                                                                                             [100%]

==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_answer ___________________________________________________________________________________

    def test_answer():
>       assert inc(3) == 5
E       assert 4 == 5
E        +  where 4 = inc(3)

test_sample.py:13: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_sample.py::test_answer - assert 4 == 5
================================================================================ 1 failed in 0.46s ================================================================================
           

  • pytest更加簡單,不需要想unittest一樣必須建立測試類
  • 使用assert斷言
  • pytest的測試檔案和測試函數必須以【test】開頭

此外,pytest也可以使用像unittest一樣,通過main()方法執行測試用例

import pytest

def inc(x):
    return x + 1

def test_answer():
    assert inc(3) == 5


if __name__ == "__main__":
    pytest.main()
           

在一個類中執行多組測試

class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")
           

進入test_class所在目錄,執行【test_class.py】

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_class.py
.F                                                                                                                                                                           [100%]
==================================================================================== FAILURES =====================================================================================
_______________________________________________________________________________ TestClass.test_two ________________________________________________________________________________

self = <test_pytest.test_class.TestClass object at 0x000002D1ACA22780>

    def test_two(self):
        x = "hello"
>       assert hasattr(x, "check")
E       AssertionError: assert False
E        +  where False = hasattr('hello', 'check')

test_class.py:16: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_class.py::TestClass::test_two - AssertionError: assert False
1 failed, 1 passed in 0.38s

           

pytest 使用方法

斷言

pytest單元測試架構沒有提供專門的斷言方法,而是直接使用Python的asser進行斷言。

# 計算a + b
def add(a, b):
    return a + b

# 判斷是否為素數
def is_prime(n):
    if n <= 1:
        return False
    else:
        for i in range(2, n):
            if n % i == 0:
                return False
        return True


# 測試相等
def test_add_1():
    assert add(3, 4) == 7

# 測試不相等
def test_add_2():
    assert add(5, 8) != 12

# 測試小于等于
def test_add_3():
    assert add(5, 8) <= 10

# 測試大于等于
def test_add_4():
    assert add(4, 5) >= 3

# 測試包含于
def test_in():
    assert "h" in "hello"

# 測試不包含
def test_not_in():
    assert "he" not in "hello"

# 測試是否為True
def test_true_1():
    assert is_prime(1)

# 測試是否為True
def test_true_2():
    assert is_prime(1) is True

# 測試是否為True
def test_true_3():
    assert is_prime(1) is not True

# 測試是否為False
def test_false():
    assert is_prime(1) is False
           

Fixture

Fixture通常用來對測試方法、測試函數、測試類和整個測試檔案進行初始化或還原測試環境

# 功能函數:計算兩個數相加
def add(a, b):
    return a + b

# ==============fixture=======================
def setup_module(module):
    print("setup_module=========================>")

def teardown_module(module):
    print("teardown_module==========================>")

def setup_function(function):
    print("setup_function=====================>")

def teardown_function(function):
    print("teardown_function===========================>")

def setup():
    print("setup=================================>")

def teardown():
    print("teardown=================================>")


# 測試用例
def test_add_2_1():
    assert add(2, 1) == 3

def test_add_gg_aa():
    assert add("gg", "aa") == "ag"
           
  • setup_module/teardown_module: 在目前檔案中,在所有測試用例執行之前與之後執行
  • setup_function/teardown_function: 在每個測試函數之前與之後執行
  • setup/teardown: 在每個測試函數之前與之後執行。

測試類

# 功能函數:計算兩個數相加
def add(a, b):
    return a + b

class TestAdd:
    @classmethod
    def setup_class(cls):
        print("setup_class======================>")

    @classmethod
    def teardown_class(cls):
        print("teardown_class====================>")

    def setup_method(self, method):
        print("setup_method==================>")

    def teardown_method(self, method):
        print("teardown_method==========================>")

    def setup(self):
        print("setup=====================>")

    def teardown(self):
        print("teardown=============================>")

    # 測試用例
    def test_number_3_4(self):
        print("test add number 3 and 4")
        assert add(3, 4) == 7

    def test_string_gg_aa(self):
        print("test add string gg and aa")
        assert add("gg", "aa") == "ga"

           
  • setup_class/teardown_class: 在目前測試類的開始與結束時執行
  • setup_method/teardown_method: 在每個測試方法開始與結束執行
  • setup/teardown: 在每個測試方法開始與結束時執行,同樣可以用于測試函數

參數化

當一組測試用例有固定的測試資料是,可以通過參數化的方式簡化測試用例書寫。

import pytest
import math


@pytest.mark.parametrize(
    "base, exponent, expected",
    [(2, 2, 4),
     (2, 3, 8),
     (1, 9, 1),
     (0, 9, 0)],
    ids=["case1", "case2", "case3", "case4"]
)
def test_pow(base, exponent, expected):
    assert math.pow(base, exponent) == expected
           
  • "base, exponent, expected"

    : 用于定義參數的名稱
  • ids

    : 預設為None,用于定義測試用例的名稱

執行結果

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -v test_parametrize.py
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\zhenghou\python_learning\test_pytest
collected 4 items                                                                                                                                                                  

test_parametrize.py::test_pow[case1] PASSED                                                                                                                                  [ 25%]
test_parametrize.py::test_pow[case2] PASSED                                                                                                                                  [ 50%]
test_parametrize.py::test_pow[case3] PASSED                                                                                                                                  [ 75%]
test_parametrize.py::test_pow[case4] PASSED                                                                                                                                  [100%]

================================================================================ 4 passed in 0.16s ================================================================================
           

運作測試

您可以從指令行通過python解釋器調用測試:

python -m pytest [...]
           

檢視幫助

運作名稱中包含某字元串的測試用例

-k EXPRESSION         only run tests which match the given substring expression. An expression is a python evaluatable
                        expression where all names are substring-matched against test names and their parent classes.
                        Example: -k 'test_method or test_other' matches all test functions and classes whose name
                        contains 'test_method' or 'test_other', while -k 'not test_method' matches those that don't
                        contain 'test_method' in their names. -k 'not test_method and not test_other' will eliminate the
                        matches. Additionally keywords are matched to classes and functions containing extra names in
                        their 'extra_keyword_matches' set, as well as functions which have names assigned directly to
                        them. The matching is case-insensitive.
           

使用

-k

指定名稱中包含某字元串的測試用例。

pytest -k add test_assert.py
           

執行【test_assert.py】

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -k add  -v test_assert.py
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\zhenghou\python_learning\test_pytest
collected 10 items / 6 deselected / 4 selected                                                                                                                                     

test_assert.py::test_add_1 PASSED                                                                                                                                            [ 25%]
test_assert.py::test_add_2 PASSED                                                                                                                                            [ 50%]
test_assert.py::test_add_3 FAILED                                                                                                                                            [ 75%]
test_assert.py::test_add_4 PASSED                                                                                                                                            [100%]

==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________

    def test_add_3():
>       assert add(5, 8) <= 10
E       assert 13 <= 10
E        +  where 13 = add(5, 8)

test_assert.py:33: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_assert.py::test_add_3 - assert 13 <= 10
==================================================================== 1 failed, 3 passed, 6 deselected in 0.24s ====================================================================
           

減少測試的運作冗長

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_assert.py
..F..FFF..                                                                                                                                                                   [100%]
==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________

    def test_add_3():
>       assert add(5, 8) <= 10
E       assert 13 <= 10
E        +  where 13 = add(5, 8)

test_assert.py:33: AssertionError
___________________________________________________________________________________ test_not_in ___________________________________________________________________________________

    def test_not_in():
>       assert "he" not in "hello"
E       AssertionError: assert 'he' not in 'hello'
E         'he' is contained here:
E           hello
E         ? ++

test_assert.py:45: AssertionError
___________________________________________________________________________________ test_true_1 ___________________________________________________________________________________

    def test_true_1():
>       assert is_prime(1)
E       assert False
E        +  where False = is_prime(1)

test_assert.py:49: AssertionError
___________________________________________________________________________________ test_true_2 ___________________________________________________________________________________

    def test_true_2():
>       assert is_prime(1) is True
E       assert False is True
E        +  where False = is_prime(1)

test_assert.py:53: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_assert.py::test_add_3 - assert 13 <= 10
FAILED test_assert.py::test_not_in - AssertionError: assert 'he' not in 'hello'
FAILED test_assert.py::test_true_1 - assert False
FAILED test_assert.py::test_true_2 - assert False is True
4 failed, 6 passed in 0.27s
           

如果出現一條測試用例失敗,則退出測試

使用

-x

執行測試用例

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -x test_assert.py
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning\test_pytest
collected 10 items                                                                                                                                                                 

test_assert.py ..F

==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________

    def test_add_3():
>       assert add(5, 8) <= 10
E       assert 13 <= 10
E        +  where 13 = add(5, 8)

test_assert.py:33: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_assert.py::test_add_3 - assert 13 <= 10
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
=========================================================================== 1 failed, 2 passed in 0.21s ===========================================================================
           

pytest --maxfail=2

 --maxfail=num         exit after first num failures or errors.
           

運作測試目錄

測試目錄既可以指定相對路徑,也可以使用絕對路徑

(py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning
collected 19 items                                                                                                                                                                 

test_pytest\test_assert.py ..F..FFF..                                                                                                                                        [ 52%]
test_pytest\test_class.py .F                                                                                                                                                 [ 63%]
test_pytest\test_fixture.py .F                                                                                                                                               [ 73%]
test_pytest\test_parametrize.py ....                                                                                                                                         [ 94%]
test_pytest\test_sample.py F                                                                                                                                                 [100%]

==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________

    def test_add_3():
>       assert add(5, 8) <= 10
E       assert 13 <= 10
E        +  where 13 = add(5, 8)

test_pytest\test_assert.py:33: AssertionError
___________________________________________________________________________________ test_not_in ___________________________________________________________________________________

    def test_not_in():
>       assert "he" not in "hello"
E       AssertionError: assert 'he' not in 'hello'
E         'he' is contained here:
E           hello
E         ? ++

test_pytest\test_assert.py:45: AssertionError
___________________________________________________________________________________ test_true_1 ___________________________________________________________________________________

    def test_true_1():
>       assert is_prime(1)
E       assert False
E        +  where False = is_prime(1)

test_pytest\test_assert.py:49: AssertionError
___________________________________________________________________________________ test_true_2 ___________________________________________________________________________________

    def test_true_2():
>       assert is_prime(1) is True
E       assert False is True
E        +  where False = is_prime(1)

test_pytest\test_assert.py:53: AssertionError
_______________________________________________________________________________ TestClass.test_two ________________________________________________________________________________

self = <test_pytest.test_class.TestClass object at 0x00000243DBAF00B8>

    def test_two(self):
        x = "hello"
>       assert hasattr(x, "check")
E       AssertionError: assert False
E        +  where False = hasattr('hello', 'check')

test_pytest\test_class.py:16: AssertionError
____________________________________________________________________________ TestAdd.test_string_gg_aa ____________________________________________________________________________

self = <test_pytest.test_fixture.TestAdd object at 0x00000243DBAF0470>

    def test_string_gg_aa(self):
        print("test add string gg and aa")
>       assert add("gg", "aa") == "ga"
E       AssertionError: assert 'ggaa' == 'ga'
E         - ga
E         + ggaa

test_pytest\test_fixture.py:68: AssertionError
------------------------------------------------------------------------------ Captured stdout setup ------------------------------------------------------------------------------
setup_method==================>
setup=====================>
------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------
test add string gg and aa
---------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------
teardown=============================>
teardown_method==========================>
teardown_class====================>
___________________________________________________________________________________ test_answer ___________________________________________________________________________________

    def test_answer():
>       assert inc(3) == 5
E       assert 4 == 5
E        +  where 4 = inc(3)

test_pytest\test_sample.py:14: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_pytest/test_assert.py::test_add_3 - assert 13 <= 10
FAILED test_pytest/test_assert.py::test_not_in - AssertionError: assert 'he' not in 'hello'
FAILED test_pytest/test_assert.py::test_true_1 - assert False
FAILED test_pytest/test_assert.py::test_true_2 - assert False is True
FAILED test_pytest/test_class.py::TestClass::test_two - AssertionError: assert False
FAILED test_pytest/test_fixture.py::TestAdd::test_string_gg_aa - AssertionError: assert 'ggaa' == 'ga'
FAILED test_pytest/test_sample.py::test_answer - assert 4 == 5
========================================================================== 7 failed, 12 passed in 0.37s ===========================================================================
           

指定特定類或方法執行

每個收集的測試都被配置設定一個唯一的 nodeid 它由子產品檔案名和諸如類名、函數名和參數化參數等說明符組成,用 :: 字元。

在子產品内運作特定測試:

pytest test_mod.py::test_func
           

在指令行中指定測試方法的另一個示例:

pytest test_mod.py::TestClass::test_method
           

例如,指定運作test_fixture.py檔案中TestAdd類下的test_number_3_4方法

py3_heima) D:\zhenghou\python_learning\test_pytest>pytest test_fixture.py::TestAdd::test_number_3_4
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1
rootdir: D:\zhenghou\python_learning\test_pytest
collected 1 item                                                                                                                                                                   

test_fixture.py .                                                                                                                                                            [100%]

================================================================================ 1 passed in 0.02s ================================================================================
           

通過main()方法運作測試

import pytest

if __name__ == "__main__":
	pytest.main("-s", "./test_dir")
           

運作所有用mark修飾的測試用例

Run tests by marker expressions

pytest -m slow
           

将運作所有用 @pytest.mark.slow 裝飾符。

生成測試報告

  1. 生成JUnit XML檔案
(py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest --junit-xml=./report/log.xml
           
單元測試架構--pytest初識pytest認知pytest第一個簡單例子pytest 使用方法pytest擴充
  1. 生成線上測試報告

執行上述代碼會生成一個session-log連結,使用浏覽器打開,會得到一張HTML格式的測試報告

單元測試架構--pytest初識pytest認知pytest第一個簡單例子pytest 使用方法pytest擴充

pytest擴充

pytest-html

pytest-html可以生成HTML格式的測試報告,還支援測試用例失敗截圖,對于web自動化測試來說非常有用。

安裝

pip install pytest-html
           

運作測試用例

(py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest --html=./report/result.html
           

執行結果

單元測試架構--pytest初識pytest認知pytest第一個簡單例子pytest 使用方法pytest擴充

pytest-rerunfailures

pytest-rerunfailures可以在測試用例失敗時進行重試

安裝

pip install pytest-rerunfailures
           

安裝完成,通過【–reruns】參數設定測試用例運作失敗後的重試次數

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -v test_sample.py --reruns 3
=============================================================================== test session starts ===============================================================================
platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.6.4', 'Platform': 'Windows-10-10.0.16299-SP0', 'Packages': {'pytest': '5.4.3', 'py': '1.8.2', 'pluggy': '0.13.1'}, 'Plugins': {'html': '2.1.1', 'metadata':
'1.9.0', 'rerunfailures': '9.0'}, 'JAVA_HOME': 'F:\\java8'}
rootdir: D:\zhenghou\python_learning\test_pytest
plugins: html-2.1.1, metadata-1.9.0, rerunfailures-9.0
collected 1 item                                                                                                                                                                   

test_sample.py::test_answer RERUN                                                                                                                                            [100%]
test_sample.py::test_answer RERUN                                                                                                                                            [100%]
test_sample.py::test_answer RERUN                                                                                                                                            [100%]
test_sample.py::test_answer FAILED                                                                                                                                           [100%]

==================================================================================== FAILURES =====================================================================================
___________________________________________________________________________________ test_answer ___________________________________________________________________________________

    def test_answer():
>       assert inc(3) == 5
E       assert 4 == 5
E         +4
E         -5

test_sample.py:14: AssertionError
============================================================================= short test summary info =============================================================================
FAILED test_sample.py::test_answer - assert 4 == 5
=========================================================================== 1 failed, 3 rerun in 0.09s ============================================================================
           

pytest-parallel擴充

pytest-parallel擴充可以實作測試用例的并行運作

安裝

pip install pytest-parallel
           

建立測試用例

import time


def test_01():
    time.sleep(3)

def test_02():
    time.sleep(5)

def test_03():
    time.sleep(6)
           

不使用線程執行測試用例

(py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_parallel.py
...                                                                                                                                                                          [100%]
3 passed in 14.18s
           

使用【–test-per-worker】指定線程數,【auto】為自動配置設定

pytest -q test_parallel.py --tests-per-worker auto
           

其他用法

pytest --workers 2  # run 2 workers with 1 test per worker at a time
pytest --workers auto  # run 4 workers(4核) with 1 test per worker 
pytest --tests-per-worker 4  # run 1 worker with 4 tests at a time 

pytest --tests-per-worker auto  # runs 1 worker with up to 50 test2 at a time

pytest --workers 2 --tests-per-worker auto
           

而pytest-parallel支援python3.6及以上版本,如果是想做多程序并發的需要在linux平台或mac上做,windows上不起作用即(workers永遠=1),如果是做多線程的Linux/Mac/Windows平台都支援,程序數為workers設定的值

參考:python-pytest使用(4)-多線程多程序運作