• Python自动化之pytest框架使用详解


    pytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:

    • 简单灵活,容易上手
    • 支持参数化
    • 能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/appnium等自动化测试、接口自动化测试(pytest+requests)
    • pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如pytest-selenium(集成selenium)、pytest-html(完美html测试报告生成)、pytest-rerunfailures(失败case重复执行)、pytest-xdist(多CPU分发)等
    • 测试用例的skip和xfail处理
    • 可以很好的和jenkins集成
    • report框架----allure 也支持了pytest
    1. 安装
    2.  pip install -U pytest

       2.查看版本

        pytest --version

      3.用例编写规范

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

    运行参数

    • 无参数
      • 读取路径下符合条件的所有类、函数、方法全部执行
    •  -v 
      • 打印详细运行日志
    • -s
      • 打印print输出
    • -k
      • 跳过运行某个或某些用例
      • pytest -k '类名'
      • pytest -k '方法名
      • pytest -k '类名 and not 方法名'   #运行类里所有方法,不包含某个方法
    • -x
      • 遇到用例失败立即停止运行
    • --maxfail
      • 用例失败数达到某个设定的值停止运行
      • pytest --maxfail=[num]
    • -m
      • 运行所有@pytest.mark.[标记名] 标记的用例

     框架结构

    与unittest类似,执行前后会执行setup,teardown来增加用例的前置和后置条件。pytest框架使用setup,teardown更为灵活,按照用例运行级别可以分为以下几类

    • setup_module/teardown_module   模块级别,在模块始末调用
    • setup_function/teardown_function     函数级别,在函数始末调用(在类外部)
    • setup_class/teardown_class              类级别,每个类里面执行前后分别执行
    • setup_method/teardown_method       方法级别,在方法始末调用(在类中)
    • setup/teardown                               方法级别,在方法始末调用(在类中)

    调用顺序:setup_module > setup_class >setup_method > setup > teardown > teardown_method > teardown_class > teardown_module

    for example:

    #!/usr/bin/env python 
    # encoding: utf-8 
    '''
    @Auther:chenshifeng
    @version: v1.0
    @file: test_calc.py
    @time: 2020/9/14 9:39 PM
    '''
    # 测试文件
    import sys, os
    
    import pytest
    
    sys.path.append(os.pardir)
    
    from pythoncode.calc import Calculator
    
    # 模块级别,在模块始末调用
    def setup_module():
        print('模块级别setup')
    
    
    def teardown_module():
        print('模块级别teardown')
    
    # 函数级别,在函数始末调用(在类外部)
    def setup_function():
        print('函数级别setup')
    
    
    def teardown_function():
        print('函数级别teardown')
    
    
    def test_case1():
        print('testcase1')
    
    
    class TestCalc:
        # setup_class,teardown_class 类级别每个类里面执行前后分别执行
        def setup_class(self):
            self.cal = Calculator()
            print('类级别setup')
    
        def teardown_class(self):
            print('类级别teardown')
    
        # 方法级别,每个方法里面的测试用例前后分别执行setup、teardown
        def setup(self):
            # self.cal = Calculator()
            print('setup')
    
        def teardown(self):
            print('teardown')
    
        # 方法级别,每个方法里面的测试用例前后分别执行setup、teardown
        def setup_method(self):
            # self.cal = Calculator()
            print('方法级别setup')
    
        def teardown_method(self):
            print('方法级别teardown')
    
    
        @pytest.mark.add
        def test_add1(self):
            # cal = Calculator()
            assert 3 == self.cal.add(1, 2)
    
        @pytest.mark.div
        def test_div(self):
            # cal = Calculator()
            assert 1 == self.cal.div(1, 1)

    运行结果如下

    Testing started at 11:05 下午 ...
    /usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_calc.py
    Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_calc.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode
    
    ============================= test session starts ==============================
    platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
    cachedir: .pytest_cache
    rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
    collecting ... collected 3 items
    
    test_calc.py::test_case1 模块级别setup
    函数级别setup
    PASSED                                          [ 33%]testcase1
    函数级别teardown
    
    test_calc.py::TestCalc::test_add1 类级别setup
    方法级别setup
    setup
    PASSED                                 [ 66%]teardown
    方法级别teardown
    
    test_calc.py::TestCalc::test_div 方法级别setup
    setup
    PASSED                                  [100%]teardown
    方法级别teardown
    类级别teardown
    模块级别teardown
    
    
    ============================== 3 passed in 0.02s ===============================
    
    Process finished with exit code 0

    pytest参数化

    Pytest是使用@pytest.mark.parametrize装饰器来实现数据驱动测试的

    for example:

    import pytest
    
    @pytest.mark.parametrize('a,b,result', [
        (1, 1, 2),
        (2, 3, 5),
        (100, 200, 300)
    ])
    def test_add(a, b, result):
        cal = Calculator()
        assert cal.add(a, b) == result

    结果:

    Testing started at 11:22 ...
    "D:Program FilesPythonpython.exe" "D:Program FilesJetBrainsPyCharm Community Edition 2020.2.1pluginspython-cehelperspycharm\_jb_pytest_runner.py" --target test_calc.py::test_add
    Launching pytest with arguments test_calc.py::test_add in D:chenshifengmycodePython	est_pytest	esting
    
    ============================= test session starts =============================
    platform win32 -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- D:Program FilesPythonpython.exe
    cachedir: .pytest_cache
    rootdir: D:chenshifengmycodePython, configfile: pytest.ini
    collecting ... collected 3 items
    
    test_calc.py::test_add[1-1-2] PASSED                                     [ 33%]
    test_calc.py::test_add[2-3-5] PASSED                                     [ 66%]
    test_calc.py::test_add[100-200-300] PASSED                               [100%]
    
    ============================== 3 passed in 0.03s ==============================
    
    Process finished with exit code 0

    修改结果显示名称

    通过上面的运行结果,我们可以看到,为了区分参数化的运行结果,在结果中都会显示数据组合而成的名称。

    数据短小还好说,如果数据比较长而复杂的话,那么就会很难看。

    @pytest.mark.parametrize() 还提供了第三个 ids 参数来自定义显示结果。

    import pytest
    
    # 参数化
    @pytest.mark.parametrize('a,b,result', [
        (1, 1, 2),
        (2, 3, 5),
        (100, 200, 300)
    ], ids=['int0', 'int1', 'int2'])  # 修改结果显示名称
    def test_add(a, b, result):
        cal = Calculator()
        assert cal.add(a, b) == result

    结果:

    ============================= test session starts =============================
    platform win32 -- Python 3.7.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- D:Program FilesPythonpython.exe
    cachedir: .pytest_cache
    rootdir: D:chenshifengmycodePython, configfile: pytest.ini
    collecting ... collected 3 items
    
    test_calc.py::test_add[int0] PASSED                                      [ 33%]
    test_calc.py::test_add[int1] PASSED                                      [ 66%]
    test_calc.py::test_add[int2] PASSED                                      [100%]
    
    ============================== 3 passed in 0.03s ==============================
    
    Process finished with exit code 0

    pytest fixtures

    fixture用途

    pytest fixture 与setup,teardown功能一样,但比之更加灵活,完全可以代替setup,teardown

    1.做测试前后的初始化设置,如测试数据准备,链接数据库,打开浏览器等这些操作都可以使用fixture来实现

    2.测试用例的前置条件可以使用fixture实现

    3.支持经典的xunit fixture ,像unittest使用的setup和teardown

    4.fixture可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题

    fixture定义

    fixture通过@pytest.fixture()装饰器装饰一个函数,那么这个函数就是一个fixture

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    """
    @author:chenshifeng
    @file:test_login.py
    @time:2020/09/15
    """
    import pytest
    
    
    @pytest.fixture()
    def login():
        print('登陆方法')
        yield   #激活fixture teardown方法
        print('teardown')
    
    # 测试用例之前,先执行login方法
    def test_case1(login):
        print('case1')
    
    def test_case2():
        print('case2')
    
    def test_case3():
        print('case3')

    运行结果如下:

    test_login.py::test_case2 
    test_login.py::test_case3 
    
    ============================== 3 passed in 0.02s ===============================
    
    Process finished with exit code 0
    登陆方法
    PASSED                                         [ 33%]case1
    teardown
    PASSED                                         [ 66%]case2
    PASSED                                         [100%]case3

     

    fixture作用范围(scope)

    fixture里面有个scope参数可以控制fixture的作用范围:session > module > class > function
    - function 每一个函数或方法都会调用,默认为function 
    - class  每一个类调用一次,一个类可以有多个方法
    - module,每一个.py文件调用一次,该文件内又有多个function和class
    - session 是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module
    • cope='function'
    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    """
    @author:chenshifeng
    @file:test_login.py
    @time:2020/09/15
    """
    import pytest
    
    
    @pytest.fixture()   #默认为scope='function'
    def login():
        print('登陆方法')
        yield ['username','passwd']  #激活fixture teardown方法
        print('teardown')
    
    # 测试用例之前,先执行login方法
    def test_case1(login):
        print(f'case1 login={login}')
    
    def test_case2(login):
        print('case2')
    
    def test_case3(login):
        print('case3')

    运行结果如下:

    Testing started at 12:02 上午 ...
    /usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py
    Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode
    
    ============================= test session starts ==============================
    platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
    cachedir: .pytest_cache
    rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
    collecting ... collected 3 items
    
    test_login.py::test_case1 
    test_login.py::test_case2 
    test_login.py::test_case3 
    
    ============================== 3 passed in 0.02s ===============================
    
    Process finished with exit code 0
    登陆方法
    PASSED                                         [ 33%]case1 login=['username', 'passwd']
    teardown
    登陆方法
    PASSED                                         [ 66%]case2
    teardown
    登陆方法
    PASSED                                         [100%]case3
    teardown
    • scope='module'
    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    """
    @author:chenshifeng
    @file:test_login.py
    @time:2020/09/15
    """
    import pytest
    
    
    @pytest.fixture(scope='module')   #默认为scope='function'
    def login():
        print('登陆方法')
        yield ['username','passwd']  #激活fixture teardown方法
        print('teardown')
    
    # 测试用例之前,先执行login方法
    def test_case1(login):
        print(f'case1 login={login}')
    
    def test_case2(login):
        print('case2')
    
    def test_case3(login):
        print('case3')
    结果:
    Testing started at 12:08 上午 ...
    /usr/local/bin/python3.6 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --path /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py
    Launching pytest with arguments /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode/test_login.py in /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest/testcode
    
    ============================= test session starts ==============================
    platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
    cachedir: .pytest_cache
    rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
    collecting ... collected 3 items
    
    test_login.py::test_case1 
    test_login.py::test_case2 
    test_login.py::test_case3 
    
    ============================== 3 passed in 0.02s ===============================
    
    Process finished with exit code 0
    登陆方法
    PASSED                                         [ 33%]case1 login=['username', 'passwd']
    PASSED                                         [ 66%]case2
    PASSED                                         [100%]case3
    teardown 

    fixture自动调用(autouse=True)

    autouse设置为True,自动调用fixture功能,无需额外继承

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    """
    @author:chenshifeng
    @file:test_search.py
    @time:2020/09/16
    """
    
    import pytest
    
    
    @pytest.fixture(autouse=True)  # 默认为scope='function'
    def login():
        print('登陆方法')
        yield ['username', 'passwd']  # 激活fixture teardown方法
        print('teardown')
        
    def test_search1():  #无需继承login
        print('搜索用例1')
    
    def test_search2():
        print('搜索用例2')

    结果:

    ============================= test session starts ==============================
    platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
    cachedir: .pytest_cache
    rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
    collecting ... collected 2 items
    
    test_search.py::test_search1 登陆方法
    PASSED                                      [ 50%]搜索用例1
    teardown
    
    test_search.py::test_search2 登陆方法
    PASSED                                      [100%]搜索用例2
    teardown
    
    
    ============================== 2 passed in 0.01s ===============================
    
    Process finished with exit code 0

    fixture参数化(params)

    @pytest.fixture有一个params参数,接受一个列表,列表中每个数据都可以作为用例的输入。也就说有多少数据,就会形成多少用例。可以通过'''request.param'''来获取该次调用的参数

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    """
    @author:chenshifeng
    @file:test_login.py
    @time:2020/09/15
    """
    import pytest
    
    
    @pytest.fixture(params=['user1', 'user2', 'user3'])
    def login(request):
        print('登陆方法')
        print('传入的参数为:'+request.param)  # 获取params参数
        yield ['username', 'passwd']  # 激活fixture teardown方法
        print('teardown')
    
    # 测试用例之前,先执行login方法
    def test_case1(login):
        print(f'case1 login={login}')
    
    def test_case2(login):
        print('case2')
    
    def test_case3(login):
        print('case3')

    结果:

    ============================= test session starts ==============================
    platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
    cachedir: .pytest_cache
    rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
    collecting ... collected 9 items
    
    test_login.py::test_case1[user1] 
    test_login.py::test_case1[user2] 
    test_login.py::test_case1[user3] 
    test_login.py::test_case2[user1] 
    test_login.py::test_case2[user2] 
    test_login.py::test_case2[user3] 
    test_login.py::test_case3[user1] 登陆方法
    传入的参数为:user1
    PASSED                                  [ 11%]case1 login=['username', 'passwd']
    teardown
    登陆方法
    传入的参数为:user2
    PASSED                                  [ 22%]case1 login=['username', 'passwd']
    teardown
    登陆方法
    传入的参数为:user3
    PASSED                                  [ 33%]case1 login=['username', 'passwd']
    teardown
    登陆方法
    传入的参数为:user1
    PASSED                                  [ 44%]case2
    teardown
    登陆方法
    传入的参数为:user2
    PASSED                                  [ 55%]case2
    teardown
    登陆方法
    传入的参数为:user3
    PASSED                                  [ 66%]case2
    teardown
    登陆方法
    传入的参数为:user1
    PASSED                                  [ 77%]case3
    teardown
    
    test_login.py::test_case3[user2] 登陆方法
    传入的参数为:user2
    PASSED                                  [ 88%]case3
    teardown
    
    test_login.py::test_case3[user3] 登陆方法
    传入的参数为:user3
    PASSED                                  [100%]case3
    teardown
    
    
    ============================== 9 passed in 0.06s ===============================
    
    Process finished with exit code 0

    参数化与fixture结合(indirect=True)

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    """
    @author:chenshifeng
    @file:test_cart.py
    @time:2020/09/16
    """
    import pytest
    
    
    @pytest.fixture(params=['user1', 'user2', 'user3'])
    def login(request):
        print('登陆方法')
        print('传入的参数为:' + str(request.param))  # 获取params参数
        yield ['username', 'passwd']  # 激活fixture teardown方法
        print('teardown')
    
    
    # 参数化结合fixture使用
    # 情况一:传入值和数据
    # 情况二:传入一个fixture方法,将数据传入到fixture方法中,fixture使用request参数来接受这组数据,在方法体中使用request.param来接受这个数据
    @pytest.mark.parametrize('login', [
        ('username1', 'passwd1'),
        ('username2', 'passwd2')
    ], indirect=True)
    def test_cart3(login):
        print('购物车用例3')

    结果:

    ============================= test session starts ==============================
    platform darwin -- Python 3.6.4, pytest-6.0.2, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.6
    cachedir: .pytest_cache
    rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
    collecting ... collected 2 items
    
    test_cart.py::test_cart3[login0] 
    test_cart.py::test_cart3[login1] 
    
    ============================== 2 passed in 0.02s ===============================
    
    Process finished with exit code 0
    登陆方法
    传入的参数为:('username1', 'passwd1')
    PASSED                                  [ 50%]购物车用例3
    teardown
    登陆方法
    传入的参数为:('username2', 'passwd2')
    PASSED                                  [100%]购物车用例3
    teardown

    conftest.py

    1.conftest.py文件名字是固定的,不可以做任何修改

    2.文件和用例文件在同一个目录下,那么conftest.py作用于整个目录

    3.conftest.py文件不能被其他文件导入

    4.所有同目录测试文件运行前都会执行conftest.py文件

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    """
    @author:chenshifeng
    @file:conftest.py
    @time:2020/09/15
    """
    
    import pytest
    
    
    @pytest.fixture(params=['user1', 'user2', 'user3'])
    def login(request):
        print('登陆方法')
        print('传入的参数为:'+str(request.param))  # 获取params参数
        yield ['username', 'passwd']  # 激活fixture teardown方法
        print('teardown'
    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    """
    @author:chenshifeng
    @file:test_login.py
    @time:2020/09/15
    """
    
    
    # 测试用例之前,先执行login方法
    import pytest
    
    
    def test_case1(login):
        print(f'case1 login={login}')
    
    @pytest.mark.usefixtures('login')
    def test_case2():
        print('case2')
        # print(f'case1 login={login}')  #该方法无法获取返回值
    
    
    def test_case3(login):
        print('case3')

    运行test_login.py文件,结果如下

    ============================= test session starts ==============================
    platform darwin -- Python 3.9.0, pytest-6.1.1, py-1.9.0, pluggy-0.13.1 -- /usr/local/bin/python3.9
    cachedir: .pytest_cache
    rootdir: /Users/chenshifeng/MyCode/PythonCode/SFDSZL/test_pytest, configfile: pytest.ini
    plugins: allure-pytest-2.8.18
    collecting ... collected 9 items
    
    test_login.py::test_case1[user1] 
    test_login.py::test_case1[user2] 
    test_login.py::test_case1[user3] 
    test_login.py::test_case2[user1] 
    test_login.py::test_case2[user2] 
    test_login.py::test_case2[user3] 
    test_login.py::test_case3[user1] 登陆方法
    传入的参数为:user1
    PASSED                                  [ 11%]case1 login=['username', 'passwd']
    teardown
    登陆方法
    传入的参数为:user2
    PASSED                                  [ 22%]case1 login=['username', 'passwd']
    teardown
    登陆方法
    传入的参数为:user3
    PASSED                                  [ 33%]case1 login=['username', 'passwd']
    teardown
    登陆方法
    传入的参数为:user1
    PASSED                                  [ 44%]case2
    teardown
    登陆方法
    传入的参数为:user2
    PASSED                                  [ 55%]case2
    teardown
    登陆方法
    传入的参数为:user3
    PASSED                                  [ 66%]case2
    teardown
    登陆方法
    传入的参数为:user1
    PASSED                                  [ 77%]case3
    teardown
    
    test_login.py::test_case3[user2] 登陆方法
    传入的参数为:user2
    PASSED                                  [ 88%]case3
    teardown
    
    test_login.py::test_case3[user3] 登陆方法
    传入的参数为:user3
    PASSED                                  [100%]case3
    teardown
    
    
    ============================== 9 passed in 0.04s ===============================
    
    Process finished with exit code 0

     end

  • 相关阅读:
    让我自闭了两个星期的题 Hello xtCpc
    kmp 回忆训练2 poj3461
    kmp 字符串匹配
    线段树之动态开点  HDU 6183 Color it.
    两个思维
    codeforces 300E Empire Strikes Back
    codeforces1392 E Omkar and Duck
    codeforces1169D
    HDU4335 欧拉函数及降幂
    HDU2588GCD(欧拉函数)
  • 原文地址:https://www.cnblogs.com/feng0815/p/13676226.html
Copyright © 2020-2023  润新知