美文网首页
Pytest学习笔记(非完整版)

Pytest学习笔记(非完整版)

作者: qixuezhiren | 来源:发表于2018-09-02 21:51 被阅读0次

    Pytest学习笔记

    记录下pytest官方文档的阅读笔记,以便后续参考使用。非完整版,个人理解为主,难免有误,望理解。

    安装与快速使用

    安装

    $ pip install pytest
    $ pytest --version
    

    第一个test

    01\test_sample.py

    def func(x):
        return x + 1
    
    
    def test_answer():
        assert  func(3) == 5
    

    运行

    # 默认会执行当前目录及子目录的所有test_*.py或*_test.py文件。用例执行成功为.,失败为F
    $ pytest
    
    # 静默执行
    $ pytest -q 01\test_sample.py
    
    # 调试方式执行,可以打印print日志等详情信息
    $ pytest 01\test_sample.py -s -v
    
    # python模块方式执行
    $ python -m pytest 01\test_sample.py
    
    # 执行单个目录下的tests
    $ python 01\
    

    test类包含多个tests

    01\test_class.py

    # pytest默认会执行所有test_前缀的函数
    class TestClass(object):
    
        def test_one(self):
            x = "this"
            assert 'h' in x
    
        def test_two(self):
            x = 'hello'
            assert hasattr(x, 'check')
    

    pytest常见的exit codes

    Exit code 0 所有tests全部通过

    Exit code 1 部分tests失败了

    Exit code 2 用户中止test执行

    Exit code 3 执行test时,内部报错

    Exit code 4 pytest命令使用姿势不对

    Exit code 5 无tests可执行

    pytest常见帮助选项

    $ pytest --version      # 显示版本信息
    $ pytest --fixtures     # 显示内置可用的函数参数
    $ pytest -h | --help    # 显示帮助信息
    $ pytest -x             # 第一个失败时即停止
    $ pytest --maxfail=2    # 两个失败后即停止
    

    pytest fixtures(明确的、模块化的、可扩展的)

    1. fixtures由明确的命名,可以通过测试函数、模块、类或整个项目激活
    2. fixtures以模块化的方式实现,因为每个名称会触发一个fixtures函数,同时函数本身也可以使用其它fixtures
    3. fixtures管理从简单的单元到复杂的功能测试,允许参数化fixtures和根据配置和组件进行测试或通过函数、类、模块或整个test会话范围重用fixtures

    Fixtures作为函数参数

    测试函数可以接收fixture对象作为输入参数,使用@pytest.fixture

    test_smtpsimple.py

    import pytest
    
    
    @pytest.fixture
    def smtp_connection():
        import smtplib
        return smtplib.SMTP(host='smtp.qq.com',port=587, timeout=5)
    
    
    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
        assert 0
    
    $ pytest 01\test_smtpsimple.py
    ============================= test session starts =============================
    platform win32 -- Python 3.6.4, pytest-3.7.4, py-1.6.0, pluggy-0.7.1
    rootdir: D:\projects\python\pytest_notes, inifile:
    collected 1 item                                                               
    
    01\test_smtpsimple.py F                                                  [100%]
    
    ================================== FAILURES ===================================
    __________________________________ test_ehlo __________________________________
    
    smtp_connection = <smtplib.SMTP object at 0x0000021F3E041828>
    
        def test_ehlo(smtp_connection):
            response, msg = smtp_connection.ehlo()
            assert response == 250
    >       assert 0
    E       assert 0
    
    01\test_smtpsimple.py:13: AssertionError
    ========================== 1 failed in 1.45 seconds ===========================
    
    # 测试函数调用smtp_connection参数,而smptlib.SMTP实列由fixture函数创建
    

    Fixtures 依赖注入

    Fixtures 允许测试函数非常容易的接收和使用特定的预初始化程序对象,而无需特别去关注import/setup/cleanup等细节

    config.py:共享fixture函数

    如果多个测试文件需要用到一个fixture函数,则把它写到conftest.py文件当中。使用时无需导入这个fixture函数,因为pytest会自动获取

    共享测试数据

    1. 如果在测试中,需要从文件加载测试数据到tests,可以使用fixture方式加载,pytest有自动缓存机制
    2. 另外一种方式是添加测试数据文件到tests目录,如使用pytest-datadirpytest-datafiles插件

    Scope:共享一个fixture实列(类、模块或会话)

    Scope - module

    conftest.py

    import pytest
    import smtplib
    
    
    @pytest.fixture(scope='module')
    def smtp_connection():
        return smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)
    

    test_module.py

    
    
    def test_ehlo(smtp_connection):
        response, msg = smtp_connection.ehlo()
        assert response == 250
        assert b'smtp.qq.com' in msg
        assert 0
    
    
    def test_noop(smtp_connection):
        response, msg = smtp_connection.noop()
        assert response == 250
        assert 0
    

    执行

    $ pytest 01\test_module.py
    ============================= test session starts =============================
    platform win32 -- Python 3.6.4, pytest-3.7.4, py-1.6.0, pluggy-0.7.1
    rootdir: D:\projects\python\pytest_notes, inifile:
    collected 2 items                                                              
    
    01\test_module.py FF                                                     [100%]
    
    ================================== FAILURES ===================================
    __________________________________ test_ehlo __________________________________
    
    smtp_connection = <smtplib.SMTP object at 0x0000018E14F13780>   # 1
    
        def test_ehlo(smtp_connection):
            response, msg = smtp_connection.ehlo()
            assert response == 250
            assert b'smtp.qq.com' in msg
    >       assert 0
    E       assert 0
    
    01\test_module.py:7: AssertionError
    __________________________________ test_noop __________________________________
    
    smtp_connection = <smtplib.SMTP object at 0x0000018E14F13780>   # 2 可以看到1和2的实列对象为同一个
    
        def test_noop(smtp_connection):
            response, msg = smtp_connection.noop()
    >       assert response == 250
    E       assert 530 == 250
    
    01\test_module.py:12: AssertionError
    ========================== 2 failed in 1.42 seconds ===========================
    

    Scope - session

    import pytest
    import smtplib
    
    
    # 所有tests能使用到它的,都是共享同一个fixture值
    @pytest.fixture(scope='session')
    def smtp_connection():
        return smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)
    

    Scope - class

    import pytest
    import smtplib
    
    
    # 每个test类,都是共享同一个fixture值
    @pytest.fixture(scope='class')
    def smtp_connection():
        return smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)
    

    高级别的scope fixtures第一个实例化

    @pytest.fixture(scope="session")
    def s1():
        pass
    
    @pytest.fixture(scope="module")
    def m1():
        pass
    
    @pytest.fixture
    def f1(tmpdir):
        pass
    
    @pytest.fixture
    def f2():
        pass
    
    def test_foo(f1, m1, f2, s1):
    ...
    
    1. s1: 是最高级别的fxiture(session)
    2. m1: 是第二高级别的fixture(module)
    3. tmpdir: 是一个function的fixture,依赖f1
    4. f1:在test_foo列表参数当中,是第一个function的fixture
    5. f2:在test_foo列表参数当中,是最后一个function的fixture

    Fixture结束或执行teardown代码

    1. 当fixture超过其scope范围,pytest支持执行fixture特定的结束代码
    2. 使用yield替换return,所有yield声明之后的代码都作为teardown代码处理

    yield替换return

    conftest.py

    import pytest
    import smtplib
    
    
    # print和smtp_connection.close()只会在module范围内最后一个test执行结束后执行,除非中间有异常
    @pytest.fixture(scope='module')
    def smtp_connection():
        smtp_connection = smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)
        yield smtp_connection
        print('teardown smtp')  
        smtp_connection.close()
    

    执行

    $ pytest -s -q 01\test_module.py --tb=no
    FFteardown smtp
    
    2 failed in 0.96 seconds
    
    

    with替换yield

    conftest.py

    import pytest
    import smtplib
    
    
    # 使用了with声明,smtp_connection等test执行完后,自动关闭
    # 注意:yield之前的setup代码发生了异常,teardown代码将不会被调用
    @pytest.fixture(scope='module')
    def smtp_connection():
        with smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5) as smtp_connection:
            yield smtp_connection
    

    使用addfinalizer清理

    yield和addfinalizer方法类似,但addfinalizer有两个不同的地方

    1. 可以注册多个finalizer函数

    2. 不论setup代码是否发生异常,均会关闭所有资源

      @pytest.fixture
      def equipments(request):
         r = []
         for port in ('C1', 'C3', 'C28'):
             equip = connect(port)
             request.addfinalizer(equip.disconnect)
             r.append(equip)
         return r
      # 假设C28抛出一次,C1和C2将正常被关闭。当然异常发生在finalize函数注册之前,它将不被执行
      

    conftest.py

    import pytest
    import smtplib
    
    
    @pytest.fixture(scope='module')
    def smtp_connection(request):
        smtp_connection = smtplib.SMTP(host='smtp.qq.com', port=587, timeout=5)
    
        def fin():
            print('teardown smtp_connection')
            smtp_connection.close()
        request.addfinalizer(fin)
        return smtp_connection
    

    执行

    $ pytest -s -q 01\test_module.py --tb=no
    FFteardown smtp_connection
    
    2 failed in 0.99 seconds
    
    

    Fixture可以使用request对象来管理 测试内容

    Fixture函数可以接收一个request对象

    conftest.py

    import pytest
    import smtplib
    
    
    # 所有使用fixture的test module,都可以读取一个可选的smtpserver地址
    @pytest.fixture(scope='module')
    def smtp_connection(request):
        server = getattr(request.module, 'smtpserver', 'smtp.qq.com')
        smtp_connection = smtplib.SMTP(host=server, port=587, timeout=5)
    
        yield smtp_connection
        print('finalizing %s (%s)' % (smtp_connection, server))
        smtp_connection.close()
    

    执行1

    $ pytest -s -q --tb=no
    .FFFfinalizing <smtplib.SMTP object at 0x00000266A3912470> (smtp.qq.com)
    FF
    5 failed, 1 passed in 2.11 seconds
    

    test_anothersmtp.py

    smtpserver = 'mail.python.org'  # 自动读取并替换fixture默认的值
    
    
    def test_showhelo(smtp_connection):
        assert 0, smtp_connection.helo()
    

    执行2

    $ pytest -qq --tb=short 01\test_anothersmtp.py
    F                                                                        [100%]
    ================================== FAILURES ===================================
    ________________________________ test_showhelo ________________________________
    01\test_anothersmtp.py:5: in test_showhelo
        assert 0, smtp_connection.helo()
    E   AssertionError: (250, b'mail.python.org')
    E   assert 0
    -------------------------- Captured stdout teardown ---------------------------
    finalizing <smtplib.SMTP object at 0x000001C1F631DBE0> (mail.python.org)
    
    

    Fixture工厂模式

    在单个test中,fixture结果需要被多次使用

    @pytest.fixture
    def make_customer_record():
        def _make_customer_record(name):
            return {
                "name": name,
                "orders": []
            }
        return _make_customer_record
    
    def test_customer_records(make_customer_record):
        customer_1 = make_customer_record("Lisa")
        customer_2 = make_customer_record("Mike")
        customer_3 = make_customer_record("Meredith")
    
    # 创建的数据需要工厂管理时,采用这种方式
    @pytest.fixture
    def make_customer_record():
        created_records = []
        
        def _make_customer_record(name):
            record = models.Customer(name=name, orders=[])
            created_records.append(record)
            return record
        
        yield _make_customer_record
    
        for record in created_records:
            record.destroy()
            
    def test_customer_records(make_customer_record):
        customer_1 = make_customer_record("Lisa")
        customer_2 = make_customer_record("Mike")
        customer_3 = make_customer_record("Meredith")
    

    Fixture 参数

    Fixture可以参数化,当需要执行多次时

    conftest.py

    import pytest
    import smtplib
    
    
    @pytest.fixture(scope='module',
                    params=['smtp.qq.com', 'mail.python.org'])
    def smtp_connection(request):
        smtp_connection = smtplib.SMTP(host=request.param, port=587, timeout=5)
    
        yield smtp_connection
        print('finalizing %s' % (smtp_connection))
        smtp_connection.close()
    

    执行

    $ pytest -q  01\test_module.py
    FFFF                                                                     [100%]
    ================================== FAILURES ===================================
    ___________________________ test_ehlo[smtp.qq.com] ____________________________
    
    smtp_connection = <smtplib.SMTP object at 0x000002769B09A908>
    
        def test_ehlo(smtp_connection):
            response, msg = smtp_connection.ehlo()
            assert response == 250
            assert b'smtp.qq.com' in msg
    >       assert 0
    E       assert 0
    
    01\test_module.py:7: AssertionError
    ___________________________ test_noop[smtp.qq.com] ____________________________
    
    smtp_connection = <smtplib.SMTP object at 0x000002769B09A908>
    
        def test_noop(smtp_connection):
            response, msg = smtp_connection.noop()
    >       assert response == 250
    E       assert 530 == 250
    
    01\test_module.py:12: AssertionError
    _________________________ test_ehlo[mail.python.org] __________________________
    
    smtp_connection = <smtplib.SMTP object at 0x000002769B09AA58>
    
        def test_ehlo(smtp_connection):
            response, msg = smtp_connection.ehlo()
            assert response == 250
    >       assert b'smtp.qq.com' in msg
    E       AssertionError: assert b'smtp.qq.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDST
    ATUSCODES\n8BITMIME\nDSN\nSMTPUTF8'
    
    01\test_module.py:6: AssertionError
    ---------------------------- Captured stdout setup ----------------------------
    finalizing <smtplib.SMTP object at 0x000002769B09A908>
    _________________________ test_noop[mail.python.org] __________________________
    
    smtp_connection = <smtplib.SMTP object at 0x000002769B09AA58>
    
        def test_noop(smtp_connection):
            response, msg = smtp_connection.noop()
            assert response == 250
    >       assert 0
    E       assert 0
    
    01\test_module.py:13: AssertionError
    -------------------------- Captured stdout teardown ---------------------------
    finalizing <smtplib.SMTP object at 0x000002769B09AA58>
    4 failed in 4.29 seconds
    

    标记fixture参数

    test_fixture_marks.py

    import pytest
    
    
    @pytest.fixture(params=[0, 1, pytest.param(2, marks=pytest.mark.skip)])
    def data_set(request):
        return request.param
    
    
    def test_data(data_set):
        pass
    

    执行

    $ pytest 01\test_fixture_marks.py -v
    ============================= test session starts =============================
    platform win32 -- Python 3.6.4, pytest-3.7.4, py-1.6.0, pluggy-0.7.1 -- d:\projects\python\pytest_notes\.venv\scripts\python.exe
    cachedir: .pytest_cache
    rootdir: D:\projects\python\pytest_notes, inifile:
    collected 3 items                                                              
    
    01/test_fixture_marks.py::test_data[0] PASSED                            [ 33%]
    01/test_fixture_marks.py::test_data[1] PASSED                            [ 66%]
    01/test_fixture_marks.py::test_data[2] SKIPPED                           [100%]
    
    ===================== 2 passed, 1 skipped in 0.14 seconds =====================
    

    经典的xunit风格setup

    Module级别的setup/teardown

    如果有多个test函数或test类在一个模块内,可以选择的实现setup_module和teardown_module,一般模块内的所有函数都会调用一次

    def setup_module(module):
        pass
    
    def teardown_module(module):
        pass
    

    Class级别的setup/teardown

    在类内,所有的方法均会调用到

    @classmethod
    def setup_class(cls):
        pass
    
    @classmethod
    def teardown_class(cls):
        pass
    

    Method和Function级别的setup/teardown

    # 指定方法调用
    def setup_method(self, method):
        pass
    
    def teardown_method(self, method):
        pass
    
    # 模块级别内可以直接使用function调用
    def setup_function(function):
        pass
    
    def teardown_function(function):
        pass
    

    相关文章

      网友评论

          本文标题:Pytest学习笔记(非完整版)

          本文链接:https://www.haomeiwen.com/subject/zbkvwftx.html