zoukankan      html  css  js  c++  java
  • Pytest测试框架

    Pytest测试框架

    基本介绍

    1. 介绍:pytest是python的一种单元测试框架,同自带的Unittest测试框架类似,相比于Unittest框架使用起来更简洁,效率更高。
    2. 特点
      1. 易上手,入门简单
      2. 支持简单单元测试和复杂的功能测试
      3. 支持参数化
      4. 执行测试过程中可以将某些测试跳过,或者对某些预期失败的case标记成失败
      5. 支持重复执行失败的case
      6. 支持运行由Nose,Unittest编写的测试Case
      7. 具有第三方插件,可以自定义扩展
      8. 方便和持续集成工具集成

    Pytest的安装方式

    1. linux下:sudo pip3 install -U pytest
    2. windows下:pip3 install -U pytest
    3. 运行pytest --version会展示当前已安装版本

    Pytest的设计原则

    1. 文件名以test_*py的格式
    2. 以test_开头的函数
    3. 以Test开头的类
    4. 以test_开头的类方法
    5. 所有包必须要有__init__.py文件
      批注:为什么每个包都必须要带有__init__.py文件?
      理由:一个包是一个带有特殊文件__init__.py文件定义了包的属性和方法.其实它可以什么都不定义,可以是一个空文件,但是必须存在.如果__init__.py不存在,这个目录就仅仅是一个目录,而不是一个包,它就不能被导入或者包含其他的模块和嵌套包.

    Pytest的常用断言

    1. assert A: 判断A是真
    2. assert not A: 判断A不为真
    3. assert A in B: 判断B包含A
    4. assert A==B: 判断A等于B
    5. assert A!=B: 判断A不等于B

    Pytest的执行方法

    1. pytest
    2. py.test
    3. python -m pytest
    4. 执行目录下的所有用例 pytest 文件名/
    5. 执行某个文件下用例 pytest 脚本名.py
    6. 标记式表达式(将会运行用@ pytest.mark.slow装饰器修饰的所有测试) pytest -m slow
    7. 遇到错误停止测试 pytest -x test_class.py
    8. 错误达到指定数量停止测试 pytest --maxfail=1

    Pytest的用例运行级别

    1. 模块级别 setup_module、teardown_module
    2. 函数级别 setup_function、teardown_function,不在类中的方法
    3. 类级别 setup_class、teardown_class
    4. 方法级别 setup_method、teardown_method
    5. 方法细化级别 setup、teardown

    Pytest之fixture

    从模块级别中来看,用例加setup和teardown可以实现在测试用例之前或之后加入一些操作,但这种是整个脚本全局生效的,如果想实现以下场景:用例1需要先登录,用例2不需要登录,用例3需要先登录,那么很显然使用setup和teardown就实现不了,于是可以自动以测试用例的预置条件

    fixture的优势

    fixture相对于setup和teardown来说有以下几点优势:

    1. 命名方式灵活,不限于setup和teardown这几个命名
    2. conftest.py配置里可以实现数据共享,不需要import就能自动找到一些配置
    3. scope="module"可以时间多个.py跨文件共享前置,每一个.py文件调用一次
    4. scope="session"以实现多个.py跨文件使用一个session来完成多个用例
      使用@pytest.fixture(scope="function",params=None,autouse=False,ids=None,name=None):装饰器来标记fuxture的功能
    5. 使用此装饰器(带或者不带参数)来定义fixture功能,fixture功能的名称可以在以后使用
    6. 引用它会在运行测试之前调用它:test模块或类可以使用pytest.mark.usefixtures
    7. 测试功能可以直接使用fixture名称作为输入参数,在这种情况下夹具实例从fixture返回功能将被注入
    8. scope scope有四个级别参数
      1. 默认的function 每一个函数或方法都会调用
      2. class 每一个类调用一次
      3. module 每一个.py文件调用一次,该文件内有多个function和class
      4. session 多个文件调用一次,可以跨.py文件调用个,每个.py文件就是module
    9. params 一个可选的参数列表,会导致多个参数fixture功能和所有测试使用它
    10. autouse 如果为True,则为所有测试激活fixture func可以看到它,如果为False(默认值),则需要显示激活fixture
    11. ids 每个字符串id的列表,每个字符串对应与params这样他们就是测试ID的一部分,如果没有提供ID它们将从params自动生成
    12. yield控制teardown
      1. 编写用例时,将yield写在结束操作之前即可,然后在所有用例执行完之后执行一次
      2. yield在用例执行过程中,用例出现异常,不影响yield后面的teardown执行,运行结果互不影响,并且全部用例执行完以后,yield呼唤teardown操作
      3. 在setup就异常了,那么是不会去执行yield后面的teardown内容

    fixture之scope参数常规使用

    使用@pytest.fixture(scope="module"):module作用是当前整个py文件都能生效,参数写上函数名称即可

    # 新建一个文件sdfdsf.py
    # coding:utf-8
    import pytest
    
    
    @pytest.fixture(scope="module")
    def open():
        print("打开浏览器,并且打开百度首页")
    
    
    def test_s1(open):
        print("用例1:搜索python-1")
    
    
    def test_s2(open):
        print("用例2:搜索python-2")
    
    
    def test_s3(open):
        print("用例3:搜索python-3")
    
    
    if __name__ == "__main__":
        pytest.main(["-s", "sdfdsf.py"])
    通过结果可以看出,三个测试用例都调用了open函数,但是只会在第一个用例之前执行一次
    
    通过规律可以得出,如果我第一个测试用例之前不调用,那么我就在第二个测试用例调用open函数
    

    scope控制setup,yield控制teardown实例

    # 新建test_demo.py
    
    import pytest
    
    
    @pytest.fixture(scope="module")
    def open():
        print("打开浏览器,访问至百度首页")
        yield  # 编写用例时,将yield写在结束操作之前就可,然后在所有用例执行完之后执行一次
        print("这是teardown操作")
        print("关闭浏览器")
    
    
    def test_case1(open):
        print("用例1")
    
    
    def test_case2(open):
        print("用例2")
    
    
    if __name__ == "__main__":
        pytest.main(["-v", "test_demo.py"])
    
    # 结果:上面的用例都调用了open()
    # 操作,在所有用例执行前执行一次open,然后运行用例,最后所有用例执行完之后执行一次yield后面的结束操作
    # 注:yield在用例里面充当了teardown操作。就算用例执行报错,yield还是会正常执行不会被影响
    

    Pytest之autouse=True

    平常写自动化用例会写一些前置的fixture操作,用例需要用到就直接传该函数的参数名称就行了,当用例很多的时候,每次都传这个参数,会比较麻烦,fixture里面有个参数autouse,默认是Fasle没开启的,可以设置为True开启自动使用fixture功能,这样用例就不用每次都去传参了
    调用fixture三种方法:

    1. 函数或类里面方法直接传fixture的函数参数名称
    2. 使用装饰器@pytest.mark.userfixtures()修饰
    3. autouse=True自动使用

    方式一:用例传fixture参数

    先定义start功能,用例全部传start参数,调用该功能

    # content of test_06.py
    import time
    import pytest
    
    
    @pytest.fixture(scope="function")
    def start(request):
        print('
    -----开始执行function----')
    
    
    def test_a(start):
        print("-------用例a执行-------")
    
    
    class Test_aaa():
    
        def test_01(self, start):
            print('-----------用例01--------------')
    
        def test_02(self, start):
            print('-----------用例02------------')
    
    
    if __name__ == "__main__":
        pytest.main(["-s", "sdfg.py"])
    

    方式二:装饰器usefixtures

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

    # content of test_07.py
    import time
    import pytest
    
    
    @pytest.fixture(scope="function")
    def start(request):
        print('
    -----开始执行function----')
    
    
    @pytest.mark.usefixtures("start")
    def test_a():
        print("-------用例a执行-------")
    
    
    @pytest.mark.usefixtures("start")
    class Test_aaa():
    
        def test_01(self):
            print('-----------用例01--------------')
    
        def test_02(self):
            print('-----------用例02------------')
    
    
    if __name__ == "__main__":
        pytest.main(["-s", "sdfg.py"])
    

    方式三:设置autouse=True

    autouse设置为True,自动调用fixture功能

    1. start设置scope为module级别,在当前.py用例模块只执行一次,autouse=True自动使用
    2. open_home设置scope为function级别,每个用例前都调用一次,自动使用
    # content of test_08.py
    import time
    import pytest
    
    
    @pytest.fixture(scope="module")
    def start(request):
        print('
    -----开始执行moule----')
        print('module      : %s' % request.module.__name__)
        print('----------启动浏览器---------')
        yield
        print("------------结束测试 end!-----------")
    
    
    @pytest.fixture(scope="function", autouse=True)
    def open_home(request):
        print("function:%s 
    --------回到首页--------" % request.function.__name__)
    
    
    def test_01():
        print('-----------用例01--------------')
    
    
    def test_02():
        print('-----------用例02------------')
    
    
    if __name__ == "__main__":
        pytest.main(["-s", "sdfg.py"])
    

    Pytest之fixture传参数request

    如果想把登录操作放到前置操作里,也就是用到@pytest.fixture装饰器,传参就用默认的request参数

    request传入一个参数

    # test_02.py
    # coding:utf-8
    import pytest
    
    # 测试账号数据
    test_user_data = ["admin1", "admin2"]
    
    
    @pytest.fixture(scope="module")
    def login(request):
        user = request.param
        return user
    
    
    @pytest.mark.parametrize("login", test_user_data, indirect=True)    #添加indirect=True参数是为了把login当成一个函数去执行,而不是一个参数
    def test_login(login):
        '''登录用例'''
        a = login
        print("测试用例中login的返回值:%s" % a)
        assert a != ""
    
    
    if __name__ == "__main__":
        pytest.main(["-s", "test_02.py"])
    

    reuquest传入两个参数

    如果用到@pytest.fixture里面用2个参数情况,可以把多个参数用一个字典去存储,这样最终还是只传一个参数不同的参数再从字典里面取对应key值就行,如: user = request.param["user"]

    # test_03.py
    # coding:utf-8
    import pytest
    
    # 测试账号数据
    test_user_data = [{"user": "admin1", "psw": "111111"},
                      {"user": "admin1", "psw": ""}]
    
    
    @pytest.fixture(scope="module")
    def login(request):
        user = request.param["user"]
        psw = request.param["psw"]
        print("登录账户:%s" % user)
        print("登录密码:%s" % psw)
        if psw:
            return True
        else:
            return False
    
    
    # indirect=True 声明login是个函数
    @pytest.mark.parametrize("login", test_user_data, indirect=True)
    def test_login(login):
        '''登录用例'''
        a = login
        print("测试用例中login的返回值:%s" % a)
        assert a, "失败原因:密码为空"
    
    
    if __name__ == "__main__":
        pytest.main(["-s", "sdfdsf.py"])
    

    多个fixture

    用例上面可以同时放多个fixture,也就是多个前置操作,可以支持装饰器叠加,使用parametrize装饰器叠加时,用例组合是2个参数个数相乘

    # test_04.py
    # coding:utf-8
    import pytest
    
    # 测试账号数据
    test_user = ["admin1", "admin2"]
    test_psw = ["11111", "22222"]
    
    
    @pytest.fixture(scope="module")
    def input_user(request):
        user = request.param
        print("登录账户:%s" % user)
        return user
    
    
    @pytest.fixture(scope="module")
    def input_psw(request):
        psw = request.param
        print("登录密码:%s" % psw)
        return psw
    
    
    @pytest.mark.parametrize("input_user", test_user, indirect=True)
    @pytest.mark.parametrize("input_psw", test_psw, indirect=True)
    def test_login(input_user, input_psw):
        '''登录用例'''
        a = input_user
        b = input_psw
        print("测试数据a-> %s, b-> %s" % (a, b))
        assert b
    
    
    if __name__ == "__main__":
        pytest.main(["-s", "test_04.py"])
    

    conftest.py配置

    在上面的案例中,同一个py文件,多个用例调用一个登陆功能,如果有多个py文件都需要调用这个登陆功能的话,就不能把登录写到用例里面去了,此时应该有个配置文件,单独管理一些预置的操作场景,pytest里面默认读取conftest.py里面的配置
    conftest.py配置需要注意一下几点:

    1. conftest.py配置名称是固定的,不能改名称
    2. conftest.py与运行的用例要在同一个package下,并且有__init__.py文件
    3. 不需要import导入conftest.py,pytest用例会自动查找

    Pytest的测试报告

    1. 安装插件 pip install pytest-html
    2. 执行方法 pytest --html=report.html
    3. 指定路径执行 pytest --html=./report/report.html
    4. 将--html=report/report.html追加到pytest.ini的addopts后
    5. 报告独立显示,产生的报告css是独立的,分享报告的时候样式会丢失,为了更好的分享发邮件展示报告,可以把css样式合并到html里 pytest --html=report.html --self-contained-html

    Pytest的失败重跑

    1. 失败重跑需要依赖pytest-rerunfailures插件,使用pip安装就行 pip install pytest-rerunfailures
    2. 使用方法:命令行格式 pytest --reruns n n:表示为重试的次数 这种命令行一般放在pytest.ini的配置文件中,在addopts中的后面添加命令行参数
    3. 用例失败再重跑1次,命令行加个参数--reruns 1 py.test --reruns 1 --html=report.html --self-contained-html

    Pytest控制函数顺序

    函数修饰符的方式标记被测函数执行的顺序
    
    安装方式:
    
      插件名称:使用命令行    pip3 install pytest-ordering
    
    使用方法:
    
    标记于被测试函数,@pytest.mark.run(order=x)   
    order的优先级    0>较小的正数>较大的正数>无标记>较小的负数>较大的负数
    根据order传入的参数来结局运行顺序
    order值全为证书或权威负数时,运行顺序:值越小,优先级越高
    正数和负数同时存在:正数优先级高
    默认情况下,pytest默认从上往下执行,可以通过第三方插件包改变其运行顺序.
    

    Pytest跳过测试函数

    1. @pytest.mark.skip可以标记无法在某些平台上运行的测试功能,或者希望失败的测试功能,
    2. skip意味着只有在满足某些条件时才希望测试通过,否则pytest应该跳过运行测试。 常见示例是在非Windows平台上跳过仅限Windows的测试,或跳过测试依赖于当前不可用的外部资源(例如数据库)。
    3. xfail意味着您希望测试由于某种原因而失败。 一个常见的例子是对功能的测试尚未实施,或尚未修复的错误。 当测试通过时尽管预计会失败(标有pytest.mark.xfail),它是一个xpass,将在测试摘要中报告。
    4. pytest计数并分别列出skip和xfail测试。

    skip

    跳过测试函数的最简单方法是使用跳过装饰器标记它,可以传递一个可选的原因

    @pytest.mark.skip(reason="no way of currently testing this")
    def test_the_unknown():
        ...
    

    也可以通过调用来测试执行或设置期间强制跳过pytest.skip(reason)功能:

    def test_function(): if not valid_config(): pytest.skip("unsupported configuration")
    

    也可以使用pytest.skip(reason,allow_module_level = True)跳过整个模块级别

    import pytest
    if not pytest.config.getoption("--custom-flag"):
        pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True)
    

    skipif

    根据特定的条件,不执行标识的测试函数
    
    方法:skipif(condition,reason=None)
    
    参数:
    
    condition:跳过的条件,必传参数    可以直接传True
    reason:标注原因,必传参数    可以传reason="done"表示正在执行跳过
    使用方法:@pytest.mark.skipif(condition,reason="xxx")
    

    Pytest之error和failed区别

    测试结果一般分为三种

    1. passed(通过)
    2. failed(一般在测试用例里面断言失败)
    3. error(一般在fixture断言失败或者自己代码写的有问题)

    Pytest标记为预期失败函数

    当用例a失败的时候,如果用例b和用例c都是依赖于第一个用例的结果,那可以直接跳过用例b和c的测试,直接给他标记失败xfail用到的场景,登录是第一个用例,登录之后的操作b是第二个用例,登录之后操作c是第三个用例,很明显三个用例都会走到登录,如果登录都失败,那后面个用例就没有测试的必要了,并且标记为失败用例

    标记测试函数为失败函数
    
    方法:xfail(condition=None,reason=None,raises=None,run=true,strict=False)
    
    常用参数:
    
    condition:预期失败的条件,必传参数    如果传True表示确认设定预期失败,传False表示设定预定成功
    reason:失败的原因,必传参数    reason="done",可以传任何参数值,我们设定为done
    使用方法:@pytest.mark.xfail(condition,reason="xx")
    

    用例设计

    pytest里面用xfail标记用例为失败的用例,可以直接跳过,实现基本思路

    1. 把登录写为前置操作
    2. 对登录的账户和密码参数化,参数用参数=[{"user":"admin","pwd":"111"}]表示
    3. 多个用例放在一个Test_xx的class里
    4. test_01,test_02,test_03全部调用fixture里面的login功能
    5. test_01测试登陆用例
    6. test_02和test_03执行前用if判断登录的结果,登录失败就执行,pytest.xfail("登录不成功,标记为xfail")

    登录成功的用例

    # content of test_05.py
    
    # coding:utf-8
    import pytest
    
    canshu = [{"user": "amdin", "psw": "111"}]
    
    
    @pytest.fixture(scope="module")
    def login(request):
        user = request.param["user"]
        psw = request.param["psw"]
        print("正在操作登录,账号:%s, 密码:%s" % (user, psw))
        if psw:
            return True
        else:
            return False
    
    
    @pytest.mark.parametrize("login", canshu, indirect=True)
    class Test_xx():
    
        def test_01(self, login):
            '''用例1登录'''
            result = login
            print("用例1:%s" % result)
            assert result == True
    
        def test_02(self, login):
            result = login
            print("用例2,登录结果:%s" % result)
            if not result:
                pytest.xfail("登录不成功, 标记为xfail")
    
            assert 1 == 1
    
        def test_03(self, login):
            result = login
            print("用例3,登录结果:%s" % result)
            if not result:
                pytest.xfail("登录不成功, 标记为xfail")
    
            assert 1 == 1
    
    
    if __name__ == "__main__":
        pytest.main(["-s", "test_05.py"])
    

    登录失败的用例

    # content of test_05.py
    # coding:utf-8
    import pytest
    
    canshu = [{"user": "amdin", "psw": ""}]
    
    
    @pytest.fixture(scope="module")
    def login(request):
        user = request.param["user"]
        psw = request.param["psw"]
        print("正在操作登录,账号:%s, 密码:%s" % (user, psw))
        if psw:
            return True
        else:
            return False
    
    
    @pytest.mark.parametrize("login", canshu, indirect=True)
    class Test_xx():
    
        def test_01(self, login):
            '''用例1登录'''
            result = login
            print("用例1:%s" % result)
            assert result == True
    
        def test_02(self, login):
            result = login
            print("用例3,登录结果:%s" % result)
            if not result:
                pytest.xfail("登录不成功, 标记为xfail")
    
            assert 1 == 1
    
        def test_03(self, login):
            result = login
            print("用例3,登录结果:%s" % result)
            if not result:
                pytest.xfail("登录不成功, 标记为xfail")
    
            assert 1 == 1
    
    
    if __name__ == "__main__":
        pytest.main(["-s", "test_05.py"])
    

    Pytest的参数化

    Pytest的常规参数化

    单个参数的参数化
    
    方法:parametrize(argnames,argvalues,indirect=False,ids=None,scope=None)
    
    常用参数:
    
    argnames:参数名
    argvalues:参数对应值,类型必须为list    ["1","2","3"]这种类型
    使用方法:@pytest.mark.parametrize(argnames,argvalues)    参数值为Nge,测试方法就会运行N次
    
    单个参数的参数化代码如下:
    
    @pytest.mark.parametrize("keys",["1","2"]) # 第二个列表参数写一个表示执行一个参数一次,两个表示执行一个参数两次
    def test_search(self,keys):
        self.driver.find_element_by_id("com.android.settings:id/search").click()
        self.driver.find_element_by_id("android:id/search_src_text").send_keys(keys)
    多个参数的参数化代码如下:
    
    # 第一个参数使用元祖,第二个参数使用列表嵌套元祖
    @pytest.mark.parametrize(("username","password"),[("wupeng","123456"),("wupeng1","123456")])
    def test_search(self,username,password):
        self.driver.find_element_by_id("com.android.settings:id/search").click()
        self.driver.find_element_by_id("android:id/search_src_text").send_keys(username)
        self.driver.find_element_by_id("android:id/search_src_text").send_keys(password)
    

    第一种生成allure动态标题方式

    import pytest
    import allure
    
    
    class TestLogin:
        datas = [
            ({"username": "liuxin", "password": 123456}, "success", "输入正确用户名,正确密码"),
            ({"username": "liuxin", "password": 123456}, "faild", "输入错误用户名,正确密码"),
            ({"username": "liuxin", "password": 123456}, "success", "输入正确用户名,正确密码"),
        ]
    
        @pytest.mark.parametrize("username,password,value", datas)
        @allure.title("{value}")
        def test_login(self, username, password, value):
            result = password
            assert result == "success"
    
    
    if __name__ == '__main__':
        pytest.main(["-s", "test_pytest.py"])
    

    第二种生成allure动态标题

    1. feature模块 allure.dynamic.feature(feature_name)
    2. 功能点story allure.dynamic.story(case_story)
    3. 用例标题title allure.dynamic.title(case_title)
    4. 用例描述 allure.dynamic.description(case_description)

    参数ids之unicode问题

    在没有配置conftest.py的情况下,终端运行如下代码:

    import pytest
    
    
    def login(username, password):
        '''登录'''
        # 返回
        return {"code": 0, "msg": "success!"}
    
    
    # 测试数据
    test_datas = [
        ({"username": "yoyo1", "password": "123456"}, "success!"),
        ({"username": "yoyo2", "password": "123456"}, "success!"),
        ({"username": "yoyo3", "password": "123456"}, "success!"),
    ]
    
    
    @pytest.mark.parametrize("test_input,expected",
                             test_datas,
                             ids=[
                                 "输入正确账号,密码,登录成功",
                                 "输入错误账号,密码,登录失败",
                                 "输入正确账号,密码,登录成功",
                             ]
                             )
    def test_login(test_input, expected):
        '''测试登录用例'''
        # 获取函数返回结果
        result = login(test_input["username"], test_input["password"])
        # 断言
        assert result["msg"] == expected
    

    配置conftest.py

    def pytest_collection_modifyitems(items):
        """
        测试用例收集完成时,将收集到的item的name和nodeid的中文显示在控制台上
        :return:
        """
        for item in items:
            item.name = item.name.encode("utf-8").decode("unicode_escape")
            print(item.nodeid)
            item._nodeid = item.nodeid.encode("utf-8").decode("unicode_escape")
    

    Pytest之mark标记

    pytest可以支持自动以标记,自动以标记可以把一个web项目划分为多个模块,然后指定模块名称执行,一个大项目自动化用例时,可以划分多个模块,也可以使用标记功能,表明哪些是模块1,哪些是模块2,运行代码时指定mark名称运行就可以
    以下用例,标记test_send_http()为webtest

    # content of test_server.py
    
    import pytest
    
    
    @pytest.mark.webtest
    def test_send_http():
        pass  # perform some webtest test for your app
    
    
    def test_something_quick():
        pass
    
    
    def test_another():
        pass
    
    
    class TestClass:
        def test_method(self):
            pass
    
    
    if __name__ == "__main__":
        pytest.main(["-s", "test_server.py", "-m=webtest"])
    只运行用webtest标记的测试,在运行的时候,加个-m参数,指定参数值webtest    pytest -v -m webtest
    
    如果不想执行标记webtest的用例,那就用"not webtest"    pytest -v -m "not webtest"
    

    如果想指定运行某个.py模块下,类里面的一个用例,如:TestClass里面test_method用例,每个test_开头(或_test结尾)的用例,函数(或方法)的名称就是用例的节点id,指定节点id运行用-v 参数,当然也可以选择运行整个class pytest -v test_server.py::TestClass

    if __name__ == "__main__":
        pytest.main(["-v", "test_server.py::TestClass::test_method"])
     
    
    if __name__ == "__main__":
        pytest.main(["-v", "test_server.py::TestClass", "test_server.py::test_send_http"])
    

    Pytest之运行上次失败用例

    80%的bug集中在20%的模块,越是容易出现bug的模块,bug是越改越多,当开发修复完bug后,一般是重点测上次失败的用例,那么自动化测试也是一样,所以为了节省时间,可以值测试上次失败的用例

    1. pytest --lf 只重新运行上次运行失败的用例(或如果没有失败的话会全部跑)
    2. pytest --ff 运行所有测试,但首选运行上次运行失败的测试(这可能会重新测试,从而导致重复的fixture setup/teardown)

    Pytest之分布式执行

    优点:节约时间
    安装插件:pip install pytest-xdist

    并行测试

    直接加个-n参数即可,后面num参数就是并行数量,比如num设置为3 pytest -n 3
    使用pytest-xdist插件也能生成html报告,完美支持pytest-html插件 pytest -n 3 --html=report.html --self-contained-html

    分布式测试(使用nginx实现)

    知识盲区,后续补充...

    Pytest之重复执行用例

    平常在做功能测试时,经常遇到某个模块不稳定,偶然会出现一些bug,或者是特定条件需要执行多次用例的,我们需要针对某个某块用例重复执行多次
    pytest-repeat是pytest的一个插件,用于重复执行单个用例或多个用例,并指定重复多次

    安装及使用

    1. 下载安装:pip install pytest-repeat
    2. 使用--count命令指定要运行测试用例和测试次数 py.test --count=10 test_file.py

    --repeat-scope

    从运行的用例结果看,是运行完其中一个测试用例一个轮回,在运行下一个测试用例一个轮回,但是希望是当前文件中的所有测试用例执行一遍后,在继续重复执行
    --repeat-scope类似于pytest fixture的scope参数,--repeat-scope也可以设置参数:

    1. function 默认范围针对每个用例重复执行,在执行下一个用例
    2. class 以class为用例集合单位,重复执行class里面的用例在执行下一个
    3. module 以模块为单位,重复执行模块里面的用例,在执行下一个
    4. session 重复这个测试会话,即所有收集的测试执行一次,然后所有这些测试在此执行等等
      使用--repeat-scope=session重复执行整个会话用例 pytest baidu/test_1_baidu.py -s --count=5 --repeat-scope=session

    @pytest.mark.repeat(count)

    如果在代码中需要标记重复多次的测试,可以使用@pytest.mark.repeat(count)装饰器

    Pytest之pytest-assume

    pytest断言失败后,后面的代码就不会执行了,通常一个用例会写多个断言,有时候我们希望第一个断言失败后,后面能继续断言,那么pytest-assume插件可以解决断言失败后继续断言的问题
    安装 pip install pytest-assume

    pytest-assume断言案例

    import pytest
    
    
    @pytest.mark.parametrize(('x', 'y'),
                             [(1, 1), (1, 0), (0, 1)])
    def test_simple_assume(x, y):
        print("测试数据x=%s, y=%s" % (x, y))
        pytest.assume(x == y)
        pytest.assume(x + y > 1)
        pytest.assume(x > 1)
        print("测试完成!")
    

    上下文管理器

    pytest.assume也可以使用上下文管理器去断言,需要注意的是每个with块只能有一个断言,如果一个with下有多个断言,当第一个断言失败的时候,后面的断言就不会起作用

    import pytest
    from pytest import assume
    
    
    @pytest.mark.parametrize(('x', 'y'),
                             [(1, 1), (1, 0), (0, 1)])
    def test_simple_assume(x, y):
        print("测试数据x=%s, y=%s" % (x, y))
        with assume: assert x == y
        with assume: assert x + y == 1
        with assume: assert x == 1
        print("测试完成!")
    

    Pytest的配置文件

    pytest的配置文件通常放到测试目录下,名称为pytest.ini,命令行运行时会使用该配置文件中的配置,在开头添加[pytest],添加剩余的内容如下:

    1. 配置pytest命令行运行参数 addopts=-s
    2. 配置测试搜索的路径 testpaths=./scripts
    3. 配置测试搜索的文件名 python_files=test_*.py
    4. 配置测试搜索的测试类名 python_classes=Test*
    相关代码:
    
    [pytest]
    # 添加命令行参数 添加生成报告快捷命令
    addopts = -s --html=report/report.html
    # 搜索哪个文件夹
    testpaths = ./scripts
    # 函数名
    python_functions=test_*
    # 文件名
    python_files = test_*.py
    # 类名
    python_classes = Test*
    

    logging日志

    基本操作:在项目根目录下创建一个log目录,log目录下创建log文件,在需要使用logging日志的时候,引入logger

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    
    """
        log.py
    """
    
    import os
    import logging
    import logging.handlers
    
    __all__ = ["logger"]
    
    # 用户配置部分 ↓
    LEVEL_COLOR = {
        'DEBUG': 'cyan',
        'INFO': 'green',
        'WARNING': 'yellow',
        'ERROR': 'red',
        'CRITICAL': 'red,bg_white',
    }
    STDOUT_LOG_FMT = "%(log_color)s[%(asctime)s] [%(levelname)s] [%(threadName)s] [%(filename)s:%(lineno)d] %(message)s"
    STDOUT_DATE_FMT = "%Y-%m-%d %H:%M:%S"
    FILE_LOG_FMT = "[%(asctime)s] [%(levelname)s] [%(threadName)s] [%(filename)s:%(lineno)d] %(message)s"
    FILE_DATE_FMT = "%Y-%m-%d %H:%M:%S"
    
    
    # 用户配置部分 ↑
    
    
    class ColoredFormatter(logging.Formatter):
        COLOR_MAP = {
            "black": "30",
            "red": "31",
            "green": "32",
            "yellow": "33",
            "blue": "34",
            "magenta": "35",
            "cyan": "36",
            "white": "37",
            "bg_black": "40",
            "bg_red": "41",
            "bg_green": "42",
            "bg_yellow": "43",
            "bg_blue": "44",
            "bg_magenta": "45",
            "bg_cyan": "46",
            "bg_white": "47",
            "light_black": "1;30",
            "light_red": "1;31",
            "light_green": "1;32",
            "light_yellow": "1;33",
            "light_blue": "1;34",
            "light_magenta": "1;35",
            "light_cyan": "1;36",
            "light_white": "1;37",
            "light_bg_black": "100",
            "light_bg_red": "101",
            "light_bg_green": "102",
            "light_bg_yellow": "103",
            "light_bg_blue": "104",
            "light_bg_magenta": "105",
            "light_bg_cyan": "106",
            "light_bg_white": "107",
        }
    
        def __init__(self, fmt, datefmt):
            super(ColoredFormatter, self).__init__(fmt, datefmt)
    
        def parse_color(self, level_name):
            color_name = LEVEL_COLOR.get(level_name, "")
            if not color_name:
                return ""
    
            color_value = []
            color_name = color_name.split(",")
            for _cn in color_name:
                color_code = self.COLOR_MAP.get(_cn, "")
                if color_code:
                    color_value.append(color_code)
    
            return "33[" + ";".join(color_value) + "m"
    
        def format(self, record):
            record.log_color = self.parse_color(record.levelname)
            message = super(ColoredFormatter, self).format(record) + "33[0m"
    
            return message
    
    
    def _get_logger(log_to_file=True, log_filename="default.log", log_level="DEBUG"):
        _logger = logging.getLogger(__name__)
    
        stdout_handler = logging.StreamHandler()
        stdout_handler.setFormatter(
            ColoredFormatter(
                fmt=STDOUT_LOG_FMT,
                datefmt=STDOUT_DATE_FMT,
            )
        )
        _logger.addHandler(stdout_handler)
    
        if log_to_file:
            _tmp_path = os.path.dirname(os.path.abspath(__file__))
            _tmp_path = os.path.join(_tmp_path, "../log/{}".format(log_filename))
            file_handler = logging.handlers.TimedRotatingFileHandler(_tmp_path, when="midnight", backupCount=30,encoding="utf-8")
            file_formatter = logging.Formatter(
                fmt=FILE_LOG_FMT,
                datefmt=FILE_DATE_FMT,
            )
            file_handler.setFormatter(file_formatter)
            _logger.addHandler(file_handler)
    
        _logger.setLevel(log_level)
        return _logger
    
    
    logger = _get_logger(log_to_file=True)
    
  • 相关阅读:
    [转]mysql视图学习总结
    [转]mysql索引详解
    mysql索引的操作
    [转]mysql的约束
    mysql表的操作
    【转】mysql的数据类型
    java泛型
    java 8新特性
    Dubbo有意思的特性介绍
    dubbo + zookeeper
  • 原文地址:https://www.cnblogs.com/wp950416/p/13946864.html
Copyright © 2011-2022 走看看