pytest的使用基础

配置环境:

Python:3.11.7

pytest:8.3.2

基本调用

在使用之前,我们需要了解pytest的默认使用限制,py文件需要以'test_'开头或'_test'结尾,类也需要以'Test'开头,且不能带init方法,其中方法也需要以'test_'开头,函数需要以'test_'开头。否则pytest会直接忽略,不运行不合规定的函数方法。

下面是一个基本的例子:

import pytest
def test_a():
    print('test_a++++++++++++++++++++')
    return 1 * 0

def test_b():
    print('test_b++++++++++++++++++++')
    return 1 / 0
# -s 表示将测试函数中的print打印出来,如果没有-s,print将不会打印
if __name__ == '__main__':
    pytest.main(['-s'])

运行结果:

pytest配置文件

因为pytest的命名规则,导致写代码很麻烦,所有我们可以通过修改配置文件来添加我们需要的东西。

pytest的配置文件为pytest.ini,将文件放置在测试目录下,在运行时pytest会自动读取配置文件,达到我们想要的效果。

addopts使用比较频繁,它可以更改pytest的运行次数、输出方式等等,类似于Linux的指令。

其中python_* 对应着运行文件名、类名。方法或者函数名,我们可以通过添加命名规则来匹配更多的函数。

[pytest]
: ini文件中,英文分号后面的内容都是注释
: 添加参数,通常是- 或 -- 开头
: --html表示将测试报告以html文档输出保存,=后面为保存路径
: --reruns 表示失败后再运行几次
addopts = -s --html=./report.html --reruns 5
: 测试模块所在目录
testpaths = ./scripts
: 测试模块文件名称规则 多个内容空格分隔
python_files = test_*.py *_test.py 
: 测试类名规则
python_classes = Test_*
: 测试函数或者方法的名称规则
python_functions = test_*

下面是一个小例子:

配置文件:

[pytest]
addopts = -s
testpaths = ./
python_files = test_*.py *_test.py 
python_classes = Test*
python_functions = demo_* test_*

代码:

import pytest
def test_a():
    print('test_a++++++++++++++++++++')
    return 1 * 0
def test_b():
    print('test_b++++++++++++++++++++')
    return 1 / 0

def demo_a():
    print('demo_a++++++++++++++++++++')
    return 1 * 0
def demo_b():
    print('demo_b++++++++++++++++++++')
    return 1 / 0
def demo_c():
    print('demo_c++++++++++++++++++++')
    return 1 * 0


if __name__ == '__main__':
    pytest.main(['-s'])

运行结果:

可以看到,即使是没有按照默认要求命名的demo函数,在更改配置文件后也能运行。

mark 标记功能

在测试运行时,如果有想要跳过的函数,或者已经认为是错误的函数,我们都可以将其标记,pytest在遇到他们时,就会打上对应的标签,会选择直接跳过。

# 跳过该函数
@pytest.mark.skip()
# 将该函数标记为错误,可以添加raises表示错误类型
@pytest.mark.xfail(raises=ZeroDivisionError)

实例代码:

import pytest
def test_a():
    print('test_a++++++++++++++++++++')
    return 1 * 0
# 使用mark来定义该函数为错误
@pytest.mark.xfail(raises=ZeroDivisionError)
def test_b():
    print('test_b++++++++++++++++++++')
    return 1 / 0

def demo_a():
    print('demo_a++++++++++++++++++++')
    return 1 * 0
# 使用mark来跳过测试
@pytest.mark.skip(reason='跳过')
def demo_b():
    print('demo_b++++++++++++++++++++')
    return 1 / 0
def demo_c():
    print('demo_c++++++++++++++++++++')
    return 1 * 0

if __name__ == '__main__':
    pytest.main(['-s'])

运行结果:

可以看到,原本两个错误的函数,一个被标记为了跳过,一个被标记为了错误。

参数化

mark

使用mark直接传入参数,函数不用调用其他的函数。

import pytest

# 使用mark进行传参,实现参数化
@pytest.mark.parametrize(['a', 'b'], [(1, 2), (30, 30), (37, 92)])
def test_a(a, b):
    print('test__________________')
    # 函数断言
    assert a + b > 100

if __name__ == '__main__':
    pytest.main()

运行结果:

fixture

通过fixture定义函数,在后面的函数中用参数的方式调用fixture函数,即可将fixture函数的返回值当做参数调用。

import pytest

@pytest.fixture()
def account():
    a = 'account'
    return a

def test_1(account):
    print(account)


if __name__ == '__main__':
    pytest.main()

运行结果:

fixture装饰器

pytest的夹具

夹具一般在函数的前后进行运行。在函数运行前搭建函数需要的环境、参数,在函数运行后回收资源。

pytest的默认夹具命名规则十分死板,为setup和teardown,其分为了模块(model),类(class),函数(function),方法(method)。

import pytest
# 夹具 用于初始化环境和回收资源,有模块化(model),类(class),函数(function),方法(method)
# 模块化夹具---------------
def setup_module(args):
    print('set_module-----------------', args)
def teardown_module(args):
    print('teardown_module-----------', args)
# ---------------------------

# 函数化夹具---------------
def setup_function(args):
    print('setup function---------------')

def teardown_function(args):
    print('teardown function---------------')
# --------------------------

def test_func_1():
    print('function11fffffffffffffffffff')
def test_func_2():
    print('function22uuuuuuuuuuuuuuuuuu')

class Test_1():
    # 类级夹具---------------------
    def setup_class(self):
        print('setup class -----------------')

    def teardown_class(self):
        print('teardown class-----------------')
    # -----------------------------------

    # 方法类夹具--------------------------
    def setup_method(self):
        print('setup method------------------')

    def teardown_method(self):
        print('teardown method------------------')
    # ----------------------------------

    def test_1(self):
        print('test1111111111111111111111')

    def test_2(self):
        print('test222222222222222222222')


if __name__ == '__main__':
    pytest.main(['-s'])

运行结果:

============================= test session starts =============================
collecting ... collected 4 items

test_3.py::test_func_1 set_module----------------- <module 'test_3' from 'F:\\自动化测试\\pytest\\test_3.py'>
setup function---------------
function11fffffffffffffffffff
PASSEDteardown function---------------

test_3.py::test_func_2 setup function---------------
function22uuuuuuuuuuuuuuuuuu
PASSEDteardown function---------------

test_3.py::Test_1::test_1 setup class -----------------
setup method------------------
test1111111111111111111111
PASSEDteardown method------------------

test_3.py::Test_1::test_2 setup method------------------
test222222222222222222222
PASSEDteardown method------------------
teardown class-----------------
teardown_module----------- <module 'test_3' from 'F:\\自动化测试\\pytest\\test_3.py'>


======================== 4 passed, 1 warning in 0.02s =========================

使用fixture定义夹具

因为pytest的默认夹具命名规则原因,使用是十分不方便,所以我们可以用fixture来定义夹具。

fixture的调用方式:

@pytest.fixture(scope = "function",params=None,autouse=False,ids=None,name=None)

scope的使用

scop的参数决定该夹具的使用范围,具体参数有函数级(function),类级(class),模块级(module)和会话级(session)。

函数级

函数级用于函数或方法的导入,下面是使用方法。

参数直接传入:

import pytest
# fixture定义夹具
# function / class / model / session

# function
@pytest.fixture()
def before():
    print('第一层fixture')
    a = 'before'
    return a
# 二次调用
@pytest.fixture()
def login(before):
    print('第二层fixture')
    return before

# 调用函数before
@pytest.mark.usefixtures('before')
def test_1():
    print('test_1')

# 直接调用函数login
def test_2(login):
    print('test_2',login)


if __name__ == '__main__':
    pytest.main(['-s'])

运行效果:

注意:function可以进行二次调用,即fixture函数'b'可以调用另一个fixture函数'a',将函数'a'看做一个参数即可,但'b'函数不会自动将'a'函数return的值进行return,'b'函数需要自己去return一个值或将'a'函数进行return,例如例子中login函数return了before函数。

类级 

类级的范围是一个class,在一个class中只运行一次,fixture函数可以在类中延迟导入,实现在某些方法不需要fixture函数时先不导入,需要fixture时再导入。

import pytest
@pytest.fixture(scope='class')
def before():
    print('class fixture-----------')
    a = 'before'
    return a


class Test1():
    def test_1(self):
        print('11111111111111111')

    def test_2(self,before):
        print('22222222222',before)

    def test_3(self,before):
        print('33333333333333',before)

    def test_4(self):
        print('44444444444444')

if __name__ == '__main__':
    pytest.main(['-s'])

运行结果:

我在test_2才导入了before函数,所以在运行test_2时fixture函数才运行,而test_3虽然也导入了before函数,但是before函数已经在该class中运行了,就不会重复运行,但是其中的参数依然是生效的。

 模块级

覆盖范围为整个py文件,在导入时生效。

import pytest

@pytest.fixture(scope='module')
def before():
    print('class fixture-----------')
    a = 'before'
    return a

def test_1():
    print('test_1')

def test_2(before):
    print('test_2',before)

def test_3(before):
    print('test_3',before)

def test_4():
    print('test_4')

class Test1():
    def test_5(self):
        print('test5')

    def test_6(self,before):
        print('test6',before)

    def test_7(self,before):
        print('test7',before)

    def test_8(self):
        print('test8')

if __name__ == '__main__':
    pytest.main(['-s'])

运行结果:

和class类似,首次调用后,在py文件中只运行一次,但效果覆盖整个文件。

会话级

回话级的覆盖范围为多个py文件,在只对一个py文件使用时,效果类似于module,所以一般在py配置文件conftest.py中。

conftest.py文件类似于pytest的配置文件,pytest在运行时会先运行conftest.py文件,生成全局fixture函数。conftest一般放在同级文件夹或父级文件夹中,以便能覆盖py文件。

params的使用

import pytest
@pytest.fixture(params=[1,2,3])
def init_data(request):
    print('request参数是',request.param)
    return request.param

# 尝试调用函数
def test_data(init_data):
    assert init_data>2


if __name__ == '__main__':
    pytest.main(['-s'])

运行效果:

params类似于一个for循环的参数,在函数导入fixture函数后,fixture函数会依次读取列表中的值给函数,params的list有多长,fixture就传几次参数,函数就循环运行几次。

params在使用时,需要添加request模块,通过request.param对params的值进行输出。

autouse的使用

autouse默认为false,为true时表示自动调用,无需传入参数名。

import pytest
@pytest.fixture()
def before():
    print('第一层函数')
    a = 'before'
    return a

@pytest.fixture(autouse=True)
def login():
    print('login')

def test_1(before):
    print(before)

@pytest.mark.usefixtures('before')
def test_2():
    pass

if __name__ == '__main__':
    pytest.main(['-s'])

运行效果:

 从图中可以看出,autouse=True的运行优先级更高,会在autouse=False函数之间运行,这里我也使用了两种不同的调用方式:装饰器'@pytest.mark.usefixture'和直接用参数引用,使用装饰器无法使用fixture函数的输出,但能够运行fixture函数来创建环境,而直接引用参数就可以使用fixture函数的输出并将其使用。

 

ids的使用

ids一般与params搭配使用,主要是给每个进程取一个标识,我们可以看看效果:

没有使用ids:

import pytest
@pytest.fixture(params=[4,[2,3],(9,2),{3,6}])
def init_data(request):
    print('request参数是',request.param)
    return request.param

# 尝试调用函数
def test_data(init_data):
    assert init_data == [2,3]


if __name__ == '__main__':
    pytest.main(['-s'])

运行结果:

加入ids:

import pytest
@pytest.fixture(params=[4,[2,3],(9,2),{3,6}],ids=['4','[2,3]','(9,2)','{3,6}'])
def init_data(request):
    print('request参数是',request.param)
    return request.param

# 尝试调用函数
def test_data(init_data):
    assert init_data == [2,3]


if __name__ == '__main__':
    pytest.main(['-s'])

运行效果:

name的使用 

默认情况下,fixture会将函数名作为参数名来传递调用,我们也可以通过name参数来重命名参数名。如果使用了name,那么原本的函数名将会被替换,函数名便不能被调用。

import pytest
@pytest.fixture(name='login')
def before():
    a = 'before'
    return a

def test_1(login):
    print(login)
    
def test_2(before):
    print(before)

if __name__ == '__main__':
    pytest.main(['-s'])

运行结果:

pytest插件

HTML报告

通过在配置文档中加入'--html=./report.html',可在当前目录下生成名为report.html的网页报告,更加方便的查看运行结果。

在使用前,我们需要下载对应的库,在命令行输入'pip install pytest-html'即可下载,然后在配置文件中的addpots后面添加'--html=文件保存路径/report.html'即可。

 

失败重试

在有外部依赖是,代码运行错误不一定是代码问题,此时我们可以对没有通过的代码进行多次运行。使用pytest-rerunfailures即可实现。

pip install pytest-rerunfailures

在配置文件的addpots中,添加'--reruns 重复运行的次数'即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值