pytest常见用法总结

最近工作中用到了pytest,总结一些用法:

1. 安装:

pip install pytest

2. 运行:

pytest 默认只能识别以test_ 开头的文件和测试用例,如果pytest后面不带文件名,则默认执行当前目录下所有以test_ 开头的文件。

  • 执行某个文件里所有以 test 开头的用例:pytest test_demo.py
# test_demo.py
def test_01():
    assert 1+1 == 2

def test_02():
    assert 1+3 == 4

执行结果:

$ pytest test_demo.py
========================================= test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items

test_demo.py ..                                                                                      [100%]

======================================== 2 passed in 0.04 seconds =========================================

如果只想执行test_01,可以使用pytest test_demo.py::test_01

  • 如果用例是用类组织起来的:
# test_class.py
class TestClass(object):
    def test_one(self):
        x = "this"
        assert 'h' in x

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

执行结果:

$ pytest test_class.py
=========================================== test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items

test_class.py .F                                                                                     [100%]

================================================= FAILURES =================================================
____________________________________________ TestClass.test_two ____________________________________________

self = <test_class.TestClass object at 0x102f46780>

    def test_two(self):
        y = "world"
>       assert 'h' in y
E       AssertionError: assert 'h' in 'world'

test_class.py:9: AssertionError
========================================== 1 failed, 1 passed in 0.08 seconds ====================================

如果一个文件里有多个类,你只想执行其中一个类里的用例:
pytest test_class.py::TestClass

  • 执行某个文件夹下所有的以 test_开头的文件:pytest testcase/
# testcase/test_demo01.py
def test_one():
    x = 'this'
    assert 'h' in x

# testcase/test_demo02.py
def test_two():
    y = 'world'
    assert 'h' in y

执行结果:

$ pytest testcase/
========================================== test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items

testcase/test_demo01.py .                                                                            [ 50%]
testcase/test_demo02.py F                                                                            [100%]

============================================== FAILURES ===========================================
_________________________________________________ test_two _________________________________________________

    def test_two():
        y = 'world'
>       assert 'h' in y
E       AssertionError: assert 'h' in 'world'

testcase/test_demo02.py:3: AssertionError
==================================== 1 failed, 1 passed in 0.09 seconds ================================
  • 执行带某个标记的用例,比如用例中带了@pytest.mark.smoke标记的用例:
# test_mark.py
@pytest.mark.smoke
def test_01():
    assert 1+1 == 2

def test_02():
    assert 1+3 == 4
$ pytest -m smoke test_mark.py
=========================================== test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items / 1 deselected / 1 selected

test_mark.py .                                                                                       [100%]

================================== 1 passed, 1 deselected in 0.03 seconds ==================================

可以看到只执行带@pytest.mark.smoke的用例。

  • 按用例函数的名称来执行:pytest -k 01 test_mark.py
$ pytest -k 01 test_mark.py
=========================================== test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items / 1 deselected / 1 selected

test_mark.py .                                                                                       [100%]

================================== 1 passed, 1 deselected in 0.01 seconds ==================================

按照用例名字来执行,只执行名字里含有01的测试用例。

3. pytest fixture

@pytest_fixture的作用是提供一个固定的参数给测试重复的使用,相当于unittest里的setupteardown.

  • pytest fixture 做函数的参数
# test_fixture.py
@pytest.fixture(scope="module")
def num():
    n = random.randint(1, 5)
    return n

def test_01(num):
    print(num)
    assert 0

def test_02(num):
    print(num)
    assert 0
  • pytest fixture的scope有class, session 和 module.
  • 此时执行可以多次执行看看两个函数里num的数值是否是一样:
$ pytest test_fixture.py
=========================================== test session starts ============================================
platform darwin -- Python 3.7.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: /Users/libo/python3workspace/learn, inifile:
plugins: rerunfailures-7.0, picked-0.4.1, parallel-0.0.9, forked-1.0.2, cov-2.7.1, allure-pytest-2.6.1
collected 2 items

test_fixture.py FF                                                                                   [100%]

================================================= FAILURES =================================================
_________________________________________________ test_01 __________________________________________________

num = 3

    def test_01(num):
        print(num)
>       assert 0
E       assert 0

test_fixture.py:12: AssertionError
------------------------------------------- Captured stdout call -------------------------------------------
3
_________________________________________________ test_02 __________________________________________________

num = 3

    def test_02(num):
        print(num)
>       assert 0
E       assert 0

test_fixture.py:16: AssertionError
------------------------------------------- Captured stdout call -------------------------------------------
3
========================================= 2 failed in 0.06 seconds =========================================
  • 多次执行可以看见两个函数里num的值是总是相同的,如果只是普通的函数调用,两个函数里的值肯定不可能总是相等的。因此pytest fixture可以用来共享数据。
  • 此外pytest fixture 还可以实现类似teardown的操作,方法是首先把return换成yield,然后把销毁的相关操作放在yield后面。
# conftest.py
import smtplib
import pytest


@pytest.fixture(scope="module")
def smtp_connection():
    smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
    yield smtp_connection  # provide the fixture value
    print("teardown smtp")
    smtp_connection.close()
# test_module.py
def test_ehlo(smtp_connection):
    response, msg = smtp_connection.ehlo()
    assert response == 250
    assert b"smtp.gmail.com" in msg
    assert 0  # for demo purposes

def test_noop(smtp_connection):
    response, msg = smtp_connection.noop()
    assert response == 250
    assert 0  # for demo purpose
$ pytest -s -q --tb=no test_module.py
FFteardown smtp

2 failed in 6.81 seconds

从上面的运行可以看出来,在执行完两个测试用例之后才调用yield后面的print语句。如果我们把scope改成scope="function"那么fixture的setup和teardown就会在每一个测试用例开始和结束时执行。

  • 像上面的close操作也可以使用with语句来实现:
import smtplib
import pytest


@pytest.fixture(scope="module")
def smtp_connection():
    with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
        yield smtp_connection  # provide the fixture value

4. pytest.mark.parametrize

pytest 内置的pytest.mark.parametrize装饰器支持将测试函数的参数的参数化。

import pytest


@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
    assert eval(test_input) == expected

可以直接通过@pytest.mark.parametrize传递参数,而不需要在测试函数里面通过for循环来实现。

@pytest.mark.parametrize还可以添加起来使用:

import pytest


@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
    assert x + y == 2

上面的例子里会一共有4中情况:x=0/y=2,x=0/y=3,x=1/y=2,x=1/y=3一共四种情况。

更多用法可以参考官方文档.

5. pytest.ini

pytest的主配置文件,可以改变pytest的默认行为。我们可以把经常使用的一些选项放到pytest.ini中,或者在里面添加一些markers。

# pytest.ini 文件
[pytest]
addopts =
    --verbose
    --reruns 5    # 需要安装 pytest-rerunfailures 插件
    --clean-alluredir    # 需要 allure-pytest 插件
    --alluredir=/tmp/allure-results/
markers = 
    smoke: 冒烟测试用例

使用 pytest --help 可以看到常用的pytest.ini选项:

[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:

  markers (linelist)       markers for test functions
  empty_parameter_set_mark (string) default marker for empty parametersets
  norecursedirs (args)     directory patterns to avoid for recursion
  testpaths (args)         directories to search for tests when no files or directories are given in the command line.
  console_output_style (string) console output: classic or with additional progress information (classic|progress).
  usefixtures (args)       list of default fixtures to be used with this project
  python_files (args)      glob-style file patterns for Python test module discovery
  python_classes (args)    prefixes or glob names for Python test class discovery
  python_functions (args)  prefixes or glob names for Python test function and method discovery
  xfail_strict (bool)      default for the strict parameter of xfail markers when not given explicitly (default: False)
  doctest_optionflags (args) option flags for doctests
  doctest_encoding (string) encoding used for doctest files
  cache_dir (string)       cache directory path.
  filterwarnings (linelist) Each line specifies a pattern for warnings.filterwarnings. Processed after -W and --pythonwarnings.
  log_print (bool)         default value for --no-print-logs
  log_level (string)       default value for --log-level
  log_format (string)      default value for --log-format
  log_date_format (string) default value for --log-date-format
  log_cli (bool)           enable log display during test run (also known as "live logging").
  log_cli_level (string)   default value for --log-cli-level
  log_cli_format (string)  default value for --log-cli-format
  log_cli_date_format (string) default value for --log-cli-date-format
  log_file (string)        default value for --log-file
  log_file_level (string)  default value for --log-file-level
  log_file_format (string) default value for --log-file-format
  log_file_date_format (string) default value for --log-file-date-format
  addopts (args)           extra command line options
  minversion (string)      minimally required pytest version
  workers (string)         Set the max num of workers (aka processes) to start (int or "auto" - one per core)
  tests_per_worker (string) Set the max num of concurrent tests for each worker (int or "auto" - split evenly)
  • addopts:更改命令行的默认行为,可以把一些总是用到的选项添加到这里,这样就不用每次执行的时候都输入该命令。
  • markers:自定义的标记,可以使用pytest --markers看到。
  • pytest.ini文件放到工程的目录即可。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容