码迷,mamicode.com
首页 > 其他好文 > 详细

Pytest测试框架

时间:2020-11-16 12:56:36      阅读:13      评论:0      收藏:0      [点我收藏+]

标签:设计   实例   tor   浏览器   failure   if判断   for   pre   作用   

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文件

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(‘\n-----开始执行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(‘\n-----开始执行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(‘\n-----开始执行moule----‘)
    print(‘module      : %s‘ % request.module.__name__)
    print(‘----------启动浏览器---------‘)
    yield
    print("------------结束测试 end!-----------")


@pytest.fixture(scope="function", autouse=True)
def open_home(request):
    print("function:%s \n--------回到首页--------" % 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 "\033[" + ";".join(color_value) + "m"

    def format(self, record):
        record.log_color = self.parse_color(record.levelname)
        message = super(ColoredFormatter, self).format(record) + "\033[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)

Pytest测试框架

标签:设计   实例   tor   浏览器   failure   if判断   for   pre   作用   

原文地址:https://www.cnblogs.com/wp950416/p/13946864.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!