Pytest02-用法和调用

通过python -m pytest调用pytest

通过python解释器,在命令行执行:
python -m pytest XXXX.py
几乎等同于直接pytest XXXX.py,除了通过python会将当前目录添加到sys.path中。

有可能会遇到的退出码

运行pytest可能会导致六个不同的退出码:

  • 退出码0:所有测试均已收集并成功通过
  • 退出码1:测试已收集并运行,但是某些测试失败
  • 退出码2:测试执行被用户中断
  • 退出码3:执行测试时发生内部错误
  • 退出码4:pytest命令行使用错误
  • 退出码5:未收集测试
    它们由_pytest.config.ExitCode枚举表示。 退出码是公共API的一部分,可以使用以下命令直接导入和访问:
    from pytest import ExitCode
    注意:如果要在某些情况下自定义退出代码,尤其是在未收集任何测试的情况下,请考虑使用pytest-custom exit_code插件。

获取相关信息

pytest --version            # 获取版本号
pytest --fixtures           # 获取可用的内置函数参数
pytest -h / pytest --help   # 获取帮助文档

最大测试失败数

pytest -x             # 失败一个后就不再继续跑了 
pytest --maxfail=2    # 失败两个后就不再继续跑了

指定测试/选择测试

先上代码:

# -*- coding: utf-8 -*-
# @Time    : 2020/7/7 10:59
# @Author  : 无罪的坏人
# @File    : test_mod.py
import pytest


def test_func():
    assert 1 == 1


@pytest.mark.slow
def test_slow():
    assert 2 == 3


class TestClass:
    def test_method(self):
        assert 2 == 1

    @pytest.mark.parametrize('x,y', [(1, 2), (3, 4), (5, 5), (1, 1)])  # 实现参数化
    def test_equal(self, x, y):
        assert x == y

运行某一个python模块

pytest test_mod.py

运行某一个目录

pytest testing/

运行包含XX关键字的但又不包含XXX关键字的用例

pytest -k "MyClass and not method"
上面这个命令:这个会运行TestMyClass.test_something,但是不会运行TestMyClass.test_method_simple.

运行指定的nodeid的用例

每个收集到的测试都分配有一个唯一的节点ID,该ID由模块文件名,后跟说明符(例如类名,函数名和参数化参数)组成,并由::字符分隔。
比如:
pytest test_mod.py::test_func # 模块名::方法名
pytest test_mod.py::TestClass::test_method # 模块名::类名::方法名
pytest test_mod.py::TestClass::test_equal[1-2] # 模块名::类名::方法名[参数1-参数2]
注意:1.这里的参数要已经存在的,就是在参数化的元祖里面;2.中间用-连接。

执行指定标记的用例:

pytest -m slow
测试用例可以用@pytest.mark.slow装饰即可。

执行包里的测试用例

pytest --pyargs pkg.testing
pytest会导入pkg.testing,并且找到测试用例并执行。

修改pytest的回溯信息

pytest --showlocals    # 在回溯信息中打印本地变量
pytest -l              # 在回溯信息中打印本地变量 (简短的)
pytest --tb=auto       # (默认) 
pytest --tb=long       # 详尽的回溯信息
pytest --tb=short      # 更简短的回溯信息
pytest --tb=line       # 每个失败信息总结在一行中
pytest --tb=native     # Python标准库格式
pytest --tb=no         # 屏蔽全部回溯信息

PS:还有一个更加详细的模式:--full-trace(比--tb = long还要长)。如果测试花费的时间太长,而你却用Ctrl + C中断它们以找出测试的挂起位置。默认情况下,不会显示任何输出(因为pytest捕获了KeyboardInterrupt),通过使用此选项,可以确保显示跟踪。

详细的总结报告

-r参数可以在测试用例执行完后显示一个简短的测试摘要信息,从而使得在大型测试套件中可以比较清楚的看出哪些失败,哪些跳过。

# -*- coding: utf-8 -*-
# @Time    : 2020/7/7 13:58
# @Author  : 无罪的坏人
# @File    : test_example.py
import pytest


@pytest.fixture
def error_fixture():
    assert 0


def test_ok():
    print("ok")


def test_fail():
    assert 0


def test_error(error_fixture):
    pass


def test_skip():
    pytest.skip("skipping this test")


def test_xfail():
    pytest.xfail("xfailing this test")


@pytest.mark.xfail(reason="always xfail")
def test_xpass():
    pass

结果:

>> pytest -ra test_example.py
================================================================================== test session starts ==================================================================================
platform win32 -- Python 3.7.3, pytest-5.4.3, py-1.8.1, pluggy-0.13.1
rootdir: D:\Python\EDR2\testcase2
plugins: allure-pytest-2.8.16, html-2.1.1, metadata-1.10.0, rerunfailures-9.0
collected 6 items                                                                                                                                                                        

test_example.py .FEsxX                                                                                                                                                             [100%]

======================================================================================== ERRORS =========================================================================================
_____________________________________________________________________________ ERROR at setup of test_error ______________________________________________________________________________

    @pytest.fixture
    def error_fixture():
>       assert 0
E       assert 0

test_example.py:11: AssertionError
======================================================================================= FAILURES ========================================================================================
_______________________________________________________________________________________ test_fail _______________________________________________________________________________________

    def test_fail():
>       assert 0
E       assert 0

test_example.py:19: AssertionError
================================================================================ short test summary info ================================================================================
SKIPPED [1] D:\Python\EDR2\testcase2\test_example.py:27: skipping this test
XFAIL test_example.py::test_xfail
  reason: xfailing this test
XPASS test_example.py::test_xpass always xfail
ERROR test_example.py::test_error - assert 0
FAILED test_example.py::test_fail - assert 0
========================================================= 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.18s =========================================================

-r选项后面的a是什么含义呢?让我们再来看下。

  • f - 失败的
  • E - 报错的
  • s - 跳过的
  • x - 跳过执行并标记为xfailed的
  • X - 跳过执行并标记为xpassed的
  • p - 通过的
  • P - 通过并且有输出的
  • a - 除p和P外所有的
  • A - 所有的
  • N - 无
    上面这些参数可以结合在一起使用,比如:
    pytest -rfs
    可以在short test summary info看到失败的和跳过的。
    pytest -rpP P会把最后测试通过的用例输出捕获,就是那个ok
......
======================================================================================== PASSES =========================================================================================
________________________________________________________________________________________ test_ok ________________________________________________________________________________________
--------------------------------------------------------------------------------- Captured stdout call ----------------------------------------------------------------------------------
ok
================================================================================ short test summary info ================================================================================
PASSED test_example.py::test_ok
========================================================= 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.13s =========================================================

用例失败时加载PDB(Python调试器)

Python带有一个内置的称为PDB的Python调试器。 pytest允许通过以下命令进入PDB:
pytest --pdb
这将在每次失败(或KeyboardInterrupt)时调用Python调试器。 通常,你可能只想对第一个失败的测试执行此操作,以了解某些失败情况:
pytest -x --pdb # 第一次失败就直接调用pdb
pytest --pdb --maxfail=3 # 前面三次失败都调用pdb
请注意,在发生任何故障时,异常信息都存储在sys.last_value,sys.last_type和sys.last_traceback中。在交互式使用中,你可以使用任何调试工具进行事后调试,也可以手动访问异常信息,例如:

(Pdb) x  # 可以直接访问局部变量x
1
(Pdb) import sys
(Pdb) sys.last_value
AssertionError('assert 1 == 0')
(Pdb) sys.last_type
<class 'AssertionError'>
(Pdb) sys.last_traceback
<traceback object at 0x00000207F01933C8>
(Pdb) sys.last_traceback.tb_lineno
1477
(Pdb) exit
....此处就退出Pdb了.....

在每个用例执行时就加载PDB

pytest --trace

设置断点

在你的测试代码中,写入import pdb;pdb.set_trace()就可以设置断点了。然后pytest会自动为该测试禁用其输出捕获

  • 其他测试中的输出捕获不受影响;
  • 结束调试就continue,就恢复输出捕获;

使用内置断点功能

Python 3.7有一个内置breakpoint()方法,pytest在以下场景支持:

  • breakpoint()被调用,并且PYTHONBREAKPOINT为默认值时,pytest会使用内部自定义的PDB代替系统的;
  • 测试执行结束时,自动切回系统自带的PDB;
  • 当加上--pdb选项时,breakpoint()和测试发生错误时,都会调用内部自定义的PDB;
  • --pdbcls选项允许指定一个用户自定义的PDB类;

分析测试执行时间

获取执行最慢的10个测试用例:
pytest --durations=10
通常,pytest不会展示时间小于0.01s的用例,除非用-vv

错误处理

5.0版本的新特性。
Faulthandler标准模块可用于在segfault上或超时后转储Python跟踪。除非在命令行上给出-p no:faulthandler,否则该模块将自动启用pytest运行。
如果测试花费的时间超过X秒(在Windows上不可用),则faulthandler_timeout = X配置选项也可以用于转储所有线程的回溯。
注意:这个功能是从pytest-faulthandler插件合并而来的,但是有两点不同:

  • 使能时,使用-p no:faulthandler代替原来的--no-faulthandler;
  • 使用faulthandler_timeout配置项代替--faulthandler-timeout命令行选项来配置超时时间。当然,你也可以使用-o faulthandler_timeout=X在命令行配置;

创建JunitXML格式的文件

通过以下命令,能创建一个可以被Jenkins或其他持续集成服务器读取的结果文件:
pytest --junitxml=path
你可以在配置文件pytest.ini里面设置junit_suite_name(4.0版本新增)的值,从而来改变junitxml文件中testsuite根节点的name值(默认是pytest):

# pytest.ini
[pytest]
junit_suite_name = pytest_test

添加新属性

def test_function(record_property):
    record_property("example_key", 1)
    assert True

报告中:

<testcase classname="test_mod" file="test_mod.py" line="14" name="test_function" time="0.002">
      <properties>
            <property name="example_key" value="1"/>
      </properties>
</testcase>

或者,你也可以将此功能集成在自定义conftest.py中。

def pytest_collection_modifyitems(session, config, items):
    for item in items:
        for marker in item.iter_markers(name="test_id"):
            test_id = marker.args[0]
            item.user_properties.append(("test_id", test_id))

在测试用例中:

@pytest.mark.test_id(1501)
def test_function():
    assert True

报告中:

<testcase classname="test_mod" file="test_mod.py" line="19" name="test_function" time="0.002">
      <properties>
            <property name="test_id" value="1501"/>
      </properties>
</testcase>

警告:record_property是一个实验性功能,将来可能会发生变化。另外,这将破坏一些XML结构验证,与某些持续集成软件一起使用时,可能会导致一些问题。

修改xml节点属性

def test_function(self, record_xml_attribute):
    record_xml_attribute("assertions", "REQ-1234")
    record_xml_attribute("classname", "custom_classname")
    print("hello world")
    assert True

与record_property不同, 它不会在节点下添加子元素,而是在生成的testcase标签内添加一个属性assertions ="REQ-1234",并使用classname = custom_classname覆盖默认的classname属性:

<testcase assertions="REQ-1234" classname="custom_classname" file="test_mod.py" line="43" name="test_function" time="0.001"/>

修改套件属性

4.5版本新特性。
record_testsuite_property接收两个参数namevalue以构成<property>标签,其中,name必须为字符串,value会转换为字符串并进行XML转义

@pytest.fixture(scope="session", autouse=True)
def log_global_env_facts(record_testsuite_property):
    record_testsuite_property("ARCH", "PPC")
    record_testsuite_property("STORAGE_TYPE", "CEPH")

class TestMe:
    def test_foo(self):
        assert True

报告中:

<testsuites>
      <testsuite errors="0" failures="0" hostname="Liujin" name="pytest_chinese_doc" skipped="0" tests="2" time="1.048" timestamp="2020-07-07T16:28:45.192945">
            <properties>
                  <property name="ARCH" value="PPC"/>
                  <property name="STORAGE_TYPE" value="CEPH"/>
            </properties>
            <testcase classname="test_mod" file="test_mod.py" line="9" name="test_func" time="1.000"/>
            <testcase classname="test_mod.TestMe" file="test_mod.py" line="57" name="test_foo" time="0.000"/>
      </testsuite>
</testsuites>

生成的测试报告表现为:在testsuite节点中,多了一个properties子节点,包含所有新增的属性节点,而且,它和所有的testcase节点是平级的。

创建结果日志格式文件

使用如下命令,可以在指定的path中创建一个纯文本的测试报告(PS:不推荐使用,官方6.0就摒弃了):

pytest --resultlog=path

将测试报告发送到在线pastebin服务

  • 为每一个失败的测试用例创建一个URL
    pytest --pastebin=failed
    也可以通过添加-x选项,只为第一个失败的测试用例创建一个URL
  • 为所有的测试用例创建一个URL
    pytest --pastebin=all
    用例执行完后,会自动生成一个URL,点击它即可在浏览器中查看结果:
========================================================================== Sending information to Paste Service ===========================================================================
test_mod.py:12: AssertionError --> https://bpaste.net/show/CF7A
================================================================================= short test summary info =================================================================================
FAILED test_mod.py::test_func - assert 1 == 2
==================================================================================== 1 failed in 9.40s ====================================================================================

尽早加载插件

pytest -p mypluginmodule

插件不能用

pytest -p no:doctest

在Python代码调用pytest

pytest.main()

  • 这个方法和你直接在命令行执行pytest的效果几乎一样,只是不会触发SystemExit,而是返回exitcode;
  • 传递参数
    pytest.main(["-x", "mytestdir"])
  • 指定一个插件
import pytest


class MyPlugin:
    def pytest_sessionfinish(self):
        print("*** test run reporting finishing")


pytest.main(["-qq"], plugins=[MyPlugin()])
posted @ 2020-07-07 11:25  无罪的坏人  阅读(332)  评论(0编辑  收藏  举报