• Pytest(上篇)


    摘要

    pytest是成熟的功能齐全的Python测试工具,有助于编写更好的程序。

    pytest基本知识

    参考官方文档翻译过来,了解一下pytest知识点

            pytest中可以按节点ID运行测试。

    在命令行中指定测试方法的另一个示例:

    pytest test_mod.py::TestClass::test_method

            通过标记表达式运行测试

    pytest -m slow

    将运行用@pytest.mark.slow装饰器装饰的所有测试。

           详细的总结报告

    pytest –ra

           分析测试执行持续时间

    要获取最慢的10个测试持续时间的列表,请执行以下操作:

    pytest --durations=10

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

    为每个测试失败创建一个URL:

    pytest --pastebin=failed

    为整个测试会话日志创建一个URL

    pytest --pastebin=all

            从python代码中调用pytest

    pytest.main()

    注意:

    调用pytest.main()将导致导入你的测试及其导入的任何模块。由于python导入系统的缓存机制,pytest.main()从同一进程进行后续调用不会反映两次调用之间对这些文件的更改。因此,pytest.main()不建议从同一进程进行多次调用(例如,以重新运行测试)。

             断言

    使用标准python assert来验证python测试中的期望和值。

            conftest.py

    对于可能的值scope有:function,class,module,package或session。

    1. pytest将建立一个字符串,它是用于在参数化fixture,例如每个fixtures测试ID。这些ID可以用于-k选择要运行的特定情况,并且当一个故障发生时,它们还将标识特定情况。使用pytest运行--collect-only将显示生成的ID。
    2.  使用直接测试参数化fixture

    给定测试文件的结构为:

    tests/
        __init__.py
        conftest.py
            # content of tests/conftest.py
            import pytest
            @pytest.fixture
            def username():
                return 'username'
            @pytest.fixture
            def other_username(username):
                return 'other-' + username
     
        test_something.py
            # content of tests/test_something.py
            import pytest
     
            @pytest.mark.parametrize('username', ['directly-overridden-username'])
            def test_username(username):
                assert username == 'directly-overridden-username'
     
            @pytest.mark.parametrize('username', ['directly-overridden-username-other'])
            def test_username_other(other_username):
                assert other_username == 'other-directly-overridden-username-other'

    使用非参数化的参数覆盖参数化fixture

    给定测试文件的结构为:

    tests/
        __init__.py
     
        conftest.py
            # content of tests/conftest.py
            import pytest
     
            @pytest.fixture(params=['one', 'two', 'three'])
            def parametrized_username(request):
                return request.param
     
            @pytest.fixture
            def non_parametrized_username(request):
                return 'username'
     
        test_something.py
            # content of tests/test_something.py
            import pytest
     
            @pytest.fixture
            def parametrized_username():
                return 'overridden-username'
     
            @pytest.fixture(params=['one', 'two', 'three'])
            def non_parametrized_username(request):
                return request.param
     
            def test_username(parametrized_username):
                assert parametrized_username == 'overridden-username'
     
            def test_parametrized_username(non_parametrized_username):
                assert non_parametrized_username in ['one', 'two', 'three']
     
        test_something_else.py
            # content of tests/test_something_else.py
            def test_username(parametrized_username):
                assert parametrized_username in ['one', 'two', 'three']
     
            def test_username(non_parametrized_username):
                assert non_parametrized_username == 'username'

    monkeypatch

    简单的api获取例子

    # contents of app.py, a simple API retrieval example

    import requests

     

     

    def get_json(url):

        """Takes a URL, and returns the JSON."""

        r = requests.get(url)

        return r.json()

    此外,如果该模拟程序旨在应用于所有测试,则fixture可以将其移动到conftest.py文件中并使用with autouse=True选项。

    设置捕获方法或禁用捕获

    有两种pytest执行捕获的方法:

    • 文件描述符(FD)级别捕获(默认):将捕获对操作系统文件描述符1和2的所有写操作。
    • sys级别捕获:仅写入Python文件,sys.stdout 并且sys.stderr将被捕获​​。不捕获对文件描述符的写入。

    您可以从命令行影响输出捕获机制:

    pytest -s            # disable all capturing
    pytest --capture=sys # replace sys.stdout/stderr with in-mem files
    pytest --capture=fd  # also point filedescriptors 1 and 2 to temp file

    内部pytest警告

    pytestWarning

    基类:UserWarning。

    pytest发出的所有警告的基类。

    pytestAssertRewriteWarning

    基类:pytestWarning。

    pytest断言重写模块发出的警告。

    pytestCacheWarning

    基地:pytestWarning。

    缓存插件在各种情况下发出的警告。

    pytestCollectionWarning

    基类:pytestWarning。

    pytest无法在模块中收集文件或符号时发出警告。

    pytestConfigWarning

    基地:pytestWarning。

    针对配置问题发出警告。

    pytestDeprecationWarning

    基类:pytest.pytestWarning,DeprecationWarning。

    在将来的版本中将删除的功能的警告类。

    pytestExperimentalApiWarning

    基类:pytest.pytestWarning,FutureWarning。

    警告类别,用于表示pytest中的实验。请谨慎使用,因为API可能会更改,甚至在将来的版本中会完全删除。

    pytestUnhandledCoroutineWarning

    基类:pytestWarning

    当pytest遇到作为协程的测试函数时发出警告,但任何异步感知插件均未处理该警告。本机不支持协程测试功能。

    pytestUnknownMarkWarning

    基类:pytestWarning。

    使用未知标记会发出警告。

            doctest

    就像普通的一样conftest.py,fixtures是在目录树conftest中发现的。这意味着,如果将doctest与源代码一起放入,则相关的conftest.py需要位于同一目录树中。fixtures不会在同级目录树中发现!

            跳过和xfail

    一个xfail意味着你期望测试失败的某些原因。一个常见的示例是对尚未实现的功能或尚未修复的错误进行测试。如果尽管测试通过但预期会失败(标记为pytest.mark.xfail),则为xpass,并将在测试摘要中报告。

    pytest分别统计和列出跳过xfail测试。默认情况下,不显示有关跳过/未通过测试的详细信息,以避免混乱输出。

    pytest -rxXs  # show extra info on xfailed, xpassed, and skipped tests

    如何在不同情况下跳过模块中的测试的快速指南:

    1)无条件跳过模块中的所有测试:

    pytestmark = pytest.mark.skip("all tests still WIP")

    2)根据某些条件跳过模块中的所有测试:

    pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="tests for linux only")

    3)如果缺少某些导入,请跳过模块中的所有测试:

    pexpect = pytest.importorskip("pexpect")

    跨测试运行

    该插件提供了两个命令行选项,以从上次pytest调用重新运行失败:

    • --lf,--last-failed-仅重新运行失败。
    • --ff,--failed-first-先运行故障,然后测试的其余部分

    最后一次运行没有测试失败,或者找不到缓存的lastfailed数据,

    pytest则可以使用以下--last-failed-no-failures选项之一将该选项配置为运行所有测试或不运行测试:

    pytest --last-failed --last-failed-no-failures all    # run all tests (default behavior)

    pytest --last-failed --last-failed-no-failures none   # run no tests and exit

    pytest支持unittest开箱即用地运行基于Python 的测试

    到目前为止,pytest不支持以下功能:

    • load_tests协议 ;
    • 子测试 
    1. 具有在给定上下文中自动使用的fixture,@pytest.fixture(autouse=True) 
    2. xunit样式: 在每个模块/类/功能的基础上实现fixture

    Module setup/teardown

    def setup_module(module):

               """ setup any state specific to the execution of the given module."""

    def teardown_module(module):

    """ teardown any state that was previously setup with a setup_module

        method.

        """

    Class setup/teardown

    @classmethod

    def setup_class(cls):

    """ setup any state specific to the execution of the given class (which

        usually contains tests).

        """

    @classmethod

    def teardown_class(cls):

    """ teardown any state that was previously setup with a call to

        setup_class.

        """

    方法和功能的setup/teardown

    def setup_method(self,method):

    """ setup any state tied to the execution of the given method in a

        class.  setup_method is invoked for every test method of a class.

        """

    def teardown_method(self,method):

          """ teardown any state that was previously setup with a setup_method

        call.

        """

    def setup_function(function):

         """ setup any state tied to the execution of the given function.

        Invoked for every test function in the module.

        """

    def teardown_function(function):

    """ teardown any state that was previously setup with a setup_function call.

        """

           工具启动时插件发现顺序

    通常最好将conftest.py文件保存在顶级测试或项目根目录中。

    典型setup.py摘录:

    setup(..., entry_points={"pytest11": ["foo = pytest_foo.plugin"]}, ...)

    通过名字来访问另一个插件

    插件想要与另一个插件的代码协作,则可以通过插件管理器获取引用

    plugin = config.pluginmanager.get_plugin("name_of_plugin")

    pytest API

    caplog()

    访问和控制日志捕获。

    捕获的日志可通过以下属性/方法获得:

    * caplog.messages        -> list of format-interpolated log messages

    * caplog.text            -> string containing formatted log output

    * caplog.records         -> list of logging.LogRecord instances

    * caplog.record_tuples   -> list of (logger_name, level, message) tuples

    * caplog.clear()         -> clear captured records and formatted log output string

    pip用于安装应用程序和所有依赖项以及pytest程序包本身。这样可确保您的代码和依赖项与系统Python安装隔离。

    pytest.approx

    失败的视频/屏幕截图pytest-splinter、pytest-bdd

    考虑以下文件和目录布局:

    root/

    |- foo/

       |- __init__.py

       |- conftest.py

       |- bar/

          |- __init__.py

          |- tests/

             |- __init__.py

             |- test_foo.py

    执行时会执行以下所有的目录文件

    pytest root/

    测试模块名称不能相同

    从中找到rootdir的算法args:

    • 确定指定的公共祖先目录,这些目录args被识别为文件系统中存在的路径。如果找不到此类路径,则将公共祖先目录设置为当前工作目录。
    • 寻找pytest.ini,tox.ini并setup.cfg在父目录和文件向上。如果匹配,它将成为ini文件,并且其目录将成为rootdir。
    • 如果未找到ini文件,请setup.py从公共祖先目录向上查找以确定rootdir。
    • 如果没有setup.py被发现,寻找pytest.ini,tox.ini并 setup.cfg在每个指定args向上。如果匹配,它将成为ini文件,并且其目录将成为rootdir。
    • 如果找不到ini文件,则使用已经确定的公共祖先作为根目录。这允许在不属于包且没有任何特定ini文件配置的结构中使用pytest。

    pytest实际运用

    命令行中执行

    pip install pytest

    pip show pytest

    想要在Pycharm环境中测试用例以pytest形式运行,可以这样设置。Settings->Tools->Python Integreated Tools,选择默认的test runner为“py.test”。

    pytest生成html报告,命令行中安装pytest-html插件。

    pip install pytest-html

    cmd中执行 >pytest test_***.py --html=./testreport.html

    在报告存放路径下,可以用Chrome浏览器打开本地html链接,如file:///E:/ATS/Test_doctor/testreport.html。

    pytest测试报告形式如下所示

    pytest失败重跑机制。在UI自动化测试中,由于受到网络不稳定、appium server等影响,不是每次正确的测试用例都能运行通过,于是使用pytest的失败重跑提高自动化测试的稳定性。安装插件pytest-rerunsfailures,在命令行执行

    pip install pytest-rerunsfailures

    >pytest test_patlist.py --reruns 3 --html=./report.html

    实现对测试用例重跑3次,3次不通过才算失败,反之则运行成功。

    实践得到,pytest不需要创建类,直接定义函数即可。

    pytest 和unittest 最大的区别是不要求我们必须创建测试类, 自己创建的测试类也不需要继承于unittest.TestCase类。但是pytest 要求我们创建的测试文件,测试类、方法、测试函数必须以“test”开头,pytest默认按照这个规则查找测试用例并执行它们。”

    pytest避开的坑

    1)PytestUnknownMarkWarning

    解决方案:

    若是单个标签

    在conftest.py添加如下代码,直接拷贝过去,把标签名改成你自己的就行了

    def pytest_configure(config):
    config.addinivalue_line(
    "markers", "login_success" # login_success 是标签名
    )

    若是多个标签

    在conftest.py添加如下代码,直接拷贝过去,把标签名改成你自己的就行了

    def pytest_configure(config):
    marker_list = ["testmark1","testmark2","testmark3"]  # 标签名集合
    for markers in marker_list:
    config.addinivalue_line(
    "markers", markers
    )

    这样添加之后,再次运行,不再出现warning。

    2)UnicodeDecodeError: 'gbk' codec can't decode byte 0xae in position 42: illegal m

    ultibyte sequence

    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.ini文件,内容是这样:

    [pytest]

    doctest_encoding = UTF-8  #默认编码是UTF-8

    删除pytest.ini文件之后,再次运行就可以了。

    3)  pytest执行用例时collected 0 items

    pytest执行的文件需要以test开头才能被查找到。

    4)断言assert

    断言元素是否存在,例如

    element = appdriver.find_element_by_id('icon_id')

    assert element

  • 相关阅读:
    2019年6月4号总结
    2019年5月21号总结
    2019年5月8号总结
    2019年5月6号总结
    2019年5月5号总结
    2019年4月18号总结
    java错误笔记之判断字符知否为空出错
    错误笔记:静态方法不能实例化,直接用类名.方法名调用
    Thymeleaf中"th:each""th:if"的用法解析
    @ResponseBody 表示返回的是josn
  • 原文地址:https://www.cnblogs.com/fengye151/p/12901168.html
Copyright © 2020-2023  润新知