Python单元测试框架——pytest(二)


前后置处理(固件/夹具)

测试用例:

​ 前置操作(准备工作-准备环境、数据、资源等)

​ 后置操作(清理工作-关闭环境、清理数据、释放资源等)

pytest常用的前后置处理有三种:setup/teardownfixtureconftest.py结合fixture。下面进行逐一介绍。

setup/teardown

pytest推荐的前后置处理是后面两种方式,setup/teardown方式已很少使用,所以这里仅做了解。

pytest的setup/teardown跟unittest类似,主要有3种(不止3种):

  • 模块级别:setup_module,teardown_module (对整个py文件起作用)
  • 类级别:setup_class,teardown_class (对单个测试类起作用)
  • 函数/方法级别:setup,teardown (对测试类中的测试方法起作用)
import pytest


def setup_module():
    print("\n-- setup_module --")


def teardown_module():
    print("\n-- steardown_module --")


class TestDemo10:

    @classmethod
    def setup_class(cls):
        print("\n-- setup_class --")

    @classmethod
    def teardown_class(cls):
        print("\n-- teardown_class --")

    def setup(self):
        print("\n-- setup --执行测试用例之前初始化代码:测试环境准备,准备测试数据")

    def teardown(self):
        print("\n-- teardown --在执行测试用例之后的扫尾工作:清理测试数据,恢复测试环境")

    def test_case1(self):
        x = 1
        assert x

    def test_case2(self):
        x = 0
        assert not x

    def test_case3(self):
        x = 'python'
        assert 'o' in x


class TestDemo11:

    def test_case4(self):
        x = 'python'
        assert 'i' not in x


if __name__ == '__main__':
    pytest.main()

执行测试:

$ pytest testcases/test_10_demo.py 
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 4 items                                                                                                                                                 

testcases/test_10_demo.py::TestDemo10::test_case1 
-- setup_module --

-- setup_class --

-- setup --执行测试用例之前初始化代码:测试环境准备,准备测试数据
PASSED
-- teardown --在执行测试用例之后的扫尾工作:清理测试数据,恢复测试环境

testcases/test_10_demo.py::TestDemo10::test_case2 
-- setup --执行测试用例之前初始化代码:测试环境准备,准备测试数据
PASSED
-- teardown --在执行测试用例之后的扫尾工作:清理测试数据,恢复测试环境

testcases/test_10_demo.py::TestDemo10::test_case3 
-- setup --执行测试用例之前初始化代码:测试环境准备,准备测试数据
PASSED
-- teardown --在执行测试用例之后的扫尾工作:清理测试数据,恢复测试环境

-- teardown_class --

testcases/test_10_demo.py::TestDemo11::test_case4 PASSED
-- steardown_module --

------------------------------------------- generated html file: file:///Users/laobai/workspaces/testdemo01/report.html -------------------------------------------
======================================================================== 4 passed in 0.03s ========================================================================

根据测试结果可以看出,setup/teardown对当前测试类中的每个测试方法都有效,会在每个方法的前和后执行,setup_class/teardown_class仅对当前测试类有效,在当前测试类的前和后仅执行一次,setup_module/teardown_module对当前py文件的所有测试类都有效,在当前模块前和后仅执行一次。

如果只想对部分测试用例进行初始化,用setup/teardown就无法实现了

fixture装饰器

fixture是pytest用于将测试前后进行准备、清理工作的代码分离出核心测试逻辑代码的一种机制。可以灵活的处理很多场景下的用例测试需求。

  • 定义fixture跟定义普通函数一样,唯一的区别就是在函数上加个装饰器@pytest.fixture(),fixture命名不要用test_开头(或在pytest.ini中自定义的开头关键字),要跟测试用例区分开。
  • fixture装饰器里的scope参数有5个可选值,表示不同的测试用例应用范围:functionclassmodulepackageseesion。没有设置scope的值,默认为function
  • fixture可以有返回值,返回一个元组、列表或字典。如果没有return,会返回None;用例调用fixture的返回值,会直接把fixture的函数名称作为参数传入。
  • 不同的fixture之间可以相互调用

基础语法

pytest使用@pytest.fixture() 装饰器来实现部分用例/全部用例的前后置,完整的语法格式如下:

fixture(scope='function' ,parms=None, autouse=False, ids=None, name=None)

参数说明:

  • scope参数:标记方法的作用域。有5个可选值:function(默认值,函数)、class(类)、module(模块)、package(包)、session(会话)
    • function:每一个函数或方法都会调用
    • class:每一个类调用一次,一个类中可以有多个方法
    • module:每一个.py文件调用一次,该文件内有可以有多个class和function
    • package:是多个文件(整个项目)调用一次,可以跨.py文件调用
    • session:是当前运行的会话调用一次
  • params:一个可选的参数列表,实现参数化
  • autouse:默认False,需要通过调用来激活fixture;如果为True,则所有符合条件的用例自动调用fixture
  • ids:用例标识ID,每个ids对应于params,如果没有ids他们将从params自动生成
  • name:fixture的重命名。若使用了name,在使用时将传入name,原来的函数名将不再生效

fixture是将setup/teardown整合在一个fixture()函数中实现,用yield关键字区隔,yield之前是setupyield之后是teardown

@pytest.fixture(scope='function')
def login():
    print("登录系统")
    yield
    print("退出登录")

前后置方法本质上是一个生成器函数,生产器函数在使用next进行迭代时,执行到yeild会返回数据,暂停执行,等待下一次进行迭代时才会继续执行,pytest就是利用的生成器的机制,通过yeild将测试前后置代码分开执行。

为函数增加夹具

  1. 创建一个包:test_fixture,在包中创建test_demo01.py文件,这里scope定义为function

    import pytest
    
    
    @pytest.fixture(scope='function')
    def login():
        print("登录系统")
        yield
        print("退出登录")
    
    
    class TestCase1:
        def test_01(self):
            print("测试用例1")
    
        def test_02(self):
            print("测试用例2")
    
    
    if __name__ == '__main__':
        pytest.main()

    执行测试:

    $ pytest test_01_demo.py 
    ======================================================================= test session starts =======================================================================
    platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
    cachedir: .pytest_cache
    metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
    rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
    plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
    collected 2 items                                                                                                                                                 
    
    test_01_demo.py::TestCase1::test_01 测试用例1
    PASSED
    test_01_demo.py::TestCase1::test_02 测试用例2
    PASSED
    
    ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
    ======================================================================== 2 passed in 0.03s ========================================================================

    执行结果中,并未显示出夹具的登录和退出。这是因为上面的语法提到,autouse默认是False

  2. 调用激活:在需要调用的函数或类将fixture()装饰器修饰的函数作为参数进行调用,代码如下:

    import pytest
    
    
    @pytest.fixture(scope='function')
    def login():
        print("登录系统")
        yield
        print("退出登录")
    
    
    class TestCase1:
        def test_01(self):
            print("测试用例1")
    
        def test_02(self,login):
            print("测试用例2")
    
    
    if __name__ == '__main__':
        pytest.main()

    执行测试:

    $ pytest test_01_demo.py 
    ======================================================================= test session starts =======================================================================
    platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
    cachedir: .pytest_cache
    metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
    rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
    plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
    collected 2 items                                                                                                                                                 
    
    test_01_demo.py::TestCase1::test_01 测试用例1
    PASSED
    test_01_demo.py::TestCase1::test_02 登录系统
    测试用例2
    PASSED退出登录
    
    
    ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
    ======================================================================== 2 passed in 0.03s ========================================================================

    通过执行结果,可以看到,调用了login的函数增加了前后置的代码执行。这也说明了fixture的灵活性,可以在一批用例中的某些用例设置前后置,而不是像setup/teardown(包括unittest)要么全部是,要么全部否。比如这里的例子,前置是登录,后置是退出登录,用例1可以是非登录状态下的测试,而用例2是登录状态下的测试。

  3. 不通过单个用例激活,设置autouseTrue

    import pytest
    
    
    @pytest.fixture(scope='function', autouse=True)
    def login():
        print("登录系统")
        yield
        print("退出登录")
    
    
    class TestCase1:
        def test_01(self):
            print("测试用例1")
    
        def test_02(self):
            print("测试用例2")
    
    
    if __name__ == '__main__':
        pytest.main()

    执行测试:

    $ pytest test_01_demo.py 
    ======================================================================= test session starts =======================================================================
    platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
    cachedir: .pytest_cache
    metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
    rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
    plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
    collected 2 items                                                                                                                                                 
    
    test_01_demo.py::TestCase1::test_01 登录系统
    测试用例1
    PASSED退出登录
    
    test_01_demo.py::TestCase1::test_02 登录系统
    测试用例2
    PASSED退出登录
    
    
    ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
    ======================================================================== 2 passed in 0.02s ========================================================================

    从测试结果可以看到两个用例都执行了前后置代码。

  4. 装饰器@pytest.fixture()不设置scope的值,默认按scope='function'执行

    import pytest
    
    
    @pytest.fixture(autouse=True)
    def login():
        print("登录系统")
        yield
        print("退出登录")
    
    
    class TestCase4:
        def test_07(self):
            print("测试用例7")
    
        def test_08(self):
            print("测试用例8")
    
    
    class TestCase5:
        def test_09(self):
            print("测试用例9")
    
        def test_10(self):
            print("测试用例10")
    
    
    def test_11():
        print("测试用例11")
    
    
    if __name__ == '__main__':
        pytest.main()

    执行测试

    $ pytest test_04_demo.py 
    ======================================================================= test session starts =======================================================================
    platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
    cachedir: .pytest_cache
    metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
    rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
    plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
    collected 5 items                                                                                                                                                 
    
    test_04_demo.py::TestCase4::test_07 登录系统
    测试用例7
    PASSED退出登录
    
    test_04_demo.py::TestCase4::test_08 登录系统
    测试用例8
    PASSED退出登录
    
    test_04_demo.py::TestCase5::test_09 登录系统
    测试用例9
    PASSED退出登录
    
    test_04_demo.py::TestCase5::test_10 登录系统
    测试用例10
    PASSED退出登录
    
    test_04_demo.py::test_11 登录系统
    测试用例11
    PASSED退出登录
    
    
    ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
    ======================================================================== 5 passed in 0.03s ========================================================================

    每个函数都增加了前后置

为类增加夹具

  1. 创建test_demo02.py文件,这里scope定义为class

    import pytest
    
    
    @pytest.fixture(scope='class', autouse=True)
    def login():
        print("登录系统")
        yield
        print("退出登录")
    
    
    class TestCase2:
        def test_03(self):
            print("测试用例3")
    
        def test_04(self):
            print("测试用例4")
    
    
    class TestCase3:
        def test_05(self):
            print("测试用例5")
    
        def test_06(self):
            print("测试用例6")
    
    
    if __name__ == '__main__':
        pytest.main()

    执行测试:

    $ pytest test_02_demo.py 
    ======================================================================= test session starts =======================================================================
    platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
    cachedir: .pytest_cache
    metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
    rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
    plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
    collected 4 items                                                                                                                                                 
    
    test_02_demo.py::TestCase2::test_03 登录系统
    测试用例3
    PASSED
    test_02_demo.py::TestCase2::test_04 测试用例4
    PASSED退出登录
    
    test_02_demo.py::TestCase3::test_05 登录系统
    测试用例5
    PASSED
    test_02_demo.py::TestCase3::test_06 测试用例6
    PASSED退出登录
    
    
    ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
    ======================================================================== 4 passed in 0.03s ========================================================================

    两个类的执行都加上了前后置。

  2. 若只想为其中一个类添加前后置,需要去掉autouse参数,在需要增加前后置的类用@fixture.mark.usefixtures()装饰器来实现,代码如下:

    import pytest
    
    
    @pytest.fixture(scope='class')
    def login():
        print("登录系统")
        yield
        print("退出登录")
    
    
    class TestCase2:
        def test_03(self):
            print("测试用例3")
    
        def test_04(self):
            print("测试用例4")
    
    
    @pytest.mark.usefixtures("login")
    class TestCase3:
        def test_05(self):
            print("测试用例5")
    
        def test_06(self):
            print("测试用例6")
    
    
    if __name__ == '__main__':
        pytest.main()

    执行测试:

    $ pytest test_02_demo.py 
    ======================================================================= test session starts =======================================================================
    platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
    cachedir: .pytest_cache
    metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
    rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
    plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
    collected 4 items                                                                                                                                                 
    
    test_02_demo.py::TestCase2::test_03 测试用例3
    PASSED
    test_02_demo.py::TestCase2::test_04 测试用例4
    PASSED
    test_02_demo.py::TestCase3::test_05 登录系统
    测试用例5
    PASSED
    test_02_demo.py::TestCase3::test_06 测试用例6
    PASSED退出登录
    
    
    ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
    ======================================================================== 4 passed in 0.03s ========================================================================

    只有被@fixture.mark.usefixtures()装饰器修饰的类增加了前后置

为模块增加夹具

  1. 创建test_demo03.py文件,这里scope定义为module(当前的py文件)

    import pytest
    
    
    @pytest.fixture(scope='module', autouse=True)
    def login():
        print("登录系统")
        yield
        print("退出登录")
    
    
    class TestCase4:
        def test_07(self):
            print("测试用例7")
    
        def test_08(self):
            print("测试用例8")
    
    
    class TestCase5:
        def test_09(self):
            print("测试用例9")
    
        def test_10(self):
            print("测试用例10")
    
    
    def test_11():
        print("测试用例11")
    
    
    if __name__ == '__main__':
        pytest.main()

    执行测试:

    $ pytest test_03_demo.py 
    ======================================================================= test session starts =======================================================================
    platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
    cachedir: .pytest_cache
    metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
    rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
    plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
    collected 5 items                                                                                                                                                 
    
    test_03_demo.py::TestCase4::test_07 登录系统
    测试用例7
    PASSED
    test_03_demo.py::TestCase4::test_08 测试用例8
    PASSED
    test_03_demo.py::TestCase5::test_09 测试用例9
    PASSED
    test_03_demo.py::TestCase5::test_10 测试用例10
    PASSED
    test_03_demo.py::test_11 测试用例11
    PASSED退出登录
    
    
    ------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
    ======================================================================== 5 passed in 0.03s ========================================================================

    文件执行的开始和结尾增加了前后置处理

为会话增加夹具

scope=session的情况基本是跨多个py文件来执行的,需要配合conftest.py来实现,会在下面的内容中进行介绍。

fixture其他参数

params

在test_demo02.py的fixture()中增加参数params,这里模拟的是登录,多个用户切换登录测试。

import pytest


@pytest.fixture(scope='class', params=['张三', '李四', '王五'])
def login(request):   // 参数request是固定写法,必须是request
    print("登录系统")
    yield request.param  // 前置处理完毕后回传request.param的值,这里的yield相当于return
    print("退出登录")


class TestCase2:
    def test_03(self, login):
        print("测试用例3")
        print(login)

    def test_04(self):
        print("测试用例4")


@pytest.mark.usefixtures("login")
class TestCase3:
    def test_05(self):
        print("测试用例5")

    def test_06(self):
        print("测试用例6")


if __name__ == '__main__':
    pytest.main()

上面代码中,类TestCase2中函数test_03增加了对fixturn()的调用,类TestCase3增加了对fixture()的调用。

执行测试:

$ pytest test_02_demo.py 
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 10 items                                                                                                                                                

test_02_demo.py::TestCase2::test_03[\u5f20\u4e09] 登录系统
测试用例3
张三
PASSED
test_02_demo.py::TestCase2::test_03[\u674e\u56db] 退出登录
登录系统
测试用例3
李四
PASSED
test_02_demo.py::TestCase2::test_03[\u738b\u4e94] 退出登录
登录系统
测试用例3
王五
PASSED
test_02_demo.py::TestCase2::test_04 测试用例4
PASSED退出登录

test_02_demo.py::TestCase3::test_05[\u5f20\u4e09] 登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[\u5f20\u4e09] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[\u674e\u56db] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[\u674e\u56db] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[\u738b\u4e94] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[\u738b\u4e94] 测试用例6
PASSED退出登录


------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
======================================================================= 10 passed in 0.04s ========================================================================

调用了fixture()的函数或类都对参数params进行了遍历输出执行

ids

在test_demo02.py的fixture()中增加参数idsids是与parmas一一对应的,不设置ids,pytest会自动设置一个ids,这里将ids设置成为3个用户名的拼音,代码如下:

import pytest


@pytest.fixture(scope='class', params=['张三', '李四', '王五'], ids=['zhangsan', 'lisi', 'wangwu'])
def login(request):
    print("登录系统")
    yield request.param
    print("退出登录")


class TestCase2:
    def test_03(self, login):
        print("测试用例3")
        print(login)

    def test_04(self):
        print("测试用例4")


@pytest.mark.usefixtures("login")
class TestCase3:
    def test_05(self):
        print("测试用例5")

    def test_06(self):
        print("测试用例6")


if __name__ == '__main__':
    pytest.main()

执行测试:

$ pytest test_02_demo.py 
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 10 items                                                                                                                                                

test_02_demo.py::TestCase2::test_03[zhangsan] 登录系统
测试用例3
张三
PASSED
test_02_demo.py::TestCase2::test_03[lisi] 退出登录
登录系统
测试用例3
李四
PASSED
test_02_demo.py::TestCase2::test_03[wangwu] 退出登录
登录系统
测试用例3
王五
PASSED
test_02_demo.py::TestCase2::test_04 测试用例4
PASSED退出登录

test_02_demo.py::TestCase3::test_05[zhangsan] 登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[zhangsan] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[lisi] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[lisi] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[wangwu] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[wangwu] 测试用例6
PASSED退出登录


------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
======================================================================= 10 passed in 0.04s ========================================================================

用例的ids都显示成了用户名的拼音

name

在test_demo02.py的fixture()中增加参数namename是将fixture()装饰的函数改了调用时的名称,设置name后,无法再用原来的名字调用,比如这里的login就是失效了,代码如下:

import pytest


@pytest.fixture(scope='class', params=['张三', '李四', '王五'], ids=['zhangsan', 'lisi', 'wangwu'], name='denglu')
def login(request):
    print("登录系统")
    yield request.param
    print("退出登录")


class TestCase2:
    def test_03(self, login):
        print("测试用例3")
        print(login)

    def test_04(self):
        print("测试用例4")


@pytest.mark.usefixtures("login")
class TestCase3:
    def test_05(self):
        print("测试用例5")

    def test_06(self):
        print("测试用例6")


if __name__ == '__main__':
    pytest.main()

执行测试:

$ pytest test_02_demo.py 
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 4 items                                                                                                                                                 

test_02_demo.py::TestCase2::test_03 ERROR
test_02_demo.py::TestCase2::test_04 测试用例4
PASSED
test_02_demo.py::TestCase3::test_05 ERROR
test_02_demo.py::TestCase3::test_06 ERROR

============================================================================= ERRORS ==============================================================================
_______________________________________________________________ ERROR at setup of TestCase2.test_03 _______________________________________________________________
file /Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py, line 16
      def test_03(self, login):
E       fixture 'login' not found
>       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, denglu, doctest_namespace, extra, include_metadata_in_junit_xml, metadata, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, testrun_uid, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
>       use 'pytest --fixtures [testpath]' for help on them.

/Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py:16
_______________________________________________________________ ERROR at setup of TestCase3.test_05 _______________________________________________________________
file /Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py, line 26
      def test_05(self):
E       fixture 'login' not found
>       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, denglu, doctest_namespace, extra, include_metadata_in_junit_xml, metadata, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, testrun_uid, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
>       use 'pytest --fixtures [testpath]' for help on them.

/Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py:26
_______________________________________________________________ ERROR at setup of TestCase3.test_06 _______________________________________________________________
file /Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py, line 29
      def test_06(self):
E       fixture 'login' not found
>       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, denglu, doctest_namespace, extra, include_metadata_in_junit_xml, metadata, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, testrun_uid, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory, worker_id
>       use 'pytest --fixtures [testpath]' for help on them.

/Users/laobai/workspaces/testdemo01/test_fixture/test_02_demo.py:29
------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
===================================================================== short test summary info =====================================================================
ERROR test_02_demo.py::TestCase2::test_03
ERROR test_02_demo.py::TestCase3::test_05
ERROR test_02_demo.py::TestCase3::test_06
=================================================================== 1 passed, 3 errors in 0.03s ===================================================================

调用到login的地址都报错了。将代码中调用的login改成denglu,代码如下:

import pytest


@pytest.fixture(scope='class', params=['张三', '李四', '王五'], ids=['zhangsan', 'lisi', 'wangwu'], name='denglu')
def login(request):
    print("登录系统")
    yield request.param
    print("退出登录")


class TestCase2:
    def test_03(self, denglu):
        print("测试用例3")
        print(denglu)

    def test_04(self):
        print("测试用例4")


@pytest.mark.usefixtures("denglu")
class TestCase3:
    def test_05(self):
        print("测试用例5")

    def test_06(self):
        print("测试用例6")


if __name__ == '__main__':
    pytest.main()

执行测试:

$ pytest test_02_demo.py 
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 10 items                                                                                                                                                

test_02_demo.py::TestCase2::test_03[zhangsan] 登录系统
测试用例3
张三
PASSED
test_02_demo.py::TestCase2::test_03[lisi] 退出登录
登录系统
测试用例3
李四
PASSED
test_02_demo.py::TestCase2::test_03[wangwu] 退出登录
登录系统
测试用例3
王五
PASSED
test_02_demo.py::TestCase2::test_04 测试用例4
PASSED退出登录

test_02_demo.py::TestCase3::test_05[zhangsan] 登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[zhangsan] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[lisi] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[lisi] 测试用例6
PASSED
test_02_demo.py::TestCase3::test_05[wangwu] 退出登录
登录系统
测试用例5
PASSED
test_02_demo.py::TestCase3::test_06[wangwu] 测试用例6
PASSED退出登录


------------------------------------ generated html file: file:///Users/laobai/workspaces/testdemo01/test_fixture/report.html -------------------------------------
======================================================================= 10 passed in 0.04s ========================================================================

conftest.py结合fixture

  • conftest.py是专门用于存放fixture的配置文件,名字是固定的,不能变;
  • 在conftest.py文件里的所有方法在调用时都不需要导入(会自动查找);
  • conftest.py文件可以有多个,并且多个conftest.py文件里的多个fixture可以被一个用例调用;
  • conftest.py与运行的用例要在同一个package下,并有init.py文件

举例演示:

  • ​ 项目根目录

    • 项目级conftest.py

      ​ 登录/登出 fixture()

    • 资质管理目录

      • 模块级conftest.py
        • 资质管理fixture()
      • 资质管理模块测试用例.py
    • 下单管理目录

      • 模块级conftest.py
        • 下单管理fixture()
      • 下单管理模块测试用例.py

​ 目录结构如下:

  • ​ /
    • test_zizhi
      • __ init __.py
      • conftest.py
      • test_demo01.py
    • test_xiadan
      • __ init __.py
      • conftest.py
      • test_demo02.py
    • conftest.py

根据上面的目录结构,创建包、文件

根目录conftest.py代码如下:

import pytest


@pytest.fixture(scope='class', autouse=True)
def login():
    print("登录系统")
    yield
    print("退出登录")

test_zizhi/conftest.py代码如下:

import pytest


@pytest.fixture(scope='class', autouse=True)
def zizhi():
    print("进入资质管理")
    yield
    print("退出资质管理")

test_zizhi/test_demo01.py代码如下:

class TestCase1:
    def test_01(self):
        print("资质管理用例01")

    def test_02(self):
        print("资质管理用例02")

test_xiadan/conftest.py代码如下:

import pytest


@pytest.fixture(scope='class', autouse=True)
def xiada():
    print("开始下单")
    yield
    print("完成下单")

test_xiadan/test_demo02.py代码如下:

class TestCase2:
    def test_11(self):
        print("下单用例11")

    def test_12(self):
        print("下单用例12")

在命令中,输入pytest执行用例,结果如下:

$ pytest
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini, testpaths: test_zizhi, test_xiadan
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 4 items                                                                                                                                                 

test_zizhi/test_demo01.py::TestCase1::test_01 登录系统
进入资质管理
资质管理用例01
PASSED
test_zizhi/test_demo01.py::TestCase1::test_02 资质管理用例02
PASSED退出资质管理
退出登录

test_xiadan/test_demo02.py::TestCase2::test_11 登录系统
开始下单
下单用例11
PASSED
test_xiadan/test_demo02.py::TestCase2::test_12 下单用例12
PASSED完成下单
退出登录


------------------------------------------- generated html file: file:///Users/laobai/workspaces/testdemo01/report.html -------------------------------------------
======================================================================== 4 passed in 0.03s ========================================================================

可以看到测试结果中,资质管理的用例和下单的用例没有连续执行,而是退出登录,再次登录后才执行的。

这里将根目录下的conftest.pyfixture中的scope的值由class改为sessionpackage

import pytest


@pytest.fixture(scope='session', autouse=True)
def login():
    print("登录系统")
    yield
    print("退出登录")

再次执行:

$ pytest
======================================================================= test session starts =======================================================================
platform darwin -- Python 3.9.12, pytest-7.2.0, pluggy-1.0.0 -- /Users/laobai/workspaces/testdemo01/venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.9.12', 'Platform': 'macOS-10.14.6-x86_64-i386-64bit', 'Packages': {'pytest': '7.2.0', 'pluggy': '1.0.0'}, 'Plugins': {'rerunfailures': '10.3', 'xdist': '3.1.0', 'html': '3.2.0', 'ordering': '0.6', 'metadata': '2.0.4'}, 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home'}
rootdir: /Users/laobai/workspaces/testdemo01, configfile: pytest.ini, testpaths: test_zizhi, test_xiadan
plugins: rerunfailures-10.3, xdist-3.1.0, html-3.2.0, ordering-0.6, metadata-2.0.4
collected 4 items                                                                                                                                                 

test_zizhi/test_demo01.py::TestCase1::test_01 登录系统
进入资质管理
资质管理用例01
PASSED
test_zizhi/test_demo01.py::TestCase1::test_02 资质管理用例02
PASSED退出资质管理

test_xiadan/test_demo02.py::TestCase2::test_11 开始下单
下单用例11
PASSED
test_xiadan/test_demo02.py::TestCase2::test_12 下单用例12
PASSED完成下单
退出登录


------------------------------------------- generated html file: file:///Users/laobai/workspaces/testdemo01/report.html -------------------------------------------
======================================================================== 4 passed in 0.03s ========================================================================

以上所有用例只需登录一次即可全部测试完成。在实际工作中可以更加不同需要灵活设置用例的前后置。

总结

pytest执行过程

  1. 查询项目根目录下的conftest.py文件;
  2. 查询项目根目录下的pytest.ini文件;
  3. 查询用例目录下的conftest.py文件;
  4. 查询测试用例py文件中是否有setup_class、teardown_class、setup、teardown;
  5. 再根据pytest.ini文件的测试用例规则去查找用例并执行。

文章作者: 老百
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 老百 !
 上一篇
Python单元测试框架——pytest(三) Python单元测试框架——pytest(三)
在自动化的测试中,经常需要把测试的数据分离出来,这样也是基于数据驱动的设计模式来进行思考。本节将会介绍4种文件格式如何分离、存储、读取测试数据。
2022-12-12
下一篇 
Python单元测试框架——pytest(一) Python单元测试框架——pytest(一)
pytest是一个功能齐全的Python单元测试框架,可以帮助编写更好的程序,不仅可以编写小测试,还可以扩展到复杂的功能测试。
2022-12-03
  目录