zoukankan      html  css  js  c++  java
  • pytest测试框架 -- assert断言和fixture固件

    一、断言

    (1)使用assert语句进行断言

    # test_run.py
       
    @pytest.mark.assert
    def test_assert(self):
          r = requests.get("https://www.baidu.com")
          assert r.status_code == 100
    # pytest常用的python断言:    
    1)assert xx:判断xx为真
    2)assert not xx:判断xx不为真
    3)assert a in b:判断b包含a
    4)assert a not in b: 判断b不包含a
    5)assert a == b:判断a等于b
    6)assert a !=b:判断a不等于b

    (2)使用pytest.raises触发期望异常的断言

    
    
    # test_run.py
    
    @pytest.mark.arrser
    def test_assert(self):
          r = requests.get("https://www.baidu.com")
          with pytest.raises(AssertionError) as excinfo:
              assert r.status_code == 201     # 此处可能存在异常,所以主动raises抛出
              print("This will be not execute")   # 此处未被执行
          assert excinfo.type == AssertionError   # 此处被执行
          assert "200" in str(excinfo.value)      # 此处被执行
          print("This will be execute")           # 此处被执行

    excinfo 是一个异常信息实例,它是围绕实际引发的异常的包装器。主要属性是.type、 .value 和 .traceback

    注意:断言type的时候,异常类型是不需要加引号的,断言value值的时候需转str。

    二、fixture的使用

    (1)fixture的作用

    • 完成setup和teardown操作,处理数据库、文件等资源的打开和关闭
    • 完成大部分测试用例需要完成的通用操作,例如login、设置config参数、环境变量等
    • 准备测试数据,将数据提前写入到数据库,或者通过params返回给test用例,等
    (2)fixture的优势
    • 命名方式灵活,不局限于setup和teardown这几个命名
    • conftest.py 配置里可以实现数据共享,不需要import就能自动找到一些配置
    • scope="function",若多个用例都调用了fixture函数,则此fixture在每个用例开始前都执行一次
    • scope="class",如果一个class的多个用例都调用了次fixture,则此fixture仅在第一次调用开始前执行一次,后续调用不执行
    • scope="module" 在当前.py脚本里面所有用例开始前只执行一次。
    • scope="session" 以实现多个.py跨文件使用一个session来完成多个用例
    (3)fixture源码详解

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

    scope:有四个级别参数"function"(默认),"class","module","session"

    params:一个可选的参数列表,它将导致多个参数调用fixture功能和所有测试使用它。

    autouse:如果True,则为所有测试激活fixture func可以看到它。如果为False则显示需要参考来激活fixture

    ids:每个字符串id的列表,每个字符串对应于params这样他们就是测试ID的一部分。如果没有提供ID它们将从params自动生成

    name:fixture的名称。这默认为装饰函数的名称。如果fixture在定义它的统一模块中使用,夹具的功能名称将被请求夹具的功能arg遮蔽,解决这个问题的一种方法时将装饰函数命令"fixture_<fixturename>"然后使用"@pytest.fixture(name='<fixturename>')"。

    (4)源码演示
    1、若scope="function"
    @pytest.fixture()
    def login():
        print("登录操作!")
    
    def test_01(login):
        print("执行用例01,需先登录")
    
    def test_02():
        print("执行用例02,不需登录")
    
    def test_03(login):
        print("执行用例03,需先登录"

    输出结果:

    2、若scope="class"

    @pytest.fixture(scope='class')
    def login():
    print("登录操作!")

    class TestFixture1():
    def test_01(self, login):
    print("执行用例01,需先登录")
    def test_02(self):
    print("执行用例02,不需登录")
    def test_03(self, login):
    print("执行用例03,需先登录")

    class TestFixture2():
    def test_01(self, login):
    print("执行用例01,需先登录")
    def test_02(self):
    print("执行用例02,不需登录")
    def test_03(self, login):
    print("执行用例03,需先登录")

    输出结果:

     

    3、若scope="module"
    @pytest.fixture(scope='module')
    def login():
        print("登录操作!")
    
    class TestFixture1():
        def test_01(self, login):
            print("执行用例01,需先登录")
        def test_02(self):
            print("执行用例02,不需登录")
        def test_03(self, login):
            print("执行用例03,需先登录")
    
    class TestFixture2():
        def test_01(self, login):
            print("执行用例01,需先登录")
        def test_02(self):
            print("执行用例02,不需登录")
        def test_03(self, login):
            print("执行用例03,需先登录")

    输出结果:

    4、若scope="session"

    fixture为session级别是可以跨.py模块调用的,也就是当我们有多个.py文件的用例的时候,如果多个用例只需调用一次fixture,那就可以设置为scope="session",并且写到conftest.py文件里。

    conftest.py文件名称时固定的,pytest会自动识别该文件。放到项目的根目录下就可以全局调用了,如果放到某个package下,那就在该package内有效。

    一个工程下可以建多个conftest.py的文件,一般在工程根目录下设置的conftest文件起到全局作用。在不同子目录下也可以放conftest.py的文件,作用范围只能在该层级以及以下目录生效。

    # conftest.py
    import pytest
    
    @pytest.fixture(scope='session')
    def test1():
        print("登录操作")
    # test_fixture1.py
    import pytest
    def test_01(test1):
        print("用例01,需要先登录")
    
    def test_02(test1):
        print("用例02,需要先登录")
    
    if __name__ == "__main__":
        pytest.main(['-s', 'test_fixture1.py'])
        pytest.main(['-s', 'test_fixture2.py'])
    #  test_fixture2.py
    import pytest
    class TestCase():
        def test_03(self, test1):
            print("用例03,需先登录")
    
        def test_04(self, test1):
            print("用例04,需先登录")
    
    if __name__ == "__main__":
        pytest.main(['-s', 'test_fixture2.py'])

    (1)若pycharm下运行test_fixture1.py,则输出结果如下:

     (2)若在命令行下同时运行test_fixture1.py 和test_fixture2.py两个文件,则结果输出如下:

    三、使用装饰器@pytest.mark.usefixtures()修饰需要运行的用例

    如果一个方法或者一个class用例想要同时调用多个fixture,可以使用@pytest.mark.usefixture()进行叠加。注意叠加顺序,先执行的放底层,后执行的放上层。

    import pytest

    @pytest.fixture()
    def test1():
    print("执行function1")

    @pytest.fixture()
    def test2():
    print("执行function2")

    @pytest.mark.usefixtures('test1')
    def test_01():
    print("---用例a执行---")

    @pytest.mark.usefixtures('test1') # 后执行
    @pytest.mark.usefixtures('test2') # 先执行
    class TestCase():
    def test_02(self):
    print("---用例b执行---")

    def test_03(self):
    print("---用例c执行---")

    输出结果:
    test_fixture2.py 执行function1
    ---用例a执行---
    .执行function2
    执行function1
    ---用例b执行---
    .执行function2
    执行function1
    ---用例c执行---

    usefixtures与传fixture区别

    (1)如果fixture有返回值,那么usefixture就无法获取到返回值,这个是装饰器usefixture与用例直接传fixture参数的区别。

    (2)当fixture需要用到return出来的参数时,只能将参数名称直接当参数传入,不需要用到return出来的参数时,两种方式都可以。

    四、使用pytestmark或autouse使得每个函数都调用该固件

    (1)如果你想要模块中的每个函数都调用该固件,你也可以使用pytestmark标记(注意:pytestmark变量名不可更改) 

    import pytest
    
    @pytest.fixture()
    def test2():
        print("执行function2")
    
    pytestmark = pytest.mark.usefixtures('test2')
    
    def test_01():
        print("---用例a执行---")
    
    class TestCase():
        def test_02(self):
            print("---用例b执行---")
    
        def test_03(self):
            print("---用例c执行---")

    输出结果:
    test_fixture2.py 执行function2
    ---用例a执行---
    .执行function2
    ---用例b执行---
    .执行function2
    ---用例c执行---

    (2)使用@pytest.fixture()中的参数autouse(自动使用),将其设为true(默认为false),这样每个函数都会自动调用该前置函数了:

    import pytest
    
    @pytest.fixture(autouse='true')
    def test1():
        print("执行function1")
    
    def test_01():
        print("---用例a执行---")
    
    class TestCase():
        def test_02(self):
            print("---用例b执行---")
    
        def test_03(self):
            print("---用例c执行---")

    输出结果:
    test_fixture2.py 执行function1
    ---用例a执行---
    .执行function1
    ---用例b执行---
    .执行function1
    ---用例c执行---

    五、params参数化

    params参数可以实现前置函数的参数化,调用前置函数的测试用例可以根据前置函数不同的参数执行多次。

    在fixture标签中的params参数传入了列表类型的参数列表;我们又在前置函数中传入了request参数;最后在测试函数中我们通过request.param来表示参数(参数列表中的每个值)

    import pytest
    
    @pytest.fixture(params=['参数1','参数2'])
    def test1(request):
        print("执行前置函数, %s" % request.param)
    
    def test_01(test1):           # 注意,此时test1未用引号 
        print("---用例a执行---")
    
    class TestCase():
        def test_02(self):
            print("---用例b执行---")
    
        def test_03(self):
            print("---用例c执行---")    

    输出结果:
    test_fixture2.py 执行前置函数, 参数1
    ---用例a执行---
    .执行前置函数, 参数2
    ---用例a执行---
    .---用例b执行---
    .---用例c执行--


    或者:
    import pytest
    import requests

    url_datas = ["http://www.baidu.com", "http://www.weibo.com"]

    @pytest.fixture(params=url_datas)
    def get_url(request):
    return request.param


    def test_get_status_code(get_url):
    r = requests.get(get_url)
    print(r.status_code)

    if __name__ == "__main__":
    pytest.main(['-s', 'test_fixture1.py'])

    输出结果:
    test_fixture1.py 200
    .200
    .

     使用yield实现前置和后置函数(如setUp和tearDown)。

    yield相当于 return 返回一个值,并且记住这个返回的位置,下次迭代时,代码从yield的下一条语句开始执行。

    import pytest
    
    def connect():
        print("连接数据库")
    
    def disconnect():
        print("断开数据库")
    
    @pytest.fixture(scope="class")
    def databases():
        connect()       # 前置函数
        yield
        disconnect()    # 后置函数
    
    def test_01(databases):
        print("执行用例a")
    
    class TestCase():
    
        def test_02(self, databases):
            print("执行用例b")
    
        def test_03(self, databases):
            print("执行用例c")
    
    if __name__ == "__main__":
        pytest.main(['-s', 'test_fixture1.py'])

    运行结果:
    连接数据库
    执行用例a
    .断开数据库
    连接数据库
    执行用例b
    .执行用例c
    .断开数据库

    @pytest.mark.parametrize参数化:参数为列表数据

    # get_excel.py
    # 读取excel表的数据
    from openpyxl import load_workbook
    class ExcelData(): def __init__(self, file, sheet_name): '''初始化Excel对象''' self.file = file self.wb = load_workbook(self.file) self.sh = self.wb[sheet_name] def get_row_value(self, row): '''获取Excel中某一行数据''' max_col = self.sh.max_column row_value = [] for col in range(1, max_col+1): value = self.sh.cell(row, col).value row_value.append(value) return row_value def get_all_row_value(self): '''获取Excel中所有行的数据''' max_row = self.sh.max_row row_value = [] for row in range(2, max_row+1): value = self.get_row_value(row) row_value.append(value) return row_value
    # test_student.py
    
    from bases.get_excel import ExcelData
    import pytest
    import requests
    import json
    
    # 登录接口的数据
    file_path = r'E:ApiAuto	estdatadatas1.xlsx'
    login_excel = ExcelData(file_path, 'login')
    login_data = login_excel.get_all_row_value()
    
    class TestStudent():
        '''学生模块api'''
    
        def assertHandler(self, excel, response, expect_result, row_index, response_index, conclusion_index):
            '''将实际结果写入Excel文件中'''
            excel.write_value(row_index, response_index, response)
            response = json.loads(response)
            try:
                '''断言,比较实际结果与预期结果'''
                assert response['error_code'] == expect_result
            except Exception as e:
                '''将测试结论写入Excel文件中'''
                login_excel.write_value(row_index, conclusion_index, "fail")
                print("测试失败!")
                raise e
            else:
                login_excel.write_value(row_index, conclusion_index, "pass")
                print("测试通过")
        
        @pytest.mark.parametrize('student', login_data)
        def test_login(self, student):
            '''登录接口'''
            is_execute = student[2]
            url = student[4]
            method = student[5]
            username = student[6]
            passwd = student[7]
            response_index = 10
            expect_result = student[10]
            conclusion_index = 12
            row_index = student[12] + 1
            if is_execute == "yes":
                body = {
                    'username': username,
                    'passwd': passwd,
                }
                print("用例ID:", student[0])
                print("模块:", student[1])
                print("用例标题:", student[3])
                response = requests.request(url=url, method=method, data=body).text
                '''断言处理'''
                self.assertHandler(login_excel, response, expect_result, row_index, response_index, conclusion_index)
    
    if __name__ == "__main__":
        pytest.main(['-s', __file__])

    参考:https://www.cnblogs.com/huizaia/p/10331469.html

    参考:https://blog.csdn.net/qq_39721240/article/details/88650329

    
    
  • 相关阅读:
    Access, SQL Server, and Oracle数据类型的对应关系
    [转]SQL Server 2005 从差异备份还原数据库
    疲惫
    关于在cmd命令里的路径包含空格的问题
    导Excel时的科学计数法问题
    [转]SQL SERVER 2005 备份与恢复简介
    [转]用C#创建Windows Service
    [转] vb.net option
    [转]sql server profiler only TrueType fonts are supported. this is not a truetype font
    进程、线程、协程之概念理解[转帖]
  • 原文地址:https://www.cnblogs.com/Maruying/p/13388182.html
Copyright © 2011-2022 走看看