Pytest學(xué)習(xí)筆記(非完整版)

Pytest學(xué)習(xí)筆記

記錄下pytest官方文檔的閱讀筆記,以便后續(xù)參考使用。非完整版,個(gè)人理解為主,難免有誤,望理解。

安裝與快速使用

安裝

$ pip install pytest
$ pytest --version

第一個(gè)test

01\test_sample.py

def func(x):
    return x + 1


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

運(yùn)行

# 默認(rèn)會(huì)執(zhí)行當(dāng)前目錄及子目錄的所有test_*.py或*_test.py文件。用例執(zhí)行成功為.,失敗為F
$ pytest

# 靜默執(zhí)行
$ pytest -q 01\test_sample.py

# 調(diào)試方式執(zhí)行,可以打印print日志等詳情信息
$ pytest 01\test_sample.py -s -v

# python模塊方式執(zhí)行
$ python -m pytest 01\test_sample.py

# 執(zhí)行單個(gè)目錄下的tests
$ python 01\

test類包含多個(gè)tests

01\test_class.py

# pytest默認(rèn)會(huì)執(zhí)行所有test_前綴的函數(shù)
class TestClass(object):

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

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

pytest常見的exit codes

Exit code 0 所有tests全部通過

Exit code 1 部分tests失敗了

Exit code 2 用戶中止test執(zhí)行

Exit code 3 執(zhí)行test時(shí),內(nèi)部報(bào)錯(cuò)

Exit code 4 pytest命令使用姿勢(shì)不對(duì)

Exit code 5 無tests可執(zhí)行

pytest常見幫助選項(xiàng)

$ pytest --version      # 顯示版本信息
$ pytest --fixtures     # 顯示內(nèi)置可用的函數(shù)參數(shù)
$ pytest -h | --help    # 顯示幫助信息
$ pytest -x             # 第一個(gè)失敗時(shí)即停止
$ pytest --maxfail=2    # 兩個(gè)失敗后即停止

pytest fixtures(明確的、模塊化的、可擴(kuò)展的)

  1. fixtures由明確的命名,可以通過測(cè)試函數(shù)、模塊、類或整個(gè)項(xiàng)目激活
  2. fixtures以模塊化的方式實(shí)現(xiàn),因?yàn)槊總€(gè)名稱會(huì)觸發(fā)一個(gè)fixtures函數(shù),同時(shí)函數(shù)本身也可以使用其它fixtures
  3. fixtures管理從簡(jiǎn)單的單元到復(fù)雜的功能測(cè)試,允許參數(shù)化fixtures和根據(jù)配置和組件進(jìn)行測(cè)試或通過函數(shù)、類、模塊或整個(gè)test會(huì)話范圍重用fixtures

Fixtures作為函數(shù)參數(shù)

測(cè)試函數(shù)可以接收fixture對(duì)象作為輸入?yún)?shù),使用@pytest.fixture

test_smtpsimple.py

import pytest


@pytest.fixture
def smtp_connection():
    import smtplib
    return smtplib.SMTP(host='smtp.qq.com',port=587, timeout=5)


def test_ehlo(smtp_connection):
    response, msg = smtp_connection.ehlo()
    assert response == 250
    assert 0
$ pytest 01\test_smtpsimple.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.7.4, py-1.6.0, pluggy-0.7.1
rootdir: D:\projects\python\pytest_notes, inifile:
collected 1 item                                                               

01\test_smtpsimple.py F                                                  [100%]

================================== FAILURES ===================================
__________________________________ test_ehlo __________________________________

smtp_connection = <smtplib.SMTP object at 0x0000021F3E041828>

    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
>       assert 0
E       assert 0

01\test_smtpsimple.py:13: AssertionError
========================== 1 failed in 1.45 seconds ===========================

# 測(cè)試函數(shù)調(diào)用smtp_connection參數(shù),而smptlib.SMTP實(shí)列由fixture函數(shù)創(chuàng)建

Fixtures 依賴注入

Fixtures 允許測(cè)試函數(shù)非常容易的接收和使用特定的預(yù)初始化程序?qū)ο?,而無需特別去關(guān)注import/setup/cleanup等細(xì)節(jié)

config.py:共享fixture函數(shù)

如果多個(gè)測(cè)試文件需要用到一個(gè)fixture函數(shù),則把它寫到conftest.py文件當(dāng)中。使用時(shí)無需導(dǎo)入這個(gè)fixture函數(shù),因?yàn)閜ytest會(huì)自動(dòng)獲取

共享測(cè)試數(shù)據(jù)

  1. 如果在測(cè)試中,需要從文件加載測(cè)試數(shù)據(jù)到tests,可以使用fixture方式加載,pytest有自動(dòng)緩存機(jī)制
  2. 另外一種方式是添加測(cè)試數(shù)據(jù)文件到tests目錄,如使用pytest-datadirpytest-datafiles插件

Scope:共享一個(gè)fixture實(shí)列(類、模塊或會(huì)話)

Scope - module

conftest.py

import pytest
import smtplib


@pytest.fixture(scope='module')
def smtp_connection():
    return smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)

test_module.py



def test_ehlo(smtp_connection):
    response, msg = smtp_connection.ehlo()
    assert response == 250
    assert b'smtp.qq.com' in msg
    assert 0


def test_noop(smtp_connection):
    response, msg = smtp_connection.noop()
    assert response == 250
    assert 0

執(zhí)行

$ pytest 01\test_module.py
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.7.4, py-1.6.0, pluggy-0.7.1
rootdir: D:\projects\python\pytest_notes, inifile:
collected 2 items                                                              

01\test_module.py FF                                                     [100%]

================================== FAILURES ===================================
__________________________________ test_ehlo __________________________________

smtp_connection = <smtplib.SMTP object at 0x0000018E14F13780>   # 1

    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
        assert b'smtp.qq.com' in msg
>       assert 0
E       assert 0

01\test_module.py:7: AssertionError
__________________________________ test_noop __________________________________

smtp_connection = <smtplib.SMTP object at 0x0000018E14F13780>   # 2 可以看到1和2的實(shí)列對(duì)象為同一個(gè)

    def test_noop(smtp_connection):
        response, msg = smtp_connection.noop()
>       assert response == 250
E       assert 530 == 250

01\test_module.py:12: AssertionError
========================== 2 failed in 1.42 seconds ===========================

Scope - session

import pytest
import smtplib


# 所有tests能使用到它的,都是共享同一個(gè)fixture值
@pytest.fixture(scope='session')
def smtp_connection():
    return smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)

Scope - class

import pytest
import smtplib


# 每個(gè)test類,都是共享同一個(gè)fixture值
@pytest.fixture(scope='class')
def smtp_connection():
    return smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)

高級(jí)別的scope fixtures第一個(gè)實(shí)例化

@pytest.fixture(scope="session")
def s1():
    pass

@pytest.fixture(scope="module")
def m1():
    pass

@pytest.fixture
def f1(tmpdir):
    pass

@pytest.fixture
def f2():
    pass

def test_foo(f1, m1, f2, s1):
...
  1. s1: 是最高級(jí)別的fxiture(session)
  2. m1: 是第二高級(jí)別的fixture(module)
  3. tmpdir: 是一個(gè)function的fixture,依賴f1
  4. f1:在test_foo列表參數(shù)當(dāng)中,是第一個(gè)function的fixture
  5. f2:在test_foo列表參數(shù)當(dāng)中,是最后一個(gè)function的fixture

Fixture結(jié)束或執(zhí)行teardown代碼

  1. 當(dāng)fixture超過其scope范圍,pytest支持執(zhí)行fixture特定的結(jié)束代碼
  2. 使用yield替換return,所有yield聲明之后的代碼都作為teardown代碼處理

yield替換return

conftest.py

import pytest
import smtplib


# print和smtp_connection.close()只會(huì)在module范圍內(nèi)最后一個(gè)test執(zhí)行結(jié)束后執(zhí)行,除非中間有異常
@pytest.fixture(scope='module')
def smtp_connection():
    smtp_connection = smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)
    yield smtp_connection
    print('teardown smtp')  
    smtp_connection.close()

執(zhí)行

$ pytest -s -q 01\test_module.py --tb=no
FFteardown smtp

2 failed in 0.96 seconds

with替換yield

conftest.py

import pytest
import smtplib


# 使用了with聲明,smtp_connection等test執(zhí)行完后,自動(dòng)關(guān)閉
# 注意:yield之前的setup代碼發(fā)生了異常,teardown代碼將不會(huì)被調(diào)用
@pytest.fixture(scope='module')
def smtp_connection():
    with smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5) as smtp_connection:
        yield smtp_connection

使用addfinalizer清理

yield和addfinalizer方法類似,但addfinalizer有兩個(gè)不同的地方

  1. 可以注冊(cè)多個(gè)finalizer函數(shù)

  2. 不論setup代碼是否發(fā)生異常,均會(huì)關(guān)閉所有資源

    @pytest.fixture
    def equipments(request):
       r = []
       for port in ('C1', 'C3', 'C28'):
           equip = connect(port)
           request.addfinalizer(equip.disconnect)
           r.append(equip)
       return r
    # 假設(shè)C28拋出一次,C1和C2將正常被關(guān)閉。當(dāng)然異常發(fā)生在finalize函數(shù)注冊(cè)之前,它將不被執(zhí)行
    

conftest.py

import pytest
import smtplib


@pytest.fixture(scope='module')
def smtp_connection(request):
    smtp_connection = smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)

    def fin():
        print('teardown smtp_connection')
        smtp_connection.close()
    request.addfinalizer(fin)
    return smtp_connection

執(zhí)行

$ pytest -s -q 01\test_module.py --tb=no
FFteardown smtp_connection

2 failed in 0.99 seconds

Fixture可以使用request對(duì)象來管理 測(cè)試內(nèi)容

Fixture函數(shù)可以接收一個(gè)request對(duì)象

conftest.py

import pytest
import smtplib


# 所有使用fixture的test module,都可以讀取一個(gè)可選的smtpserver地址
@pytest.fixture(scope='module')
def smtp_connection(request):
    server = getattr(request.module, 'smtpserver', 'smtp.qq.com')
    smtp_connection = smtplib.SMTP(host=server, port=587, timeout=5)

    yield smtp_connection
    print('finalizing %s (%s)' % (smtp_connection, server))
    smtp_connection.close()

執(zhí)行1

$ pytest -s -q --tb=no
.FFFfinalizing <smtplib.SMTP object at 0x00000266A3912470> (smtp.qq.com)
FF
5 failed, 1 passed in 2.11 seconds

test_anothersmtp.py

smtpserver = 'mail.python.org'  # 自動(dòng)讀取并替換fixture默認(rèn)的值


def test_showhelo(smtp_connection):
    assert 0, smtp_connection.helo()

執(zhí)行2

$ pytest -qq --tb=short 01\test_anothersmtp.py
F                                                                        [100%]
================================== FAILURES ===================================
________________________________ test_showhelo ________________________________
01\test_anothersmtp.py:5: in test_showhelo
    assert 0, smtp_connection.helo()
E   AssertionError: (250, b'mail.python.org')
E   assert 0
-------------------------- Captured stdout teardown ---------------------------
finalizing <smtplib.SMTP object at 0x000001C1F631DBE0> (mail.python.org)

Fixture工廠模式

在單個(gè)test中,fixture結(jié)果需要被多次使用

@pytest.fixture
def make_customer_record():
    def _make_customer_record(name):
        return {
            "name": name,
            "orders": []
        }
    return _make_customer_record

def test_customer_records(make_customer_record):
    customer_1 = make_customer_record("Lisa")
    customer_2 = make_customer_record("Mike")
    customer_3 = make_customer_record("Meredith")
# 創(chuàng)建的數(shù)據(jù)需要工廠管理時(shí),采用這種方式
@pytest.fixture
def make_customer_record():
    created_records = []
    
    def _make_customer_record(name):
        record = models.Customer(name=name, orders=[])
        created_records.append(record)
        return record
    
    yield _make_customer_record

    for record in created_records:
        record.destroy()
        
def test_customer_records(make_customer_record):
    customer_1 = make_customer_record("Lisa")
    customer_2 = make_customer_record("Mike")
    customer_3 = make_customer_record("Meredith")

Fixture 參數(shù)

Fixture可以參數(shù)化,當(dāng)需要執(zhí)行多次時(shí)

conftest.py

import pytest
import smtplib


@pytest.fixture(scope='module',
                params=['smtp.qq.com', 'mail.python.org'])
def smtp_connection(request):
    smtp_connection = smtplib.SMTP(host=request.param, port=587, timeout=5)

    yield smtp_connection
    print('finalizing %s' % (smtp_connection))
    smtp_connection.close()

執(zhí)行

$ pytest -q  01\test_module.py
FFFF                                                                     [100%]
================================== FAILURES ===================================
___________________________ test_ehlo[smtp.qq.com] ____________________________

smtp_connection = <smtplib.SMTP object at 0x000002769B09A908>

    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
        assert b'smtp.qq.com' in msg
>       assert 0
E       assert 0

01\test_module.py:7: AssertionError
___________________________ test_noop[smtp.qq.com] ____________________________

smtp_connection = <smtplib.SMTP object at 0x000002769B09A908>

    def test_noop(smtp_connection):
        response, msg = smtp_connection.noop()
>       assert response == 250
E       assert 530 == 250

01\test_module.py:12: AssertionError
_________________________ test_ehlo[mail.python.org] __________________________

smtp_connection = <smtplib.SMTP object at 0x000002769B09AA58>

    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
>       assert b'smtp.qq.com' in msg
E       AssertionError: assert b'smtp.qq.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDST
ATUSCODES\n8BITMIME\nDSN\nSMTPUTF8'

01\test_module.py:6: AssertionError
---------------------------- Captured stdout setup ----------------------------
finalizing <smtplib.SMTP object at 0x000002769B09A908>
_________________________ test_noop[mail.python.org] __________________________

smtp_connection = <smtplib.SMTP object at 0x000002769B09AA58>

    def test_noop(smtp_connection):
        response, msg = smtp_connection.noop()
        assert response == 250
>       assert 0
E       assert 0

01\test_module.py:13: AssertionError
-------------------------- Captured stdout teardown ---------------------------
finalizing <smtplib.SMTP object at 0x000002769B09AA58>
4 failed in 4.29 seconds

標(biāo)記fixture參數(shù)

test_fixture_marks.py

import pytest


@pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)])
def data_set(request):
    return request.param


def test_data(data_set):
    pass

執(zhí)行

$ pytest 01\test_fixture_marks.py -v
============================= test session starts =============================
platform win32 -- Python 3.6.4, pytest-3.7.4, py-1.6.0, pluggy-0.7.1 -- d:\projects\python\pytest_notes\.venv\scripts\python.exe
cachedir: .pytest_cache
rootdir: D:\projects\python\pytest_notes, inifile:
collected 3 items                                                              

01/test_fixture_marks.py::test_data[0] PASSED                            [ 33%]
01/test_fixture_marks.py::test_data[1] PASSED                            [ 66%]
01/test_fixture_marks.py::test_data[2] SKIPPED                           [100%]

===================== 2 passed, 1 skipped in 0.14 seconds =====================

經(jīng)典的xunit風(fēng)格setup

Module級(jí)別的setup/teardown

如果有多個(gè)test函數(shù)或test類在一個(gè)模塊內(nèi),可以選擇的實(shí)現(xiàn)setup_module和teardown_module,一般模塊內(nèi)的所有函數(shù)都會(huì)調(diào)用一次

def setup_module(module):
    pass

def teardown_module(module):
    pass

Class級(jí)別的setup/teardown

在類內(nèi),所有的方法均會(huì)調(diào)用到

@classmethod
def setup_class(cls):
    pass

@classmethod
def teardown_class(cls):
    pass

Method和Function級(jí)別的setup/teardown

# 指定方法調(diào)用
def setup_method(self, method):
    pass

def teardown_method(self, method):
    pass

# 模塊級(jí)別內(nèi)可以直接使用function調(diào)用
def setup_function(function):
    pass

def teardown_function(function):
    pass
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容