pytest框架

Python - pytest

快速入門
回到頂部

pytest是Python的單元測試框架,同自帶的unittest框架相似,但pytest框架使用起來更簡潔,效率更高。php

pytest特色css

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

安裝html

pip install pytest

測試python

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

在測試以前要作的準備linux

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

M:\py_tests\ # 個人是M盤的 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

踩坑:你建立的pytest腳本名稱中不容許含有.,好比1.簡單上手.py,這樣會報錯。固然,能夠這麼寫1-簡單上手.pygithub

簡單示例
回到頂部

demo1.pyweb

import pytest

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

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

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

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

上例中,當咱們在執行(就像Python解釋器執行普通的Python腳本同樣)測試用例的時候,pytest.main(["-s", "demo1.py"])中的傳參須要是一個元組或者列表(個人pytest是5.2.2版本),以前的版本可能須要這麼調用pytest.main("-s demo1.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 __________________________________________________________

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">()</span>:</span>
    print(<span class="hljs-string">'執行用例01.......'</span>)

> assert 0 # 斷言失敗
E assert 0

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">()</span>:</span> print(<span class="hljs-string">'執行用例01.......'</span>)demo1.py:11: AssertionError
================================================= 1 failed, 1 passed in 0.13s ==================================================

大體的信息就是告訴咱們:

  • collected 2 items:本次執行中,收集了2個用例。
  • 完了開始執行用例,.表示執行成功,F表示執行失敗。
  • 腳本中的第一個用例執行失敗;第二個用例執行成功;可是第三個也就是custom_case03並無執行,由此咱們知道,pytest只識別以test_開頭的用例。

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

  • -s,在控制檯中輸出print的結果,有些狀況下,ptest不會展現print的輸出,因此使用-s參數來實現。
  • demo1.py是要執行的腳本名稱。

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

import pytest

class TestCase(object):

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-string">""" 用例 01 """</span>
    print(<span class="hljs-string">'執行用例01.......'</span>)
    <span class="hljs-keyword">assert</span> <span class="hljs-number">0</span>  <span class="hljs-comment"># 斷言失敗</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case02</span><span class="hljs-params">(slef)</span>:</span>
    <span class="hljs-string">""" 用例 02 """</span>
    print(<span class="hljs-string">'執行用例02.......'</span>)
    <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>  <span class="hljs-comment"># 斷言成功</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-string">""" 用例 01 """</span> print(<span class="hljs-string">'執行用例01.......'</span>) <span class="hljs-keyword">assert</span> <span class="hljs-number">0</span> <span class="hljs-comment"># 斷言失敗</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case02</span><span class="hljs-params">(slef)</span>:</span> <span class="hljs-string">""" 用例 02 """</span> print(<span class="hljs-string">'執行用例02.......'</span>) <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span> <span class="hljs-comment"># 斷言成功</span>if name == 'main':
pytest.main(["-s", "demo1.py"])

用法跟unittest差很少,類名要以Test開頭,而且其中的用例方法也要以test開頭,而後執行也同樣。

執行結果:

M:\py_tests>python demo1.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>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-string">""" 用例 01 """</span>
    print(<span class="hljs-string">'執行用例01.......'</span>)

> assert 0 # 斷言失敗
E assert 0

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-string">""" 用例 01 """</span> print(<span class="hljs-string">'執行用例01.......'</span>)demo1.py:49: AssertionError
====================================================== 1 failed, 1 passed in 0.12s =======================================================

那麼,你這個時候可能會問,我記得unittest中有setup和teardown的方法,難道pytest中沒有嘛?你怎麼提都不提?穩住,答案是有的。

接下來,咱們來研究一下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"])

執行結果:

M:\py_tests>python demo1.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 _______________________________________________________________

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">()</span>:</span>
    print(<span class="hljs-string">'執行用例01.......'</span>)

> assert 0 # 斷言失敗
E assert 0

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">()</span>:</span> print(<span class="hljs-string">'執行用例01.......'</span>)demo1.py:16: AssertionError
====================================================== 1 failed, 1 passed in 0.12s =======================================================

類級別的setup_class/teardown_class

import pytest

class TestCase(object):

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setup_class</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-string">""" 類級別的 setup,在該類中內用例集執行以前觸發執行 """</span>
    print(<span class="hljs-string">'類級別的 setup.....'</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">teardown_class</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-string">""" 類級別的 teardown,在該類中內用例集執行以後觸發執行 """</span>
    print(<span class="hljs-string">'類級別的 teardown.....'</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-string">""" 用例 01 """</span>
    print(<span class="hljs-string">'執行用例01.......'</span>)
    <span class="hljs-keyword">assert</span> <span class="hljs-number">0</span>  <span class="hljs-comment"># 斷言失敗</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case02</span><span class="hljs-params">(slef)</span>:</span>
    <span class="hljs-string">""" 用例 02 """</span>
    print(<span class="hljs-string">'執行用例02.......'</span>)
    <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>  <span class="hljs-comment"># 斷言成功</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setup_class</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-string">""" 類級別的 setup,在該類中內用例集執行以前觸發執行 """</span> print(<span class="hljs-string">'類級別的 setup.....'</span>) <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">teardown_class</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-string">""" 類級別的 teardown,在該類中內用例集執行以後觸發執行 """</span> print(<span class="hljs-string">'類級別的 teardown.....'</span>) <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-string">""" 用例 01 """</span> print(<span class="hljs-string">'執行用例01.......'</span>) <span class="hljs-keyword">assert</span> <span class="hljs-number">0</span> <span class="hljs-comment"># 斷言失敗</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case02</span><span class="hljs-params">(slef)</span>:</span> <span class="hljs-string">""" 用例 02 """</span> print(<span class="hljs-string">'執行用例02.......'</span>) <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span> <span class="hljs-comment"># 斷言成功</span>if name == 'main':
pytest.main(["-s", "demo1.py"])

執行結果:

M:\py_tests>python demo1.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):

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setup_method</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-string">""" 類中方法級別的 setup,在該類中內每一個用例執行以前觸發執行 """</span>
    print(<span class="hljs-string">'類中方法級別的 setup.....'</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">teardown_method</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-string">""" 類中方法級別的 teardown,在該類中內每一個用例執行以後觸發執行 """</span>
    print(<span class="hljs-string">'類中方法級別的 teardown.....'</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-string">""" 用例 01 """</span>
    print(<span class="hljs-string">'執行用例01.......'</span>)
    <span class="hljs-keyword">assert</span> <span class="hljs-number">0</span>  <span class="hljs-comment"># 斷言失敗</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case02</span><span class="hljs-params">(slef)</span>:</span>
    <span class="hljs-string">""" 用例 02 """</span>
    print(<span class="hljs-string">'執行用例02.......'</span>)
    <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>  <span class="hljs-comment"># 斷言成功</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setup_method</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-string">""" 類中方法級別的 setup,在該類中內每一個用例執行以前觸發執行 """</span> print(<span class="hljs-string">'類中方法級別的 setup.....'</span>) <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">teardown_method</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-string">""" 類中方法級別的 teardown,在該類中內每一個用例執行以後觸發執行 """</span> print(<span class="hljs-string">'類中方法級別的 teardown.....'</span>) <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-string">""" 用例 01 """</span> print(<span class="hljs-string">'執行用例01.......'</span>) <span class="hljs-keyword">assert</span> <span class="hljs-number">0</span> <span class="hljs-comment"># 斷言失敗</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case02</span><span class="hljs-params">(slef)</span>:</span> <span class="hljs-string">""" 用例 02 """</span> print(<span class="hljs-string">'執行用例02.......'</span>) <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span> <span class="hljs-comment"># 斷言成功</span>if name == 'main':
pytest.main(["-s", "demo1.py"])

執行結果:

M:\py_tests>python demo1.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>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-string">""" 用例 01 """</span>
    print(<span class="hljs-string">'執行用例01.......'</span>)

> assert 0 # 斷言失敗
E assert 0

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-string">""" 用例 01 """</span> print(<span class="hljs-string">'執行用例01.......'</span>)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"])

執行結果:

M:\py_tests>python demo1.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 _______________________________________________________________

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">()</span>:</span>
    print(<span class="hljs-string">'執行用例01.......'</span>)

> assert 0 # 斷言失敗
E assert 0

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case01</span><span class="hljs-params">()</span>:</span> print(<span class="hljs-string">'執行用例01.......'</span>)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 函數

也能夠在命令行中運行:

M:\py_tests>python demo1.py

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

M:\py_tests>pytest -s demo1.py

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

在項目的根目錄下,咱們能夠創建一個pytest.ini文件,在這個文件中,咱們能夠實現相關的配置:

[pytest]
addopts = -s -v
testpaths = ./scripts
python_files = test_*.py
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):

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_03</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-keyword">assert</span> <span class="hljs-number">0</span>  <span class="hljs-comment"># 斷言失敗</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_03</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-keyword">assert</span> <span class="hljs-number">0</span> <span class="hljs-comment"># 斷言失敗</span>

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>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_03</span><span class="hljs-params">(self)</span>:</span>

> assert 0
E assert 0

scripts\test_case_01.py:22: AssertionError
___________________________________________________________ test_case_05 ___________________________________________________________

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_05</span><span class="hljs-params">()</span>:</span>

> assert 0
E assert 0

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_03</span><span class="hljs-params">(self)</span>:</span><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_05</span><span class="hljs-params">()</span>:</span>scripts\test_case_dir1\test_case_02.py:14: AssertionError
=================================================== 2 failed, 3 passed in 0.14s ====================================================

由執行結果能夠發現,2 failed, 3 passed ,跟咱們的預期一致。

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

進階
回到頂部

跳過用例
回到頂部

咱們知道在unittest中,跳過用例能夠用skip,那麼這一樣是適用於pytest。

來看怎麼使用:

import pytest

@pytest.mark.skip(condition='我就是要跳過這個用例啦')
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,以前的示例結果中,沒有加!
另外,此時,在輸出的控制檯中, 還沒法打印出reason信息,若是須要打印,則能夠在配置文件中的addopts參數的-s變爲-rs

[pytest]
addopts = -rs -v
testpaths = ./scripts
python_files = test_*.py
python_classes = Test*
python_functions = test_*

標記預期失敗
回到頂部

若是咱們事先知道測試函數會執行失敗,但又不想直接跳過,而是但願顯示的提示。

Pytest 使用 pytest.mark.xfail 實現預見錯誤功能::

xfail(condiition, reason, [raises=None, run=True, strict=False])

須要掌握的必傳參數的是:

  • condition,預期失敗的條件,當條件爲真的時候,預期失敗。
  • reason,失敗的緣由。

那麼關於預期失敗的幾種狀況須要瞭解一下:

  • 預期失敗,但實際結果卻執行成功。
  • 預期失敗,實際結果也執行執行失敗。
    來看示例:

import pytest

class TestCase(object):

@pytest.mark.xfail(1 < 2, reason='預期失敗, 執行失敗')
def test_case_01(self):
""" 預期失敗, 執行也是失敗的 """
print('預期失敗, 執行失敗')
assert 0

@pytest.mark.xfail(1 < 2, reason='預期失敗, 執行成功')
def test_case_02(self):
""" 預期失敗, 但實際執行結果卻成功了 """
print('預期失敗, 執行成功')
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, cov-2.8.1, forked-1.1.3, html-2.0.0, metadata-1.8.0, ordering-0.6, rerunfailures-7.0, xdist-1.30.0 collected 2 items

scripts\demo1.py xX [100%]

===================================================== 1 xfailed, 1 xpassed in 0.15s =================================================

pytest 使用 x 表示預見的失敗(XFAIL)。

若是預見的是失敗,但實際運行測試卻成功經過,pytest 使用 X 進行標記(XPASS)。

而在預期失敗的兩種狀況中,咱們不但願出現預期失敗,結果卻執行成功了的狀況出現,由於跟咱們想的不同嘛,我預期這條用例失敗,那這條用例就應該執行失敗纔對,你雖然執行成功了,但跟我想的不同,你照樣是失敗的!

因此,咱們須要將預期失敗,結果卻執行成功了的用例標記爲執行失敗,能夠在pytest.ini文件中,加入:

[pytest]
xfail_strict=true

這樣就就把上述的狀況標記爲執行失敗了。

參數化
回到頂部

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 ======================================================

固件
回到頂部

什麼是固件
回到頂部

固件(Fixture)是一些函數,pytest 會在執行測試函數以前(或以後)加載運行它們,也稱測試夾具。

咱們能夠利用固件作任何事情,其中最多見的可能就是數據庫的初始鏈接和最後關閉操做。

Pytest 使用 pytest.fixture() 定義固件,下面是最簡單的固件,訪問主頁前必須先登陸:

import pytest

@pytest.fixture()
def login():
print('登陸....')

def test_index(login):
print('主頁....')

結果:

M:\py_tests>pytest

scripts/test_case_01.py::test_index 登陸....
主頁....
PASSED

====================================================== 1 passed in 0.13s =======================================================

做用域
回到頂部

在以前的示例中,你可能會以爲,這跟以前的setup和teardown的功能也相似呀,可是,fixture相對於setup和teardown來講更靈活。pytest經過scope參數來控制固件的使用範圍,也就是做用域。

在定義固件時,經過 scope 參數聲明做用域,可選項有:

  • function: 函數級,每一個測試函數都會執行一次固件;
  • class: 類級別,每一個測試類執行一次,全部方法均可以使用;
  • module: 模塊級,每一個模塊執行一次,模塊內函數和方法均可使用;
  • session: 會話級,一次測試只執行一次,全部被找到的函數和方法均可用。

默認的做用域爲 function

好比以前的login固件,能夠指定它的做用域:

import pytest

@pytest.fixture(scope='function')
def login():
print('登陸....')

def test_index(login):
print('主頁....')

預處理和後處理
回到頂部

不少時候須要在測試前進行預處理(如新建數據庫鏈接),並在測試完成進行清理(關閉數據庫鏈接)。

當有大量重複的這類操做,最佳實踐是使用固件來自動化全部預處理和後處理。

Pytest 使用 yield 關鍵詞將固件分爲兩部分,yield 以前的代碼屬於預處理,會在測試前執行;yield 以後的代碼屬於後處理,將在測試完成後執行。

如下測試模擬數據庫查詢,使用固件來模擬數據庫的鏈接關閉:

import pytest

@pytest.fixture()
def db():
print('Connection successful')

<span class="hljs-keyword">yield</span>

print(<span class="hljs-string">'Connection closed'</span>)

def search_user(user_id):
d = {
'001': 'xiaoming',
'002': 'xiaohua'
}
return d[user_id]

def test_case_01(db):
assert search_user('001') == 'xiaoming'

<span class="hljs-keyword">yield</span> print(<span class="hljs-string">'Connection closed'</span>)def test_case_02(db):
assert search_user('002') == 'xiaohua'

結果:

M:\py_tests>pytest

scripts/test_case_01.py::test_case_01 Connection successful
PASSEDConnection closed

scripts/test_case_01.py::test_case_02 Connection successful
PASSEDConnection closed

====================================================== 2 passed in 0.15s =======================================================

能夠看到在兩個測試用例執行先後都有預處理和後處理。

經常使用插件
回到頂部

pytest中還有很是多的插件供咱們使用,咱們來介紹幾個經常使用的。

先來看一個重要的,那就是生成測試用例報告。

pytest測試報告插件
回到頂部

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

下載

pip install pytest-html

https://github.com/pytest-dev/pytest-html

若是下載失敗,可使用PyCharm下載,怎麼用PyCharm下載這裏無需多言了吧。

使用

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

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

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

效果很不錯吧!

沒完,看我大招 ↓

allure
回到頂部

Allure框架是一個靈活的輕量級多語言測試報告工具,它不只以web的方式展現了簡介的測試結果,並且容許參與開發過程的每一個人從平常執行的測試中最大限度的提取有用信息。
從開發人員(dev,developer)和質量保證人員(QA,Quality Assurance)的角度來看,Allure報告簡化了常見缺陷的統計:失敗的測試能夠分爲bug和被中斷的測試,還能夠配置日誌、步驟、fixture、附件、計時、執行歷史以及與TMS和BUG管理系統集成,因此,經過以上配置,全部負責的開發人員和測試人員能夠儘量的掌握測試信息。
從管理者的角度來看,Allure提供了一個清晰的「大圖」,其中包括已覆蓋的特性、缺陷彙集的位置、執行時間軸的外觀以及許多其餘方便的事情。allure的模塊化和可擴展性保證了咱們老是可以對某些東西進行微調。

少扯點,來看看怎麼使用。

Python的pytest中allure下載

pip install allure-pytest

但因爲這個allure-pytest插件生成的測試報告不是html類型的,咱們還須要使用allure工具再「加工」一下。因此說,咱們還須要下載這個allure工具。

allure工具下載

在如今allure工具以前,它依賴Java環境,咱們還須要先配置Java環境。

PS:Java JDK安裝包也在下面的百度雲連接中。

Java JDK for Windows

Java JDK for Mac

Java JDK for linux

注意,若是你的電腦已經有了Java環境,就無需從新配置了。

配置完了Java環境,咱們再來下載allure工具,我這裏直接給出了百度雲盤連接,你也能夠去其餘連接中自行下載:

https://github.com/allure-framework/allure2

優先選擇:https://bintray.com/qameta/maven/allure2

百度雲盤連接:連接:https://pan.baidu.com/s/1Xj1A_xsRscOZHskTR4xjAg 提取碼:6b33

下載並解壓好了allure工具包以後,還須要將allure包內的bin目錄添加到系統的環境變量中。

完過後打開你的終端測試:

C:\Users\Anthony\Desktop>allure --version
2.10.0

返回了版本號說明安裝成功。

使用

通常使用allure要經歷幾個步驟:

  • 配置pytest.ini文件。
  • 編寫用例並執行。
  • 使用allure工具生成html報告。

來看配置pytest.ini

[pytest]
addopts =  -v -s --html=report/report.html --alluredir ./report/result
testpaths = ./scripts/
python_files = test_allure_case.py
python_classes = Test*
python_functions = test_*
# xfail_strict=true

就是--alluredir ./report/result參數。

在終端中輸入pytest正常執行測試用例便可:

import pytest

def test_case_01():
assert 1

def test_case_02():
assert 0

def test_case_03():
assert 1

執行完畢後,在項目的根目下,會自動生成一個report目錄,這個目錄下有:

  • report.html是咱們的以前的pytest-html插件生成的HTML報告,跟allure無關。
  • result和assets目錄是allure插件生成的測試報告文件,但此時該目錄內尚未什麼HTML報告,只有一些相關數據。

接下來須要使用allure工具來生成HTML報告。

此時咱們在終端(若是是windows平臺,就是cmd),路徑是項目的根目錄,執行下面的命令。

PS:我在pycharm中的terminal輸入allure提示'allure' 不是內部或外部命令,也不是可運行的程序或批處理文件。但windows的終端沒有問題。

M:\py_tests>allure generate report/result -o report/allure_html --clean
Report successfully generated to report\allure_html

命令的意思是,根據report\result目錄中的數據(這些數據是運行pytest後產生的)。在report目錄下新建一個allure_html目錄,而這個目錄內有index.html纔是最終的allure版本的HTML報告;若是你是重複執行的話,使用--clean清除以前的報告。

結果很漂亮:

allure open
默認的,allure報告須要HTTP服務器來打開,通常咱們能夠經過pycharm來完成,另一種狀況就是經過allure自帶的open命令來完成。

allure的其餘用法
固然,故事仍是沒有完!在使用allure生成報告的時候,在編寫用例階段,還能夠有一些參數可使用:

  • title,自定義用例標題,標題默認是用例名。
  • description,測試用例的詳細說明。
  • feature和story被稱爲行爲驅動標記,由於使用這個兩個標記,經過報告能夠更加清楚的掌握每一個測試用例的功能和每一個測試用例的測試場景。或者你能夠理解爲feature是模塊,而story是該模塊下的子模塊。
  • allure中對bug的嚴重(severity)級別也有定義,allure使用severity來標識測試用例或者測試類的bug級別,分爲blocker,critical,normal,minor,trivial5個級別。通常,bug分爲以下幾個級別:
    • Blocker級別:中斷缺陷(客戶端程序無響應,沒法執行下一步操做),系統沒法執行、崩潰或嚴重資源不足、應用模塊沒法啓動或異常退出、沒法測試、形成系統不穩定。
    • Critical級別:即影響系統功能或操做,主要功能存在嚴重缺陷,但不會影響到系統穩定性。好比說一個服務直接不可用了,微信不能發消息,支付寶不能付款這種,打開直接報錯。
    • Major:即界面、性能缺陷、兼容性。如操做界面錯誤(包括數據窗口內列名定義、含義是否一致)、長時間操做無進度提示等。
    • Normal級別:普通缺陷(數值計算錯誤),是指非核心業務流程產生的問題,好比說知乎沒法變動頭像,暱稱等。這個要看本身的定義。
    • Minor/Trivial級別:輕微缺陷(必輸項無提示,或者提示不規範),好比各類影響體驗,但不影響使用的內容。
  • dynamic,動態設置相關參數。

allure.title與allure.description

import pytest import allure

@allure.title('測試用例標題1')
@allure.description('這是測試用例用例1的描述信息')
def test_case_01():
assert 1

def test_case_02():
assert 0

def test_case_03():
assert 1

feature和story

import pytest import allure

@allure.feature('登陸模塊')
class TestCaseLogin(object):

@allure.story('登陸模塊下的子模塊: test1')
def test_case_01(self):
assert 1

@allure.story('登陸模塊下的子模塊: test1')
def test_case_02(self):
assert 1

@allure.story('登陸模塊下的子模塊: test2')
def test_case_03(self):
assert 1

@allure.story('登陸模塊下的子模塊: test3')
def test_case_04(self):
assert 1

@allure.feature('註冊模塊')
class TestCaseRegister(object):
@allure.story('註冊模塊下的子模塊: test1')
def test_case_01(self):
assert 1

@allure.story('註冊模塊下的子模塊: test1')
def test_case_02(self):
assert 1

@allure.story('註冊模塊下的子模塊: test1')
def test_case_03(self):
assert 1

@allure.story('註冊模塊下的子模塊: test2')
def test_case_04(self):
assert 1

由上圖能夠看到,不一樣的用例被分爲不一樣的功能中。

allure.severity

allure.severity用來標識測試用例或者測試類的級別,分爲blocker,critical,normal,minor,trivial5個級別。

import pytest import allure

@allure.feature('登陸模塊')
class TestCaseLogin(object):

@allure.severity(allure.severity_level.BLOCKER)
def test_case_01(self):
assert 1

@allure.severity(allure.severity_level.CRITICAL)
def test_case_02(self):
assert 1

@allure.severity(allure.severity_level.MINOR)
def test_case_03(self):
assert 1

@allure.severity(allure.severity_level.TRIVIAL)
def test_case_04(self):
assert 1

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_05</span><span class="hljs-params">(self)</span>:</span>
    <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_05</span><span class="hljs-params">(self)</span>:</span> <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>

severity的默認級別是normal,因此上面的用例5能夠不添加裝飾器了。

allure.dynamic

import pytest import allure

@allure.feature('登陸模塊')
class TestCaseLogin(object):

@allure.severity(allure.severity_level.BLOCKER)
def test_case_01(self):
assert 1

@allure.severity(allure.severity_level.CRITICAL)
def test_case_02(self):
assert 1

@allure.severity(allure.severity_level.MINOR)
def test_case_03(self):
assert 1

@allure.severity(allure.severity_level.TRIVIAL)
def test_case_04(self):
assert 1
@pytest.mark.parametrize('name', ['動態名稱1', '動態名稱2'])
def test_case_05(self, name):
allure.dynamic.title(name)

控制用例執行順序
回到頂部

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

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):

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_03</span><span class="hljs-params">(self)</span>:</span>
    print(<span class="hljs-string">'執行用例03.......'</span>)
    <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_03</span><span class="hljs-params">(self)</span>:</span> print(<span class="hljs-string">'執行用例03.......'</span>) <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>

結果:

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\testcase01.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\testcase01.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):

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_03</span><span class="hljs-params">(self)</span>:</span>
    print(<span class="hljs-string">'執行用例03.......'</span>)
    <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_03</span><span class="hljs-params">(self)</span>:</span> print(<span class="hljs-string">'執行用例03.......'</span>) <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>

經過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):

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_03</span><span class="hljs-params">(self)</span>:</span>
    print(<span class="hljs-string">'執行用例03.......'</span>)
    <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_04</span><span class="hljs-params">(self)</span>:</span>
    print(<span class="hljs-string">'執行用例04.......'</span>)
    <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_03</span><span class="hljs-params">(self)</span>:</span> print(<span class="hljs-string">'執行用例03.......'</span>) <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test_case_04</span><span class="hljs-params">(self)</span>:</span> print(<span class="hljs-string">'執行用例04.......'</span>) <span class="hljs-keyword">assert</span> <span class="hljs-number">1</span>

結果:

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 ===================================================

pytest-sugar
回到頂部

pytest-sugar 改變了 pytest 的默認外觀,添加了一個進度條,並當即顯示失敗的測試。它不須要配置,只需 下載插件便可,用 pytest 運行測試,來享受更漂亮、更有用的輸出。

下載

pip install pytest-sugar

其餘照舊執行用例便可。

pytest-cov
回到頂部

pytest-covpytest 中增長了覆蓋率支持,來顯示哪些代碼行已經測試過,哪些尚未。它還將包括項目的測試覆蓋率。

下載

pip install pytest-cov

使用

在配置文件中:

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

也就是配置--cov=./scripts,這樣,它就會統計全部scripts目錄下全部符合規則的腳本的測試覆蓋率。

執行的話,就照常執行就行。

結果:

M:\py_tests>pytest Test session starts (platform: win32, Python 3.6.2, pytest 5.2.2, p ytest-sugar 0.9.2) cachedir: .pytest_cache metadata: {'Python': '3.6.2', 'Platform': 'Windows-10-10.0.14393-SP 0', 'Packages': {'pytest': '5.2.2', 'py': '1.8.0', 'pluggy': '0.13. 0'}, 'Plugins': {'allure-pytest': '2.8.6', 'cov': '2.8.1', 'forked' : '1.1.3', 'html': '2.0.0', 'metadata': '1.8.0', 'ordering': '0.6', 'rerunfailures': '7.0', 'sugar': '0.9.2', 'xdist': '1.30.0'}, 'JAV A_HOME': 'C:\\Program Files\\Java\\jdk1.8.0_201'} rootdir: M:\py_tests, inifile: pytest.ini, testpaths: ./scripts/ plugins: allure-pytest-2.8.6, cov-2.8.1, forked-1.1.3, html-2.0.0, metadata-1.8.0, ordering-0.6, rerunfailures-7.0, sugar-0.9.2, 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 s 50% █████
pts\test_case_01.py::test_case01 ✓ 25% ██▌
] PASSED scripts/test_case_01.py

scripts\test_case_01.py::TestCaseClass.test_case_03 ✓ 75% ████
███▌

scripts\test_case_01.py::TestCaseClass.test_case_04 ✓ 100% ████
██████ scripts/test_case_01.py
Coverage.py warning: No data was collected. (no-data-collected)

----------------------------------------- generated html file: file://M:\py_tests\report\report.html -----------------------------------------

----------- coverage: platform win32, python 3.6.2-final-0 -----------
Name Stmts Miss Cover

scripts\demo1.py 4 4 0%
scripts\test_allure_case.py 7 7 0%
scripts\test_case_01.py 15 2 87%

TOTAL 26 13 50%

Results (2.58s):
3 passed
1 skipped

更多插件參考:https://zhuanlan.zhihu.com/p/50317866

常見問題
回到頂部

有的時候,在pytest.ini中配置了pytest-htmlallure插件以後,執行後報錯:

出現了這個報錯,檢查你配置的解釋器中是否存在pytest-htmlallure-pytest這兩個模塊。若是是使用的pycharm ide,那麼你除了檢查settings中的解釋器配置以外,還須要保證運行腳本的編輯器配置是否跟settings中配置一致。


see also:

8 個很棒的 pytest 插件 | Pytest【pytest-xdist】並行運行測試用例 | Pytest 使用手冊 | pytest參數化 |Allure-pytest功能特性介紹

相關文章
相關標籤/搜索