pytest测试框架

  • 自动化测试前,需要提前准备好数据,测试完成后,需要自动清理脏数据,有没有更好用的框架?
  • 自动化测试中,需要使用多套测试数据实现用例的参数化,有没有更便捷的方式?
  • 自动化测试后,需要自动生成优雅、简洁的测试报告,有没有更好的生成方法

pytest介绍与优势

pytest是一个基于Python的自动化测试框架,其实现原理主要包括以下几个部分:

  • 1. 支持Python标准库中unittest库的用例风格,同时也支持更为灵活的测试方法定义。
  • 2. 使用了丰富的插件机制,可以轻松扩展pytest的功能,如自定义测试收集规则、测试用例执行过程中灵活代理、运行过程中的动态配置等。
  • 3. 引入了Fixture机制,再次扩展了pytest框架的功能,Fixture可以理解为测试前提条件、测试数据、测试对象等,在运行测试之前进行准备和清理工作,支持Fixture的各个生命周期钩子函数,实现了依赖注入机制。

相比于其它自动化测试框架,pytest的优势在于:

  • 1. 结构清晰,易于扩展和定制,可以直接兼容unittest框架,兼容性强。
  • 2. Fixture机制,优化了测试用例编写过程,可以让测试代码更清晰、更简洁、更易于维护。
  • 3. 丰富的插件机制,可以轻松扩展pytest的功能,如测试收集机制、测试运行时灵活代理、运行配置等。
  • 4. 支持参数化、多进程、分布式处理等并发测试,测试效率高。

总之,pytest框架具有易用、扩展性强、使用文档齐全等优点,适合用于各种规模的测试项目。

  • pytest 能够支持简单的单元测试和复杂的功能测试;
  • pytest 可以结合 Requests 实现接口测试; 结合 Selenium、Appium 实现自动化功能测试;
  • 使用 pytest 结合 Allure 集成到 Jenkins 中可以实现持续集成。
  • pytest 支持 315 种以上的插件

1 pytest简介、安装与准备

  • 前提:本地已配置完成 Python 环境
  • 第一种方式 pip install pytest

进程入cmd界面执行pip install pytest,如果需要指定版本执行pip install pytest=XXX(版本号)

  • 第二种方式 PyCharm 直接安装

pycharm中安装可以进入Terminal界面,执行pip install pytest

 或者通过pycharm中的settings --->Python Interpreter中进行安装,点击加号进入下面界面

 输入pytest,找到pytest对应的第三方库,点击下方的Install Package就可以了

#简单实例,首先创建一个test_sample.py文件,再复制下面的代码到文件中


#功能函数
def func(x):
    return x+1

#测试功能函数的用例
def test_case():
    assert func(1) ==2

思考:1. 为什么创建的文件名称需要test开头;2. 测试用例函数为什么也是需要test开头,是因为pytest是对文件和用例函数有特定的命名要求,下面我们看看pytest的命名规则

2 pytest命名规则

类型规则实例
文件test_开头 或者 _test 结尾test_sample.py或者sample_test.py
Test 开头TestDemo
方法/函数test_开头test_case()
注意:测试类中不可以添加__init__构造函数,测试包的命名是没有要求的

3 pycharm配置与界面化运行

  1. 进入 Tools->Python Intergrated Tools
  2. 选择 Default test runner 为 pytest

4 pytest测试用例结构

用例结构3部分组成:用例名称,用例步骤,用例

#用例示例
def test_***(self):
    # 测试步骤1
    # 测试步骤2
    # 断言  实际结果 对比 预期结果
    assert ActualResult == ExpectedResult

#类级别的用例示例

class TestXXX:
    def setup(self):
        # 资源准备
        pass

    def teardown(self):
        # 资源销毁
        pass

    def test_XXX(self):
        # 测试步骤1
        # 测试步骤2
        # 断言  实际结果 对比 预期结果
        assert ActualResult == ExpectedResult

5 pytest测试用例断言

  1. 什么是断言

断言(assert),是一种在程序中的一阶逻辑(如:一个结果为真或假的逻辑判断式),目的为了表示与验证软件开发者预期的结果。当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息。

          2.断言的用法

"""
断言写法
assert <表达式>
assert <表达式>,<描述>

assert <bool expression>;       
assert <bool expression> : <message>;    
"""

#第一种:assert <表达式>
def test_case():
    assert True

def test_c():
    a = 1
    b = 1
    c = 2
    assert 'abc' in "abcd"


#第二种:assert <表达式>,<描述>
def test_case1():
    a = 1
    b = 1
    c = 2
    assert a + b == c, f"{a}+{b}=={c}, 结果为真"

import sys
def test_plat():
    assert ('win32' in sys.platform), "该代码只能在 win32下执行"

6 pytest测试框架结构(setup/teardown)

在接口自动化测试中,用setup方法可以进行测试前的初始化、参数配置等工作,用teardown方法可以进行测试后的清理、还原、退出等工作。pytest测试框架提供了5种类型的setup和teardown的方法,具体如下

类型规则
setup_module/teardown_module全局模块级
setup_class/teardown_class类级,只在类中前后运行一次
setup_function/teardown_function函数级,在类外
setup_method/teardown_method方法级,类中的每个方法执行前后
setup/teardown在类中,运行在调用方法的前后(重点)

模块级别的setup和teardown,只执行一次


#模块级别,只被调用一次
def setup_module():
    print("资源准备:模块级别setup module")

def teardown_module():
    print("资源准备:模块级别teardown module")

#定义函数级别的测试用例
def test_case1():
    print('用例01')
#定义函数级别的测试用例
def test_case2():
    print('用例02')


函数级别的setup和teardown,每次函数调用的时候,都会调用它们


#定义函数级别的测试用例
def test_case1():
    print('用例01')
#定义函数级别的测试用例
def test_case2():
    print('用例02')

#函数级别,每次函数调用的时候,都会调用
def setup_function():
    print("资源准备:函数级别setup function")

def teardown_function():
    print("资源准备:函数级别teardown function")

类级别与方法级别的setup和 teardown


class TestDemo:
    #执行类前后分别执行setup_class teardown_class
    def setup_class(self):
        print("TestDemo setup_class")

    def teardown_clase(self):
        print("TestDemo teardown_class")

    #每个类里面的方法前后分别执行setup,teardown
    def setup(self):
        print("TestDemo setup")

    def teardown(self):
        print("TestDemo teardown")

    def test_demo1(self):
        print("test_demo1")

    def test_demo2(self):
        print("test_demo2")

7 pytest参数化用例

参数化的目的:

  • 通过参数的方式传递数据,从而实现数据和脚本分离。
  • 并且可以实现用例的重复生成与执行。

参数化使用场景:

  • 测试登录场景
    • 测试登录成功,登录失败(账号错误,密码错误)
    • 创建多种账号: 中⽂文账号,英⽂文账号
  • 普通测试用例方法
    • Copy 多份代码 or 读⼊入参数?
    • 一次性执⾏多个输⼊入参数
def test_param_login_ok():
    # 登录成功
    username = "right"
    password = "right"
    login(username,password)

def test_param_login_fail():
    # 登录失败
    username = "wrong"
    password = "wrong"
    login(username,password)

通过pytest 参数化实现方法,装饰器:@pytest.mark.parametrize

#计算两数在【-99,99】之间的和
class Calculator:
    def add(self, a, b):

        if a > 99 or a < -99 or b > 99 or b < -99:
            # print("请输入范围为【-99, 99】的整数或浮点数")
            return "参数大小超出范围"

        return a + b

ca = Calculator()

#第一个参数对应的是用例的参数,第二个参数对应的参数的value值,第三个参数对应的是用例的名称
@pytest.mark.parametrize(
    "a,b",
    [[99, 99], [100, 100], [-100, -100], [-99, -99], [0, 59]],
    ids=["最大值相加", "100相加", "-100相加", "-99相加", "两数不相同相加"],
)
def test_add(a, b):
    data = ca.add(a, b)
    if a > 99 or a < -99 or b > 99 or b < -99:
        assert data == "参数大小超出范围"
    else:
        assert data == a + b

#单参数,可以将数据放在列表中
search_list = ['appium','selenium','pytest']

@pytest.mark.parametrize('name',search_list)
def test_search(name):
    assert name in search_list

#多参数情况

# 数据放在元组中
@pytest.mark.parametrize("test_input,expected",[
    ("3+5",8),("2+5",7),("7+5",12)
])
def test_mark_more(test_input,expected):
    assert eval(test_input) == expected
# 数据放在列表中
@pytest.mark.parametrize("test_input,expected",[
    ["3+5",8],["2+5",7],["7+5",12]
])
def test_mark_more(test_input,expected):
    assert eval(test_input) == expected

#笛卡尔积

"""
两组数据
a=[1,2,3]
b=[a,b,c]
对应组合形势 
(1,a),(1,b),(1,c)
(2,a),(2,b),(2,c)
(3,a),(3,b),(3,c)
"""
@pytest.mark.parametrize("b",["a","b","c"])
@pytest.mark.parametrize("a",[1,2,3])
def test_param1(a,b):
    print(f"笛卡积形式的参数化中 a={a} , b={b}")

对于ids重新对用例命名会出现中文无法显示的情况,那是因为默认的编码格式不是utf-8,需要在项目中建一个conftest.py文件,文件内容如下:

# 创建conftest.py 文件 ,将下面内容添加进去,运行脚本
def pytest_collection_modifyitems(items):
    """
    测试用例收集完成时,将收集到的用例名name和用例标识nodeid的中文信息显示在控制台上
    """
    for i in items:
        i.name=i.name.encode("utf-8").decode("unicode_escape")
        i._nodeid=i.nodeid.encode("utf-8").decode("unicode_escape")

8 pytest标记测试用例

  • 场景:只执行符合要求的某一部分用例 可以把一个web项目划分多个模块,然后指定模块名称执行。
  • 解决: 在测试用例方法上加 @pytest.mark.标签名
  • 执行: -m 执行自定义标记的相关用例
    • pytest -s test_mark_zi_09.py -m=webtest
    • pytest -s test_mark_zi_09.py -m apptest
    • pytest -s test_mark_zi_09.py -m "not ios"
import pytest


@pytest.mark.webtest
def test_case():
    assert 1 ==1
@pytest.mark.apptest
def test_case1():
    assert 'abc' in 'acbccc'
@pytest.mark.iostest
def test_case2():
    assert 5+6 > 6

warning是无法识别到iostest的标签,这个没有关系,如果想要pytest识别它们,可以新增一个pytest.ini文件,文件内容如下:

[pytest]
markers = webtest
          apptest
          iostest

9 pytest设置跳过、预期失败用例

  • 这是 pytest 的内置标签,可以处理一些特殊的测试用例,不能成功的测试用例
  • skip - 始终跳过该测试用例
  • skipif - 遇到特定情况跳过该测试用例
  • xfail - 遇到特定情况,产生一个“期望失败”输出
"""
调试时不想运行这个用例
    标记无法在某些平台上运行的测试功能
    在某些版本中执行,其他版本中跳过
    比如:当前的外部资源不可用时跳过
    如果测试数据是从数据库中取到的,
    连接数据库的功能如果返回结果未成功就跳过,因为执行也都报错
解决 1:添加装饰器
@pytest.mark.skip
@pytest.mark.skipif
解决 2:代码中添加跳过代码
pytest.skip(reason)
xfail 使用场景
与 skip 类似 ,预期结果为 fail ,标记用例为 fail
用法:添加装饰器@pytest.mark.xfail
"""

import sys

import pytest


@pytest.mark.skip(reason="代码未开发完")
def test_aaa():
    print("代码未开发完")
    assert  1==2

def check_login():
    return False

def test_a():
    print("start")

    if not check_login():
        pytest.skip("kkkkkk")
    print("end")


@pytest.mark.skipif(sys.platform=='win32', reason='does not run on windows')
def test_case():
    assert True
@pytest.mark.skipif(sys.platform=='darwin', reason="does not run on mac")
def test_case1():
    assert True
@pytest.mark.skipif(sys.version_info <(3,6), reason="requires python3.6 or higher")
def test_case2():
    assert True

@pytest.mark.xfail
def test_case3():
    print("test_xfail 方法执行")
    assert 1 ==2

xfail = pytest.mark.xfail
@xfail(reason="bug 120")
def test_case4():
    assert 1==2

def test_xfail():
    print("******开始测试********")
    pytest.xfail(reason="该功能尚未完成")
    print("测试过程")
    assert 1==1

10 pytest运行用例

  • 运行 某个/多个 用例包,在Terminal中执行命令pytest -vs可以测试包下所有的用例

  • 运行 某个/多个 用例模块:pytest 文件名.py

  • 运行 某个/多个 用例类:pytest 文件名.py::类名

  • 运行 某个/多个 用例方法:pytest 文件名.py::类名::方法名

11 pytest测试用例调度与运行

  • --lf(--last-failed) 只重新运行故障。

  •  --ff(--failed-first) 先运行故障然后再运行其余的测试

12 pytest命令行常用参数

—help 可以查看pytest所有的命令参数
-x   用例一旦失败(fail/error),就立刻停止执行
--maxfail=num 用例达到
-m  标记用例
-k  执行包含某个关键字的测试用例
-v 打印详细日志
-s 打印输出日志(一般-vs一块儿使用)
—collect-only(测试平台,pytest 自动导入功能 )

13 python执行pytest

  • 使用 main 函数
if __name__ == '__main__':
    # 1、运行当前目录下所有符合规则的用例,包括子目录(test_*.py 和 *_test.py)
    pytest.main()
    # 2、运行test_mark1.py::test_dkej模块中的某一条用例
    pytest.main(['test_mark1.py::test_dkej','-vs'])
    # 3、运行某个 标签
    pytest.main(['test_mark1.py','-vs','-m','dkej'])

运行方式
  `python test_*.py `
  • 使用 python -m pytest 调用 pytest(jenkins 持续集成用到)

14 pytest异常处理

  • 可以捕获特定的异常
  • 获取捕获的异常的细节(异常类型,异常信息)
  • 发生异常,后面的代码将不会被执行
def test_raise():
    with pytest.raises(ValueError):
        raise ValueError("value msut be 0 or None")

def test_raise1():
    with pytest.raises(ValueError) as exc_info:
        raise ValueError("value must be 42")
    assert exc_info.type is ValueError
    assert exc_info.value.args[0] == 'value must be 42'

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值