Python自動化之pytest框架使用詳解


pytest是一個非常成熟的全功能的Python測試框架,主要有以下幾個特點:

  • 簡單靈活,容易上手
  • 支持參數化
  • 能夠支持簡單的單元測試和復雜的功能測試,還可以用來做selenium/appnium等自動化測試、接口自動化測試(pytest+requests)
  • pytest具有很多第三方插件,並且可以自定義擴展,比較好用的如pytest-selenium(集成selenium)、pytest-html(完美html測試報告生成)、pytest-rerunfailures(失敗case重復執行)、pytest-xdist(多CPU分發)等
  • 測試用例的skip和xfail處理
  • 可以很好的和jenkins集成
  • report框架----allure 也支持了pytest
  1. 安裝
  2.  pip install -U pytest

   2.查看版本

    pytest --version

  3.用例編寫規范

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

運行參數

  • 無參數
    • 讀取路徑下符合條件的所有類、函數、方法全部執行
  •  -v 
    • 打印詳細運行日志
  • -s
    • 打印print輸出
  • -k
    • 跳過運行某個或某些用例
    • pytest -k '類名'
    • pytest -k '方法名
    • pytest -k '類名 and not 方法名'   #運行類里所有方法,不包含某個方法
  • -x
    • 遇到用例失敗立即停止運行
  • --maxfail
    • 用例失敗數達到某個設定的值停止運行
    • pytest --maxfail=[num]
  • -m
    • 運行所有@pytest.mark.[標記名] 標記的用例

 框架結構

與unittest類似,執行前后會執行setup,teardown來增加用例的前置和后置條件。pytest框架使用setup,teardown更為靈活,按照用例運行級別可以分為以下幾類

  • setup_module/teardown_module   模塊級別,在模塊始末調用
  • setup_function/teardown_function     函數級別,在函數始末調用(在類外部)
  • setup_class/teardown_class              類級別,每個類里面執行前后分別執行
  • setup_method/teardown_method       方法級別,在方法始末調用(在類中)
  • setup/teardown                               方法級別,在方法始末調用(在類中)

調用順序:setup_module > setup_class >setup_method > setup > teardown > teardown_method > teardown_class > teardown_module

for example:

#!/usr/bin/env python  # encoding: utf-8 
''' @Auther:chenshifeng @version: v1.0 @file: test_calc.py @time: 2020/9/14 9:39 PM '''
# 測試文件
import sys, os import pytest sys.path.append(os.pardir) from pythoncode.calc import Calculator # 模塊級別,在模塊始末調用
def setup_module(): print('模塊級別setup') def teardown_module(): print('模塊級別teardown') # 函數級別,在函數始末調用(在類外部)
def setup_function(): print('函數級別setup') def teardown_function(): print('函數級別teardown') def test_case1(): print('testcase1') class TestCalc: # setup_class,teardown_class 類級別每個類里面執行前后分別執行
    def setup_class(self): self.cal = Calculator() print('類級別setup') def teardown_class(self): print('類級別teardown') # 方法級別,每個方法里面的測試用例前后分別執行setup、teardown
    def setup(self): # self.cal = Calculator()
        print('setup') def teardown(self): print('teardown') # 方法級別,每個方法里面的測試用例前后分別執行setup、teardown
    def setup_method(self): # self.cal = Calculator()
        print('方法級別setup') def teardown_method(self): print('方法級別teardown') @pytest.mark.add def test_add1(self): # cal = Calculator()
        assert 3 == self.cal.add(1, 2) @pytest.mark.div def test_div(self): # cal = Calculator()
        assert 1 == self.cal.div(1, 1)

運行結果如下

Testing started at 11:05 下午 ... /usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_calc.py Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_calc.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode ============================= test session starts ============================== platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6 cachedir: .pytest_cache rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini collecting ... collected 3 items test_calc.py::test_case1 模塊級別setup 函數級別setup PASSED [ 33%]testcase1 函數級別teardown test_calc.py::TestCalc::test_add1 類級別setup 方法級別setup setup PASSED [ 66%]teardown 方法級別teardown test_calc.py::TestCalc::test_div 方法級別setup setup PASSED [100%]teardown 方法級別teardown 類級別teardown 模塊級別teardown ============================== 3 passed in 0.02s =============================== Process finished with exit code 0

pytest參數化

Pytest是使用@pytest.mark.parametrize裝飾器來實現數據驅動測試的

for example:

import pytest

@pytest.mark.parametrize('a,b,result', [
    (1, 1, 2),
    (2, 3, 5),
    (100, 200, 300)
])
def test_add(a, b, result):
    cal = Calculator()
    assert cal.add(a, b) == result

結果:

Testing started at 11:22 ...
"D:\Program Files\Python\python.exe" "D:\Program Files\JetBrains\PyCharm Community Edition 2020.2.1\plugins\python-ce\helpers\pycharm\_jb_pytest_runner.py" --target test_calc.py::test_add
Launching pytest with arguments test_calc.py::test_add in D:\chenshifeng\mycode\Python\test_pytest\testing

============================= test session starts =============================
platform win32 -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- D:\Program Files\Python\python.exe
cachedir: .pytest_cache
rootdir: D:\chenshifeng\mycode\Python, configfile: pytest.ini
collecting ... collected 3 items

test_calc.py::test_add[1-1-2] PASSED                                     [ 33%]
test_calc.py::test_add[2-3-5] PASSED                                     [ 66%]
test_calc.py::test_add[100-200-300] PASSED                               [100%]

============================== 3 passed in 0.03s ==============================

Process finished with exit code 0

修改結果顯示名稱

通過上面的運行結果,我們可以看到,為了區分參數化的運行結果,在結果中都會顯示數據組合而成的名稱。

數據短小還好說,如果數據比較長而復雜的話,那么就會很難看。

@pytest.mark.parametrize() 還提供了第三個 ids 參數來自定義顯示結果。

import pytest

# 參數化
@pytest.mark.parametrize('a,b,result', [
    (1, 1, 2),
    (2, 3, 5),
    (100, 200, 300)
], ids=['int0', 'int1', 'int2'])  # 修改結果顯示名稱
def test_add(a, b, result):
    cal = Calculator()
    assert cal.add(a, b) == result

結果:

============================= test session starts =============================
platform win32 -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- D:\Program Files\Python\python.exe
cachedir: .pytest_cache
rootdir: D:\chenshifeng\mycode\Python, configfile: pytest.ini
collecting ... collected 3 items

test_calc.py::test_add[int0] PASSED                                      [ 33%]
test_calc.py::test_add[int1] PASSED                                      [ 66%]
test_calc.py::test_add[int2] PASSED                                      [100%]

============================== 3 passed in 0.03s ==============================

Process finished with exit code 0

pytest fixtures

fixture用途

pytest fixture 與setup,teardown功能一樣,但比之更加靈活,完全可以代替setup,teardown

1.做測試前后的初始化設置,如測試數據准備,鏈接數據庫,打開瀏覽器等這些操作都可以使用fixture來實現

2.測試用例的前置條件可以使用fixture實現

3.支持經典的xunit fixture ,像unittest使用的setup和teardown

4.fixture可以實現unittest不能實現的功能,比如unittest中的測試用例和測試用例之間是無法傳遞參數和數據的,但是fixture卻可以解決這個問題

fixture定義

fixture通過@pytest.fixture()裝飾器裝飾一個函數,那么這個函數就是一個fixture

#!/usr/bin/python # -*- coding: UTF-8 -*-
""" @author:chenshifeng @file:test_login.py @time:2020/09/15 """
import pytest @pytest.fixture() def login(): print('登陸方法') yield   #激活fixture teardown方法
    print('teardown') # 測試用例之前,先執行login方法
def test_case1(login): print('case1') def test_case2(): print('case2') def test_case3(): print('case3')

運行結果如下:

test_login.py::test_case2 test_login.py::test_case3 ============================== 3 passed in 0.02s =============================== Process finished with exit code 0 登陸方法 PASSED [ 33%]case1 teardown PASSED [ 66%]case2 PASSED [100%]case3

 

fixture作用范圍(scope)

fixture里面有個scope參數可以控制fixture的作用范圍:session > module > class > function
  • - function 每一個函數或方法都會調用,默認為function 
  • - class  每一個類調用一次,一個類可以有多個方法
  • - module,每一個.py文件調用一次,該文件內又有多個function和class
  • - session 是多個文件調用一次,可以跨.py文件調用,每個.py文件就是module

cope='function'

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:test_login.py
@time:2020/09/15
"""
import pytest


@pytest.fixture()   #默認為scope='function'
def login():
    print('登陸方法')
    yield ['username','passwd']  #激活fixture teardown方法
    print('teardown')

# 測試用例之前,先執行login方法
def test_case1(login):
    print(f'case1 login={login}')

def test_case2(login):
    print('case2')

def test_case3(login):
    print('case3')

運行結果如下:

Testing started at 12:02 上午 ...
/usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py
Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode

============================= test session starts ==============================
platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 3 items

test_login.py::test_case1 
test_login.py::test_case2 
test_login.py::test_case3 

============================== 3 passed in 0.02s ===============================

Process finished with exit code 0
登陸方法
PASSED                                         [ 33%]case1 login=['username', 'passwd']
teardown
登陸方法
PASSED                                         [ 66%]case2
teardown
登陸方法
PASSED                                         [100%]case3
teardown

scope='module'

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:test_login.py
@time:2020/09/15
"""
import pytest


@pytest.fixture(scope='module')   #默認為scope='function'
def login():
    print('登陸方法')
    yield ['username','passwd']  #激活fixture teardown方法
    print('teardown')

# 測試用例之前,先執行login方法
def test_case1(login):
    print(f'case1 login={login}')

def test_case2(login):
    print('case2')

def test_case3(login):
    print('case3')
結果:
Testing started at 12:08 上午 ...
/usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py
Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode

============================= test session starts ==============================
platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 3 items

test_login.py::test_case1 
test_login.py::test_case2 
test_login.py::test_case3 

============================== 3 passed in 0.02s ===============================

Process finished with exit code 0
登陸方法
PASSED                                         [ 33%]case1 login=['username', 'passwd']
PASSED                                         [ 66%]case2
PASSED                                         [100%]case3
teardown 

fixture自動調用(autouse=True)

autouse設置為True,自動調用fixture功能,無需額外繼承

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:test_search.py
@time:2020/09/16
"""

import pytest


@pytest.fixture(autouse=True)  # 默認為scope='function'
def login():
    print('登陸方法')
    yield ['username', 'passwd']  # 激活fixture teardown方法
    print('teardown')
    
def test_search1():  #無需繼承login print('搜索用例1')

def test_search2():
    print('搜索用例2')

結果:

============================= test session starts ==============================
platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 2 items

test_search.py::test_search1 登陸方法
PASSED                                      [ 50%]搜索用例1
teardown

test_search.py::test_search2 登陸方法
PASSED                                      [100%]搜索用例2
teardown


============================== 2 passed in 0.01s ===============================

Process finished with exit code 0

fixture參數化(params)

@pytest.fixture有一個params參數,接受一個列表,列表中每個數據都可以作為用例的輸入。也就說有多少數據,就會形成多少用例。可以通過'''request.param'''來獲取該次調用的參數

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:test_login.py
@time:2020/09/15
"""
import pytest


@pytest.fixture(params=['user1', 'user2', 'user3'])
def login(request):
    print('登陸方法')
    print('傳入的參數為:'+request.param)  # 獲取params參數
    yield ['username', 'passwd']  # 激活fixture teardown方法
    print('teardown')

# 測試用例之前,先執行login方法
def test_case1(login):
    print(f'case1 login={login}')

def test_case2(login):
    print('case2')

def test_case3(login):
    print('case3')

結果:

============================= test session starts ==============================
platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 9 items

test_login.py::test_case1[user1] 
test_login.py::test_case1[user2] 
test_login.py::test_case1[user3] 
test_login.py::test_case2[user1] 
test_login.py::test_case2[user2] 
test_login.py::test_case2[user3] 
test_login.py::test_case3[user1] 登陸方法
傳入的參數為:user1
PASSED                                  [ 11%]case1 login=['username', 'passwd']
teardown
登陸方法
傳入的參數為:user2
PASSED                                  [ 22%]case1 login=['username', 'passwd']
teardown
登陸方法
傳入的參數為:user3
PASSED                                  [ 33%]case1 login=['username', 'passwd']
teardown
登陸方法
傳入的參數為:user1
PASSED                                  [ 44%]case2
teardown
登陸方法
傳入的參數為:user2
PASSED                                  [ 55%]case2
teardown
登陸方法
傳入的參數為:user3
PASSED                                  [ 66%]case2
teardown
登陸方法
傳入的參數為:user1
PASSED                                  [ 77%]case3
teardown

test_login.py::test_case3[user2] 登陸方法
傳入的參數為:user2
PASSED                                  [ 88%]case3
teardown

test_login.py::test_case3[user3] 登陸方法
傳入的參數為:user3
PASSED                                  [100%]case3
teardown


============================== 9 passed in 0.06s ===============================

Process finished with exit code 0

參數化與fixture結合(indirect=True)

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:test_cart.py
@time:2020/09/16
"""
import pytest


@pytest.fixture(params=['user1', 'user2', 'user3'])
def login(request):
    print('登陸方法')
    print('傳入的參數為:' + str(request.param))  # 獲取params參數
    yield ['username', 'passwd']  # 激活fixture teardown方法
    print('teardown')


# 參數化結合fixture使用
# 情況一:傳入值和數據
# 情況二:傳入一個fixture方法,將數據傳入到fixture方法中,fixture使用request參數來接受這組數據,在方法體中使用request.param來接受這個數據
@pytest.mark.parametrize('login', [
    ('username1', 'passwd1'),
    ('username2', 'passwd2')
], indirect=True)
def test_cart3(login):
    print('購物車用例3')

結果:

============================= test session starts ==============================
platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
collecting ... collected 2 items

test_cart.py::test_cart3[login0] 
test_cart.py::test_cart3[login1] 

============================== 2 passed in 0.02s ===============================

Process finished with exit code 0
登陸方法
傳入的參數為:('username1', 'passwd1')
PASSED                                  [ 50%]購物車用例3
teardown
登陸方法
傳入的參數為:('username2', 'passwd2')
PASSED                                  [100%]購物車用例3
teardown

conftest.py

1.conftest.py文件名字是固定的,不可以做任何修改

2.文件和用例文件在同一個目錄下,那么conftest.py作用於整個目錄

3.conftest.py文件不能被其他文件導入

4.所有同目錄測試文件運行前都會執行conftest.py文件

#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:conftest.py
@time:2020/09/15
"""

import pytest


@pytest.fixture(params=['user1', 'user2', 'user3'])
def login(request):
    print('登陸方法')
    print('傳入的參數為:'+str(request.param))  # 獲取params參數
    yield ['username', 'passwd']  # 激活fixture teardown方法
    print('teardown'
#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:test_login.py
@time:2020/09/15
"""


# 測試用例之前,先執行login方法
import pytest


def test_case1(login):
    print(f'case1 login={login}')

@pytest.mark.usefixtures('login')
def test_case2():
    print('case2')
    # print(f'case1 login={login}')  #該方法無法獲取返回值


def test_case3(login):
    print('case3')

運行test_login.py文件,結果如下

============================= test session starts ==============================
platform darwin -- Python 3.9.0, pytest-6.1.1, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.9
cachedir: .pytest_cache
rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
plugins: allure-pytest-2.8.18
collecting ... collected 9 items

test_login.py::test_case1[user1] 
test_login.py::test_case1[user2] 
test_login.py::test_case1[user3] 
test_login.py::test_case2[user1] 
test_login.py::test_case2[user2] 
test_login.py::test_case2[user3] 
test_login.py::test_case3[user1] 登陸方法
傳入的參數為:user1
PASSED                                  [ 11%]case1 login=['username', 'passwd']
teardown
登陸方法
傳入的參數為:user2
PASSED                                  [ 22%]case1 login=['username', 'passwd']
teardown
登陸方法
傳入的參數為:user3
PASSED                                  [ 33%]case1 login=['username', 'passwd']
teardown
登陸方法
傳入的參數為:user1
PASSED                                  [ 44%]case2
teardown
登陸方法
傳入的參數為:user2
PASSED                                  [ 55%]case2
teardown
登陸方法
傳入的參數為:user3
PASSED                                  [ 66%]case2
teardown
登陸方法
傳入的參數為:user1
PASSED                                  [ 77%]case3
teardown

test_login.py::test_case3[user2] 登陸方法
傳入的參數為:user2
PASSED                                  [ 88%]case3
teardown

test_login.py::test_case3[user3] 登陸方法
傳入的參數為:user3
PASSED                                  [100%]case3
teardown


============================== 9 passed in 0.04s ===============================

Process finished with exit code 0

 end


免責聲明!

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



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