python学习-pytest-pytest常用方法

Posted 给自己一个向前进的理由

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python学习-pytest-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文件放到工程的目录即可。


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

以上是关于python学习-pytest-pytest常用方法的主要内容,如果未能解决你的问题,请参考以下文章

45-pytest-pytest.main()使用

直播案例 | 机器学习中常用优化算法的 Python 实践

Python学习-list操作

常用的机器学习&数据挖掘知识(点)

python清洗Excel数据常用方法

《Python 机器学习》笔记