zoukankan      html  css  js  c++  java
  • Pytest自动化测试

    简介

    pytest是动态编程语言Python专用的测试框架,它具有易于上手、功能强大、可扩展性好、兼容性强、效率高、第三方插件丰富等特点。

    功能特征:

    • 完整的文档,包括安装,教程和PDF文档
    • 简单而又详细的断言模式(使用纯assert语句)
    • 自动发现测试模块和功能(以test为标识)
    • 可以运行unittest和nose框架的测试用例
    • 灵活的固件,用于管理小型或参数化的长期测试资源
    • 丰富的插件架构,拥有三百多个外部插件和丰富的社区

    编写规则:

    • 测试文件以test_开头(以_test结尾也可以)
    • 测试类以Test开头,并且不能带有 init 方法
    • 测试函数以test_开头
    • 断言使用基本的assert即可

    自动发现规则:

    如果未指定任何参数,则从testpaths(如果已配置)或当前目录开始收集。
    另外,命令行参数可以在目录、文件名或节点ID的任何组合中使用。
    在这些目录中,搜索包含 test_*.py 或 *_test.py 的测试文件。
    从这些文件中,收集以test前缀的测试方法,或者在Test前缀的测试类(无__init__方法)中的以test前缀的测试方法。

    官方文档:https://docs.pytest.org/en/latest/contents.html

     


    安装

    打开bash命令行,运行以下命令:

    pip install -U pytest

    检查是否安装了正确的版本:

    $ pytest --version
    pytest 6.1.2

     


    示例

    创建一个简单的测试函数:

     
    # test_sample.py
    # 被测功能
    def func(x):
        return x + 1
    
    # 测试成功
    def test_pass():
        assert func(3) == 4
    
    # 测试失败
    def test_fail():
        assert func(3) == 5
     

    现在开始执行测试功能:

     
    E:workspace-pyPytest>pytest
    ========================================================================== test session starts ==========================================================================
    platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
    rootdir: E:workspace-pyPytest
    plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
    collected 2 items                                                                                                                                                        
    
    test_sample.py .F                                                                                                                                                  [100%]
    
    =============================================================================== FAILURES ================================================================================
    _______________________________________________________________________________ test_fail _______________________________________________________________________________
    
        def test_fail():
    >       assert func(3) == 5
    E       assert 4 == 5
    E        +  where 4 = func(3)
    
    test_sample.py:16: AssertionError
    ======================================================================== short test summary info ========================================================================
    FAILED test_sample.py::test_fail - assert 4 == 5
    ====================================================================== 1 failed, 1 passed in 0.16s ======================================================================
     

    这里未指定测试用例,pytest将依据自动发现规则检索并执行测试,等同于 pytest ./test_sample.py

    •   pytest 使用 . 标识测试成功(PASSED)
    •   pytest 使用 F 标识测试失败(FAILED)
    •   可以使用 -v 选项,显示测试的详细信息
    •   可以使用 -h 查看 pytest 的所有选项

     


    标记

    默认情况下,pytest 会递归查找当前目录下所有以 test 开始或结尾的 Python 脚本,并执行文件内的所有以 test 开始或结束的函数和方法。

    1、如果你想定运行测试用例,可以通过 :: 显式标记(文件名:: 类名::方法名)。

     
    E:workspace-pyPytest>pytest test_sample.py::test_pass
    ========================================================================== test session starts ==========================================================================
    platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
    rootdir: E:workspace-pyPytest
    plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
    collected 1 item                                                                                                                                                         
    
    test_sample.py .                                                                                                                                                   [100%]
    
    =========================================================================== 1 passed in 0.05s ===========================================================================
     

    2、如果你想选择一些测试用例,可以使用 -k 模糊匹配

     
    E:workspace-pyPytest>pytest -k pass test_sample.py
    ========================================================================== test session starts ==========================================================================
    platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
    rootdir: E:workspace-pyPytest
    plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
    collected 2 items / 1 deselected / 1 selected                                                                                                                            
    
    test_sample.py .                                                                                                                                                   [100%]
    
    ==================================================================== 1 passed, 1 deselected in 0.02s ====================================================================
     

    3、如果你想跳过个别测试用例,可以使用 pytest.mark.skip(),或者 pytest.mark.skipif(条件表达式)。

    # 测试失败
    @pytest.mark.skip()
    def test_fail():
        assert func(3) == 5
     
    E:workspace-pyPytest>pytest -v test_sample.py
    ========================================================================== test session starts ==========================================================================
    platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 -- c:python37python.exe
    cachedir: .pytest_cache
    metadata: {'Python': '3.7.3', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'pytest': '6.0.2', 'py': '1.9.0', 'pluggy': '0.13.0'}, 'Plugins': {'allure-pytest': '2.8.
    18', 'cov': '2.10.1', 'html': '2.1.1', 'metadata': '1.8.0', 'rerunfailures': '9.1', 'xdist': '2.1.0'}}
    rootdir: E:workspace-pyPytest
    plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
    collected 2 items                                                                                                                                                        
    
    test_sample.py::test_pass PASSED                                                                                                                                   [ 50%]
    test_sample.py::test_fail SKIPPED                                                                                                                                  [100%]
    
    ===================================================================== 1 passed, 1 skipped in 0.07s ======================================================================
     

    4、如果你想捕捉一些异常,可以使用pytest.raises()。

     
    # test_raises.py
    
    def test_raises():
        with pytest.raises(TypeError) as e:
            connect('localhost', '6379')
        exec_msg = e.value.args[0]
        assert exec_msg == 'port type must be int'
     

    5、如果你事先知道测试函数会执行失败,但又不想直接跳过,而是希望显示的提示,可以使用pytest.mark.xfail()。

    # 测试失败
    @pytest.mark.xfail()
    def test_fail():
        assert func(3) == 5
     
    E:workspace-pyPytest>pytest -k fail test_sample.py
    ========================================================================== test session starts ==========================================================================
    platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
    rootdir: E:workspace-pyPytest
    plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
    collected 2 items / 1 deselected / 1 selected                                                                                                                            
    
    test_sample.py x                                                                                                                                                   [100%]
    
    =================================================================== 1 deselected, 1 xfailed in 0.05s ====================================================================
     

    6、如果你想对某个测试点进行多组数据测试,可以使用 pytest.mark.parametrize(argnames, argvalues) 参数化测试,即每组参数都独立执行一次测试

    注意:以往我们可以把这些参数写在测试函数内部进行遍历,但是当某组参数导致断言失败,测试则就终止了。

    # 测试成功
    @pytest.mark.parametrize('data', [1, 2, 3])
    def test_pass(data):
        assert func(data) == 4
     
    E:workspace-pyPytest>pytest -k pass test_sample.py
    ========================================================================== test session starts ==========================================================================
    platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
    rootdir: E:workspace-pyPytest
    plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
    collected 4 items / 1 deselected / 3 selected                                                                                                                            
    
    test_sample.py FF.                                                                                                                                                 [100%]
    
    =============================================================================== FAILURES ================================================================================
    _____________________________________________________________________________ test_pass[1] ______________________________________________________________________________
    
    data = 1
    
        @pytest.mark.parametrize('data', [1, 2, 3])
        def test_pass(data):
    >       assert func(data) == 4
    E       assert 2 == 4
    E        +  where 2 = func(1)
    
    test_sample.py:11: AssertionError
    _____________________________________________________________________________ test_pass[2] ______________________________________________________________________________
    
    data = 2
    
        @pytest.mark.parametrize('data', [1, 2, 3])
        def test_pass(data):
    >       assert func(data) == 4
    E       assert 3 == 4
    E        +  where 3 = func(2)
    
    test_sample.py:11: AssertionError
    ======================================================================== short test summary info ========================================================================
    FAILED test_sample.py::test_pass[1] - assert 2 == 4
    FAILED test_sample.py::test_pass[2] - assert 3 == 4
    =============================================================== 2 failed, 1 passed, 1 deselected in 0.17s ===============================================================
     

     


    固件

    固件(Fixture)是一些函数,pytest 会在执行测试函数之前(或之后)加载运行它们。

    我们可以利用固件做任何事情,其中最常见的可能就是数据库的初始连接和最后关闭操作。

    1、Pytest使用pytest.fixture()定义固件,为了在实际工程中可以更大程度上复用,我们更多的是使用文件conftest.py集中管理固件(pytest会自动调用)。

    # conftest.py
    import pytest
    
    @pytest.fixture()
    def data():
        return 3
    # 测试成功
    def test_pass(data):
        assert func(data) == 4
     
    E:workspace-pyPytest>pytest -k pass test_sample.py
    ========================================================================== test session starts ==========================================================================
    platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
    rootdir: E:workspace-pyPytest
    plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
    collected 2 items / 1 deselected / 1 selected                                                                                                                            
    
    test_sample.py .                                                                                                                                                   [100%]
    
    ==================================================================== 1 passed, 1 deselected in 0.05s ====================================================================
     

    2、Pytest 使用 yield 关键词将固件分为两部分,yield 之前的代码属于预处理,会在测试前执行;yield 之后的代码属于后处理,将在测试完成后执行。

     
    # conftest.py
    import pytest
    
    @pytest.fixture()
    def db():
        print('opened')
        yield
        print('closed')
     
    # 测试成功
    def test_pass(db):
        assert func(3) == 4
     
    E:workspace-pyPytest>pytest -s -k pass test_sample.py
    ========================================================================== test session starts ==========================================================================
    platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
    rootdir: E:workspace-pyPytest
    plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
    collected 2 items / 1 deselected / 1 selected                                                                                                                            
    
    test_sample.py opened
    .closed
    
    
    ==================================================================== 1 passed, 1 deselected in 0.02s ====================================================================
     

    3、为了更精细化控制固件,pytest使用作用域来进行指定固件的使用范围。

    在定义固件时,通过 scope 参数声明作用域,可选项有:

    • function: 函数级,每个测试函数都会执行一次固件(默认值);
    • class: 类级别,每个测试类执行一次,所有方法都可以使用;
    • module: 模块级,每个模块执行一次,模块内函数和方法都可使用;
    • session: 会话级,一次测试只执行一次,所有被找到的函数和方法都可用。
     
    # conftest.py
    import pytest
    
    @pytest.fixture(scope='function', autouse=True)
    def func_scope():
        pass
    
    @pytest.fixture(scope='module', autouse=True)
    def mod_scope():
        pass
    
    @pytest.fixture(scope='session')
    def sess_scope():
        pass
    
    @pytest.fixture(scope='class')
    def class_scope():
        pass
     

      # 测试成功
      @pytest.mark.usefixtures('sess_scope')
      def test_pass(class_scope):
          assert func(3) == 4

     
    E:workspace-pyPytest>pytest --setup-show -k pass test_sample.py
    ========================================================================== test session starts ==========================================================================
    platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0
    rootdir: E:workspace-pyPytest
    plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0
    collected 2 items / 1 deselected / 1 selected                                                                                                                            
    
    test_sample.py
    SETUP    S sess_scope
        SETUP    M mod_scope
          SETUP    C class_scope
            SETUP    F func_scope
            test_sample.py::test_pass (fixtures used: class_scope, func_scope, mod_scope, sess_scope).
            TEARDOWN F func_scope
          TEARDOWN C class_scope
        TEARDOWN M mod_scope
    TEARDOWN S sess_scope
    
    ==================================================================== 1 passed, 1 deselected in 0.02s ====================================================================
     

    我们可以把固件作为入参,还可以使用@pytest.mark.usefixtures('class_scope'),或者指定autouse参数让固件自动执行。

    并且还可以指定params参数来实现固件的参数化,以及指定name参数来修改固件名。

    • 可以使用 -s 选项,显示print打印信息
    • 可以使用 --setuo-show 选项,显示详细的固件信息

    4、内置固件

    tmpdir & tmpdir_factory:用于临时文件和目录管理,默认会在测试结束时删除。
    pytestconfig:用于读取命令行参数和配置文件
    capsys:用于捕获 stdout 和 stderr 的内容,并临时关闭系统输出。
    monkeypath:用于运行时动态修改类或模块。
    recwarn:用于捕获程序中 warnings 产生的警告。

     Pytest学习手册:https://learning-pytest.readthedocs.io/zh/latest/index.html

  • 相关阅读:
    C#操作配置文件
    IIS的启动与停止命令
    我的SQL里哪个语句占用的CPU最多?
    Redis 安装
    redis启动出错Creating Server TCP listening socket 127.0.0.1:6379: bind: No error
    多线程和异步
    mvc 使用Newtonsoft.Json进行序列化json数据
    深入理解JavaScript Hijacking原理
    C#中的partial class(部分类)
    在ASP.NET MVC中使用DropDownList
  • 原文地址:https://www.cnblogs.com/chenyablog/p/15145315.html
Copyright © 2011-2022 走看看