pytest

1、引入fixture,可做setup,可做基线,可在各个层级引用
2、装饰器支持 pytest.mark.各种操作
3、全局引入pytest.ini,自动加载配置项,conftest.ini中配置用例中的fixture
https://www.jianshu.com/p/b825addb4e16

<meta charset="utf-8">

安装与快速使用

安装

$ pip install pytest
$ pytest --version

第一个test

01\test_sample.py

def func(x):
    return x + 1

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

运行

# 默认会执行当前目录及子目录的所有test_*.py或*_test.py文件。用例执行成功为.,失败为F
$ pytest

# 静默执行
$ pytest -q 01\test_sample.py

# 调试方式执行,可以打印print日志等详情信息
$ pytest 01\test_sample.py -s -v

# python模块方式执行
$ python -m pytest 01\test_sample.py

# 执行单个目录下的tests
$ python 01\

test类包含多个tests

01\test_class.py

# pytest默认会执行所有test_前缀的函数
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执行

Exit code 3 执行test时,内部报错

Exit code 4 pytest命令使用姿势不对

Exit code 5 无tests可执行

pytest常见帮助选项

$ pytest --version      # 显示版本信息
$ pytest --fixtures     # 显示内置可用的函数参数
$ pytest -h | --help    # 显示帮助信息
$ pytest -x             # 第一个失败时即停止
$ pytest --maxfail=2    # 两个失败后即停止

pytest fixtures(明确的、模块化的、可扩展的)

  1. fixtures由明确的命名,可以通过测试函数、模块、类或整个项目激活
  2. fixtures以模块化的方式实现,因为每个名称会触发一个fixtures函数,同时函数本身也可以使用其它fixtures
  3. fixtures管理从简单的单元到复杂的功能测试,允许参数化fixtures和根据配置和组件进行测试或通过函数、类、模块或整个test会话范围重用fixtures

Fixtures作为函数参数

测试函数可以接收fixture对象作为输入参数,使用@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 ===========================

# 测试函数调用smtp_connection参数,而smptlib.SMTP实列由fixture函数创建

Fixtures 依赖注入

Fixtures 允许测试函数非常容易的接收和使用特定的预初始化程序对象,而无需特别去关注import/setup/cleanup等细节

config.py:共享fixture函数

如果多个测试文件需要用到一个fixture函数,则把它写到conftest.py文件当中。使用时无需导入这个fixture函数,因为pytest会自动获取

共享测试数据

  1. 如果在测试中,需要从文件加载测试数据到tests,可以使用fixture方式加载,pytest有自动缓存机制
  2. 另外一种方式是添加测试数据文件到tests目录,如使用pytest-datadirpytest-datafiles插件

Scope:共享一个fixture实列(类、模块或会话)

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

执行

$ 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的实列对象为同一个

    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能使用到它的,都是共享同一个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

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

高级别的scope fixtures第一个实例化

@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: 是最高级别的fxiture(session)
  2. m1: 是第二高级别的fixture(module)
  3. tmpdir: 是一个function的fixture,依赖f1
  4. f1:在test_foo列表参数当中,是第一个function的fixture
  5. f2:在test_foo列表参数当中,是最后一个function的fixture

Fixture结束或执行teardown代码

  1. 当fixture超过其scope范围,pytest支持执行fixture特定的结束代码
  2. 使用yield替换return,所有yield声明之后的代码都作为teardown代码处理

yield替换return

conftest.py

import pytest
import smtplib

# print和smtp_connection.close()只会在module范围内最后一个test执行结束后执行,除非中间有异常
@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()

执行

$ 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执行完后,自动关闭
# 注意:yield之前的setup代码发生了异常,teardown代码将不会被调用
@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有两个不同的地方

  1. 可以注册多个finalizer函数
  1. 不论setup代码是否发生异常,均会关闭所有资源
```
@pytest.fixture
def equipments(request):
   r = []
   for port in ('C1', 'C3', 'C28'):
       equip = connect(port)
       request.addfinalizer(equip.disconnect)
       r.append(equip)
   return r
# 假设C28抛出一次,C1和C2将正常被关闭。当然异常发生在finalize函数注册之前,它将不被执行

```

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

执行

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

2 failed in 0.99 seconds

Fixture可以使用request对象来管理 测试内容

Fixture函数可以接收一个request对象

conftest.py

import pytest
import smtplib

# 所有使用fixture的test module,都可以读取一个可选的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()

执行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'  # 自动读取并替换fixture默认的值

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

执行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工厂模式

在单个test中,fixture结果需要被多次使用

@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")

# 创建的数据需要工厂管理时,采用这种方式
@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 参数

Fixture可以参数化,当需要执行多次时

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

执行

$ 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

标记fixture参数

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

执行

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

经典的xunit风格setup

Module级别的setup/teardown

如果有多个test函数或test类在一个模块内,可以选择的实现setup_module和teardown_module,一般模块内的所有函数都会调用一次

def setup_module(module):
    pass

def teardown_module(module):
    pass

Class级别的setup/teardown

在类内,所有的方法均会调用到

@classmethod
def setup_class(cls):
    pass

@classmethod
def teardown_class(cls):
    pass

Method和Function级别的setup/teardown

# 指定方法调用
def setup_method(self, method):
    pass

def teardown_method(self, method):
    pass

# 模块级别内可以直接使用function调用
def setup_function(function):
    pass

def teardown_function(function):
    pass

作者:qixuezhiren
链接:https://www.jianshu.com/p/b825addb4e16
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容