python - pytest


pytest介紹

pytest 是 python 的第三方單元測試框架,比自帶 unittest 更簡潔和高效,支持315種以上的插件,同時兼容 unittest 框架。這就使得我們在 unittest 框架遷移到 pytest 框架的時候不需要重寫代碼。接下來我們在文中來對分析下 pytest 有哪些簡潔、高效的用法

pytest特點:

門簡單易上手,文檔支持較好。 支持單元測試和功能測試。 支持參數化。 可以跳過指定用例,或對某些預期失敗的case標記成失敗。 支持重復執行失敗的case支持運行由unittest編寫的測試用例。 有很多第三方插件,並且可自定義擴展。 方便和支持集成工具進行集成。

安裝

pip install pytest

 

 測試是否安裝成功

C:\Users\Anthony>pytest --version
This is pytest version 5.2.2, imported from c:\python36\lib\site-packages\pytest.py

在測試之前要做的准備

我的演示腳本處於這樣一個的目錄中:

M:\py_tests\ # 我的是D盤的 py_tests 目錄,所有操作都在 py_tests 目錄內完成
    ├─scripts     #編寫測試用例的文件夾
    │  ├─test_case_dir1
    │  │  ├─test_case_02.py    # 用例腳本文件
    │  │  └─__init__.py
    │  ├─test_allure_case.py   # 腳本文件
    │  ├─test_case_01.py   # 腳本文件
    │  └─__init__.py
    ├─report    --報告生成的文件夾
    │  ├─report.html   # pytest-html生成的用例報告
    │  ├─assets  # allure的依賴目錄
    │  ├─result  # allure生成的用例數據
    │  └─allure_html   # allure生成的用例報告目錄
    |     └─index.html  # allure生成的最終的html類型的測試報告  
    ├─case_set.py
    ├─demo0.py   # 用例腳本文件
    ├─demo1.py   # 用例腳本文件
    ├─pytest.ini  # 配置文件
    └─__init__.py

簡單示例

demo.py

import pytest

def test_case01():
    print('執行用例01.......')
    assert 0  # 斷言失敗

def test_case02():
    print('執行用例02.......')
    assert 1  # 斷言成功
    
def custom_case03():
    print('執行用例03.......')
    assert 1  # 斷言成功
    
if __name__ == '__main__':
    pytest.main(["-s", "demo.py"])
    # pytest.main("-s demo.py")

上例中,當我們在執行(就像Python解釋器執行普通的Python腳本一樣)測試用例的時候,pytest.main(["-s", "demo.py"])中的傳參需要是一個元組或者列表(我的pytest是5.2.2版本),之前的版本可能需要這么調用pytest.main("-s demo.py"),傳的參數是str的形式,至於你使用哪種,取決於報不報錯:

TypeError: `args` parameter expected to be a list or tuple of strings, got: '-s demo1.py' (type: <class 'str'>)

遇到上述報錯,就是參數需要一個列表或者元組的形式,而我們使用的是str形式。

上述代碼正確的執行結果是這樣的:

===================================================== test session starts ======================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests
collected 2 items                                                                                                               

demo1.py 執行用例01.......
F執行用例02.......
.
=========================================================== FAILURES ===========================================================
_________________________________________________________ test_case01 __________________________________________________________

    def test_case01():
        print('執行用例01.......')
>       assert 0  # 斷言失敗
E       assert 0

demo1.py:11: AssertionError
================================================= 1 failed, 1 passed in 0.13s ==================================================

大致的信息就是告訴我們:

  • collected 2 items:本次執行中,收集了2個用例。

  • 完了開始執行用例,.表示執行成功,F表示執行失敗。

  • 腳本中的第一個用例執行失敗;第二個用例執行成功;但是第三個也就是custom_case03並沒有執行,由此我們知道,pytest只識別以test_開頭的用例

pytest.main(["-s", "demo.py"])參數說明

  • -s,表示輸出用例執行的詳細結果。

  • demo1.py是要執行的腳本名稱。

除了上述的函數這種寫法,也可以有用例類的寫法:

import pytest
class TestCase(object):
    def test_case01(self):
        """ 用例 01 """
        print('執行用例01.......')
        assert 0  # 斷言失敗

    def test_case02(slef):
        """ 用例 02 """
        print('執行用例02.......')
        assert 1  # 斷言成功
if __name__ == '__main__':
    pytest.main(["-s", "demo.py"])

類名要以Test開頭,並且其中的用例方法也要以test開頭,不然pytest不會執行

執行結果:

 

D:\py_tests>python demo.py
========================================================== test session starts ===========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests
collected 2 items                                                                                                                         

demo1.py 執行用例01.......
F執行用例02.......
.

================================================================ FAILURES ================================================================
__________________________________________________________ TestCase.test_case01 __________________________________________________________

self = <demo1.TestCase object at 0x03DD6110>

    def test_case01(self):
        """ 用例 01 """
        print('執行用例01.......')
>       assert 0  # 斷言失敗
E       assert 0

demo1.py:49: AssertionError
====================================================== 1 failed, 1 passed in 0.12s =======================================================

 

. 表示通過, F表示失敗

接下來,我們來研究一下pytest中的setup和teardown的用法。

setup和teardown

我們知道,在unittest中,setup和teardown可以在每個用例前后執行,也可以在所有的用例集執行前后執行。那么在pytest中,有以下幾種情況:

  • 模塊級別,也就是在整個測試腳本文件中的用例集開始前后,對應的是:

    • setup_module

    • teardown_module

  • 類級別,在類中的所有用例集執行前后,在類里寫,對應的是:

    • setup_class

    • teardown_class

  • 在類中呢,也可以在進一步划分,在每一個方法執行前后,在類里寫,對應:

    • setup_method

    • teardown_methd

  • 函數級別,在用例函數之前后,對應:

    • setup_function

    • teardown_function

來一一看看各自的用法。

模塊級別setup_module/teardown_module

import pytest
def setup_module():
    """ 模塊級別的 setup,在該腳本內所有用例集執行之前觸發執行 """
    print('模塊級別的 setup.....')
def test_case01():
    print('執行用例01.......')
    assert 0  # 斷言失敗
def test_case02():
    print('執行用例02.......')
    assert 1  # 斷言成功
def teardown_module():
    """ 模塊級別的 teardown,在該腳本內所有用例集執行之后觸發執行 """
    print('模塊級別的 teardown.....')

if __name__ == '__main__':
    pytest.main(["-s", "demo1.py"])

執行結果

D:\py_tests>python demo.py
========================================================== test session starts ===========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests
collected 2 items                                                                                                                         

demo1.py 模塊級別的 setup.....
執行用例01.......
F執行用例02.......
.模塊級別的 teardown.....


================================================================ FAILURES ================================================================
______________________________________________________________ test_case01 _______________________________________________________________

    def test_case01():
        print('執行用例01.......')
>       assert 0  # 斷言失敗
E       assert 0

demo1.py:16: AssertionError
====================================================== 1 failed, 1 passed in 0.12s =======================================================

類級別的setup_class/teardown_class

import pytest

class TestCase(object):

    def setup_class(self):
        """ 類級別的 setup,在該類中內用例集執行之前觸發執行 """
        print('類級別的 setup.....')

    def teardown_class(self):
        """ 類級別的 teardown,在該類中內用例集執行之后觸發執行 """
        print('類級別的 teardown.....')

    def test_case01(self):
        """ 用例 01 """
        print('執行用例01.......')
        assert 0  # 斷言失敗

    def test_case02(slef):
        """ 用例 02 """
        print('執行用例02.......')
        assert 1  # 斷言成功

if __name__ == '__main__':
    pytest.main(["-s", "demo1.py"])

 

執行結果
D:\py_tests>python demo.py
========================================================== test session starts ===========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests
collected 2 items                                                                                                                         

demo1.py 類級別的 setup.....
執行用例01.......
F執行用例02.......
.類級別的 teardown.....


================================================================ FAILURES ================================================================
__________________________________________________________ TestCase.test_case01 __________________________________________________________

self = <demo1.TestCase object at 0x0363F710>

    def test_case01(self):
        """ 用例 01 """
        print('執行用例01.......')
>       assert 0  # 斷言失敗
E       assert 0

demo1.py:53: AssertionError
====================================================== 1 failed, 1 passed in 0.10s =======================================================

 

類中方法級別的setup_method/teardown_method

import pytest

class TestCase(object):

    def setup_method(self):
        """ 類中方法級別的 setup,在該類中內每個用例執行之前觸發執行 """
        print('類中方法級別的 setup.....')

    def teardown_method(self):
        """ 類中方法級別的 teardown,在該類中內每個用例執行之后觸發執行 """
        print('類中方法級別的 teardown.....')

    def test_case01(self):
        """ 用例 01 """
        print('執行用例01.......')
        assert 0  # 斷言失敗

    def test_case02(slef):
        """ 用例 02 """
        print('執行用例02.......')
        assert 1  # 斷言成功

if __name__ == '__main__':
    pytest.main(["-s", "demo1.py"])

執行結果:

D:\py_tests>python demo.py
========================================================== test session starts ===========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests
collected 2 items                                                                                                                         

demo1.py 類中方法級別的 setup.....
執行用例01.......
F類中方法級別的 teardown.....
類中方法級別的 setup.....
執行用例02.......
.類中方法級別的 teardown.....


================================================================ FAILURES ================================================================
__________________________________________________________ TestCase.test_case01 __________________________________________________________

self = <demo1.TestCase object at 0x042BA2D0>

    def test_case01(self):
        """ 用例 01 """
        print('執行用例01.......')
>       assert 0  # 斷言失敗
E       assert 0

demo1.py:49: AssertionError
====================================================== 1 failed, 1 passed in 0.42s =======================================================

函數級別的setup_function/teardown_function

import pytest

def setup_function():
    """ 函數級別的 setup,在該腳本內每個用例函數執行之前觸發執行 """
    print('函數級別的 setup.....')

def test_case01():
    print('執行用例01.......')
    assert 0  # 斷言失敗

def test_case02():
    print('執行用例02.......')
    assert 1  # 斷言成功

def teardown_function():
    """ 函數級別的 teardown,在該腳本內每個用例函數執行之后觸發執行 """
    print('函數級別的 teardown.....')


if __name__ == '__main__':
    pytest.main(["-s", "demo1.py"])

執行結果

D:\py_tests>python demo.py
========================================================== test session starts ===========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests
collected 2 items                                                                                                                         

demo1.py 函數級別的 setup.....
執行用例01.......
F函數級別的 teardown.....
函數級別的 setup.....
執行用例02.......
.函數級別的 teardown.....


================================================================ FAILURES 
______________________________________________________________ test_case01 

    def test_case01():
        print('執行用例01.......')
>       assert 0  # 斷言失敗
E       assert 0

demo1.py:16: AssertionError
====================================================== 1 failed, 1 passed in 0.11s 

小結

  • 在類中,不需要__init__方法。

  • 測試類的類名必須以Test開頭。

  • 類中的測試方法編寫規則跟函數一致

配置文件

該腳本有多種運行方式,如果處於PyCharm環境,可以使用右鍵或者點擊運行按鈕運行,也就是在pytest中的主函數中運行:

if __name__ == '__main__':
    pytest.main(["-s", "demo1.py"])   # 就是調用的 pytest 的 main 函數

 

也可以在命令行中運行:

D:\py_tests>python demo1.py

這種方式,跟使用Python解釋器執行Python腳本沒有什么兩樣。也可以如下面這么執行:

D:\py_tests>pytest -s demo1.py

當然,還有一種是使用配置文件運行,來看看怎么用。

在項目的根目錄下,我們可以建立一個pytest.ini文件,在這個文件中,我們可以實現相關的配置:

[pytest]
addopts = -s -v   
#可以搭配相關的參數,比如-s。多個參數以空格分割,其他參數后續用到再說
#-s,在運行測試腳本時,為了調試或打印一些內容,我們會在代碼中加一些print內容,但是在運行pytest時,這些內容不會顯示出來。如果帶上-s,就可以顯示了。
#-v,使輸出結果更加詳細
testpaths = ./scripts
#配置測試用例的目錄  ,必須有
python_files = demo*.py
#執行指定的文件,以test開頭的python文件
python_classes = Test*
#執行指定的類
python_functions = test_*
#執行指定的函數

注意,配置文件中不許有中文,那這個配置文件中的各項都是什么意思呢?

首先,pytest.ini文件必須位於項目的根目錄,而且也必須叫做pytest.ini

其他的參數:

  • addopts可以搭配相關的參數,比如-s。多個參數以空格分割,其他參數后續用到再說。

    • -s,在運行測試腳本時,為了調試或打印一些內容,我們會在代碼中加一些print內容,但是在運行pytest時,這些內容不會顯示出來。如果帶上-s,就可以顯示了。

    • -v,使輸出結果更加詳細。

  • testpaths配置測試用例的目錄,

    • 因為我們用例可能分布在不同的目錄或文件中,那么這個scripts就是我們所有文件或者目錄的頂層目錄。其內的子文件或者子目錄都要以test_開頭,pytest才能識別到。

    • 另外,上面這么寫,是從一個總目錄下尋找所有的符合條件的文件或者腳本,那么我們想要在這個總目錄下執行其中某個具體的腳本文件怎么辦?

[pytest]
testpaths = ./scripts/
python_files = test_case_01.py

 

 

  • 這么寫就是執行scripts目錄下面的test_case_01.py這個文件。

  • python_classes則是說明腳本內的所有用例類名必須是以Test開頭,當然,你也可以自定義為以Test_開頭,而類中的用例方法則當然是以test_開頭。

  • python_functions則是說腳本內的所有用例函數以test_開頭才能識別。

OK,來個示例。

首先,(詳細目錄參考開頭的目錄結構)在scripts/test_case_01.py中:

import pytest
def test_case01():
    print('執行用例01.......')
    assert 1  # 斷言成功

def test_case02():
    print('執行用例02.......')
    assert 1  # 斷言成功

class TestCaseClass(object):

    def test_case_03(self):
        assert 0  # 斷言失敗

scripts/test_case_dir1/test_case02.py中:

import pytest

def test_case_04():
    assert 1  # 斷言成功
    
def test_case_05():
    assert 0  # 斷言失敗

那么,在不同的目錄或者文件中,共有5個用例將被執行,而結果則是兩個失敗三個成功。來執行驗證一下,因為有了配置文件,我們在終端中(前提是在項目的根目錄),直接輸入pytest即可。

M:\py_tests>pytest
======================================================= test session starts ========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests, inifile: pytest.ini, testpaths: ./scripts
collected 5 items                                                                                                                   

scripts\test_case_01.py 執行用例01.......
.執行用例02.......
.F
scripts\test_case_dir1\test_case_02.py .F

============================================================= FAILURES 
____________________________________________________ TestCaseClass.test_case_03 

self = <test_case_01.TestCaseClass object at 0x03CAF4D0>

    def test_case_03(self):
>       assert 0
E       assert 0

scripts\test_case_01.py:22: AssertionError
___________________________________________________________ test_case_05 

    def test_case_05():
>       assert 0
E       assert 0

scripts\test_case_dir1\test_case_02.py:14: AssertionError
=================================================== 2 failed, 3 passed in 0.14s 

由執行結果可以發現,2 failed, 3 passed,跟我們的預期一致。

后續執行相關配置都來自配置文件,如果更改,會有相應說明,終端都是直接使用pytest執行。

進階

跳過用例

import pytest
#無條件跳過
@pytest.mark.skip()
def test_case_01():
    assert 1
#有條件跳過,兩個參數必須加上
@pytest.mark.skipif(condition=1 < 2, reason='如果條件為true就跳過用例')
def test_case_02():
    assert 1

跳過用例,我們使用@pytest.mark.skipif(condition, reason)

  • condition表示跳過用例的條件。

  • reason表示跳過用例的原因。

然后將它裝飾在需要被跳過用例的的函數上面。

效果如下:

M:\py_tests>pytest                                                                                                                   

scripts/test_allure_case.py::test_case_01 SKIPPED
scripts/test_allure_case.py::test_case_02 SKIPPED

=========================================================== 2 skipped in 0.14s

上例執行結果相對詳細,因為我們在配置文件中為addopts增加了-v,之前的示例結果中,沒有加!

參數化

pytest身為強大的測試單元測試框架,那么同樣支持DDT數據驅動測試的概念。也就是當對一個測試函數進行測試時,通常會給函數傳遞多組參數。比如測試賬號登陸,我們需要模擬各種千奇百怪的賬號密碼。

當然,我們可以把這些參數寫在測試函數內部進行遍歷。不過雖然參數眾多,但仍然是一個測試,當某組參數導致斷言失敗,測試也就終止了。

通過異常捕獲,我們可以保證程所有參數完整執行,但要分析測試結果就需要做不少額外的工作。

在 pytest 中,我們有更好的解決方法,就是參數化測試,即每組參數都獨立執行一次測試。使用的工具就是 pytest.mark.parametrize(argnames, argvalues)

  • argnames表示參數名。

  • argvalues表示列表形式的參數值。

使用就是以裝飾器的形式使用。

只有一個參數的測試用例

import pytest

mobile_list = ['10010', '10086']

@pytest.mark.parametrize('mobile', mobile_list)
def test_register(mobile):
    """ 通過手機號注冊 """
    print('注冊手機號是: {}'.format(mobile))

來看(重要部分)結果::

M:\py_tests>pytest
scripts/test_case_01.py::test_register[10010] 注冊手機號是: 10010
PASSED
scripts/test_case_01.py::test_register[10086] 注冊手機號是: 10086
PASSED

====================================================== 2 passed in 0.11s 

可以看到,列表內的每個手機號,都是一條測試用例。

 多個參數的測試用例

import pytest

mobile_list = ['10010', '10086']
code_list = ['x2zx', 'we2a']

@pytest.mark.parametrize('mobile', mobile_list)
@pytest.mark.parametrize('code', code_list)
def test_register(mobile, code):
    """ 通過手機號注冊 """
    print('注冊手機號是: {} 驗證碼是: {}'.format(mobile, code))

(重要部分)結果:

M:\py_tests>pytest                            

scripts/test_case_01.py::test_register[x2zx-10010] 注冊手機號是: 10010 驗證碼是: x2zx
PASSED
scripts/test_case_01.py::test_register[x2zx-10086] 注冊手機號是: 10086 驗證碼是: x2zx
PASSED
scripts/test_case_01.py::test_register[we2a-10010] 注冊手機號是: 10010 驗證碼是: we2a
PASSED
scripts/test_case_01.py::test_register[we2a-10086] 注冊手機號是: 10086 驗證碼是: we2a
PASSED

====================================================== 4 passed in 0.17s 

可以看到,每一個手機號與每一個驗證碼都組合一起執行了,這樣就執行了4次。那么如果有很多個組合的話,用例數將會更多。我們希望手機號與驗證碼一一對應組合,也就是只執行兩次,怎么搞呢?

import pytest

mobile_list = ['10010', '10086']
code_list = ['x2zx', 'we2a']

@pytest.mark.parametrize('mobile,code', zip(mobile_list, code_list))
def test_register(mobile, code):
    """ 通過手機號注冊 """
    print('注冊手機號是: {} 驗證碼是: {}'.format(mobile, code))

在多參數情況下,多個參數名是以,分割的字符串。參數值是列表嵌套的形式組成的

M:\py_tests>pytest                   

scripts/test_case_01.py::test_register[10010-x2zx] 注冊手機號是: 10010 驗證碼是: x2zx
PASSED
scripts/test_case_01.py::test_register[10086-we2a] 注冊手機號是: 10086 驗證碼是: we2a
PASSED

====================================================== 2 passed in 0.44s

pytest測試報告插件

想要生成測試報告,首先要有下載,才能使用。

pip install pytest-html

使用

在配置文件中,添加參數:

[pytest]
addopts = -s --html=report/report.html

完事之后,讓我們繼續終端中使用pytest重新跑測試用例,用例結果就不展示了,跟上面的結果一樣,我們關注項目目錄下的report/report.html文件,我們用瀏覽器打開它,你會發現:

效果很不錯吧!

控制測試用例執行的順序

在之前,用例的執行順序是從上到下依次執行:

import pytest

class TestCaseClass(object):
    def test_case_03(self):
        print('執行用例03.......')
        assert 1

def test_case01():
    print('執行用例01.......')
    assert 1  # 斷成功

def test_case02():
    print('執行用例02.......')
    assert 1  # 斷言成功

正如上例的執行順序是3 1 2

現在,來看看我們如何手動控制多個用例的執行順序,這里也依賴一個插件。

下載

pip install pytest-ordering

使用

import pytest

class TestCaseClass(object):
    @pytest.mark.run(order=3)
    def test_case_03(self):
        print('執行用例03.......')
        assert 1

@pytest.mark.run(order=2)
def test_case01():
    print('執行用例01.......')
    assert 1  # 斷言成功

@pytest.mark.run(order=1)
def test_case02():
    print('執行用例02.......')
    assert 1  # 斷言成功

手動控制用例執行順序的方法是在給各用例添加一個裝飾器:

@pytest.mark.run(order=x)   # x 是一個整數

那么, 現在的執行順序是2 1 3,按照order指定的排序執行的。

如果有人較勁傳個0或者負數啥的,那么它們的排序關系應該是這樣的:

0 > 正數 > 沒有參與的用例 > 負數
# 正數和負數就是按照大小關系排列的

失敗重試

失敗重試意思是指定某個用例執行失敗可以重新運行。

下載

pip install pytest-rerunfailures

使用

需要在pytest.ini文件中, 配置:

[pytest]
addopts = -s --html=report/report.html --reruns=3
;addopts = -s --alluredir ./report/result
testpaths = ./scripts/
python_files = test_case_01.py
python_classes = Test*
python_functions = test_*

addopts字段新增(其他原有保持不變)--reruns=3字段,這樣如果有用例執行失敗,則再次執行,嘗試3次。

來看示例:

import pytest

def test_case01():
    print('執行用例01.......')
    assert 1  # 斷言成功

def test_case02():
    print('執行用例02.......')
    assert 0  # 斷言失敗,需要重新執行

class TestCaseClass(object):

    def test_case_03(self):
        print('執行用例03.......')
        assert 1

結果:

M:\py_tests>pytest
======================================================= test session starts ========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests, inifile: pytest.ini, testpaths: ./scripts/
plugins: allure-pytest-2.8.6, html-2.0.0, metadata-1.8.0, ordering-0.6, rerunfailures-7.0
collected 3 items                                                                                                                   

scripts\test_case_01.py 執行用例01.......
.執行用例02.......
R執行用例02.......
R執行用例02.......
R執行用例02.......
F執行用例03.......
.

============================================================= FAILURES =============================================================
___________________________________________________________ test_case02 ____________________________________________________________

    def test_case02():
        print('執行用例02.......')
>       assert 0  # 斷言失敗,需要重新執行
E       assert 0

scripts\test_case_01.py:19: AssertionError
------------------------------------ generated html file: file://M:\py_tests\report\report.html ------------------------------------
=============================================== 1 failed, 2 passed, 3 rerun in 0.20s ===============================================

上面演示了用例失敗了,然后重新執行多少次都沒有成功,這是一種情況。

接下來,來看另一種情況,那就是用例執行失敗,重新執行次數內通過了,那么剩余的重新執行的次數將不再執行。

import random
import pytest

def test_case01():
    print('執行用例01.......')
    assert 1  # 斷言成功

def test_case02():
    print('執行用例02.......')
    status = random.randint(0, 2)
    if status:
        assert 1  # 斷言成功,無需再重復執行了
    else:
        assert 0  # 斷言失敗,需要重新執行
class TestCaseClass(object):

    def test_case_03(self):
        print('執行用例03.......')
        assert 1

通過random模塊幫助我們演示出在某次執行中出現失敗的情況,而在重新執行的時候,會出現成功的情況,看結果:

M:\py_tests>pytest
======================================================= test session starts ========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: M:\py_tests, inifile: pytest.ini, testpaths: ./scripts/
plugins: allure-pytest-2.8.6, html-2.0.0, metadata-1.8.0, ordering-0.6, rerunfailures-7.0
collected 3 items                                                                                                                   

scripts\test_case_01.py 執行用例01.......
.執行用例02.......
R執行用例02.......
.執行用例03.......
.

------------------------------------ generated html file: file://M:\py_tests\report\report.html ------------------------------------
==================================================== 3 passed, 1 rerun in 0.08s ====================================================

可以看到,用例02重新執行了一次就成功了,剩余的兩次執行就終止了。

並發執行

一條一條用例的執行,肯定會很慢,來看如何並發的執行測試用例,當然這需要相應的插件。

下載

pip install pytest-xdist

使用

在配置文件中添加:

[pytest]
addopts =  -v -s --html=report/report.html -n=auto
;addopts = -s --alluredir ./report/result
testpaths = ./scripts/
python_files = test_case_01.py
python_classes = Test*
python_functions = test_*

就是這個-n=auto

  • -n=auto,自動偵測系統里的CPU數目。

  • -n=numprocesses,也就是自己指定運行測試用例的進程數。

並發的配置可以寫在配置文件中,然后其他正常的執行用例腳本即可。另外一種就是在終端中指定,先來看示例:

import pytest

def test_case01():
    print('執行用例01.......')
    assert 1  # 斷言成功


@pytest.mark.skipif(condition= 2 > 1, reason='跳過用例')
def test_case02():
    print('執行用例02.......')
    assert 0  # 斷言失敗

class TestCaseClass(object):

    def test_case_03(self):
        print('執行用例03.......')
        assert 1

    def test_case_04(self):
        print('執行用例04.......')
        assert 1

結果:

M:\py_tests>pytest .\scripts\test_case_01.py -s -n auto
======================================================= test session starts ========================================================
platform win32 -- Python 3.6.2, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- c:\python36\python.exe
cachedir: .pytest_cache
metadata: {'Python': '3.6.2', 'Platform': 'Windows-10-10.0.14393-SP0', 'Packages': {'pytest': '5.2.2', 'py': '1.8.0', 'pluggy': '0.13
.0'}, 'Plugins': {'allure-pytest': '2.8.6', 'forked': '1.1.3', 'html': '2.0.0', 'metadata': '1.8.0', 'ordering': '0.6', 'rerunfailure
s': '7.0', 'xdist': '1.30.0'}, 'JAVA_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_201'}
rootdir: M:\py_tests, inifile: pytest.ini
plugins: allure-pytest-2.8.6, forked-1.1.3, html-2.0.0, metadata-1.8.0, ordering-0.6, rerunfailures-7.0, xdist-1.30.0
[gw0] win32 Python 3.6.2 cwd: M:\py_tests
[gw1] win32 Python 3.6.2 cwd: M:\py_tests
[gw2] win32 Python 3.6.2 cwd: M:\py_tests
[gw3] win32 Python 3.6.2 cwd: M:\py_tests
[gw0] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
[gw1] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
[gw2] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
[gw3] Python 3.6.2 (v3.6.2:5fd33b5, Jul  8 2017, 04:14:34) [MSC v.1900 32 bit (Intel)]
gw0 [4] / gw1 [4] / gw2 [4] / gw3 [4]
scheduling tests via LoadScheduling

scripts/test_case_01.py::test_case02
scripts/test_case_01.py::TestCaseClass::test_case_04
scripts/test_case_01.py::TestCaseClass::test_case_03
scripts/test_case_01.py::test_case01
[gw3] PASSED scripts/test_case_01.py::TestCaseClass::test_case_04
[gw0] PASSED scripts/test_case_01.py::test_case01
[gw2] PASSED scripts/test_case_01.py::TestCaseClass::test_case_03
[gw1] SKIPPED scripts/test_case_01.py::test_case02

------------------------------------ generated html file: file://M:\py_tests\report\report.html ---
=================================================== 3 passed, 1 skipped in 2.23s 

 

 

可參考我老師博客,更詳細哦https://www.cnblogs.com/Neeo/articles/11832655.html

 

 

 

 

 

 

 

 

結果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM