Python测试框架pytest(续)-进阶用法

2022-06-13 12:14:42 浏览数 (1)

首先我们来学习下pytest的用来做前置工作和清理工作的setup和teardown方法。

代码语言:javascript复制
模块级别:setup_module、teardown_module
函数级别:setup_function、teardown_function,不在类中的方法
类级别:setup_class、teardown_class
类方法级别:setup_method、teardown_method
函数和方法级别:setup、teardown

将我们之前的文件test_pytest.py修改为以下内容。运行后得到以下信息。

代码语言:javascript复制
#-*- codeing = utf-8 -*-#@Time: 2021/8/19 0:05#@Author: 怪盗LYL#@File: test_pytest.py#@Software: PyCharmimport pytestdef setup_module():    print("setup_module():在模块之前执行一次")def teardown_module():    print("teardown_module():在模块之后执行一次")def setup_function():    print("setup_function():每个函数(类外)之前执行")def teardown_function():    print("teardown_function():每个函数(类外)之后执行")def add(x, y):    return x   ydef test_1():    print("测试类外面的方法")    assert add(1, 2) == 3def test_2():    print("正在执行用例2")    assert 1 == 1def setUp():    print("每个用例开始前执行")def tearDown():    print("每个用例结束后执行")class TestClass1(object):    @classmethod    def setup_class(self):        print("setup_class(self):每个类之前执行一次,只执行一次")    @classmethod    def teardown_class(self):        print("teardown_class(self):每个类之后执行一次,只执行一次")    # 测试是否符合预期    def test_add(self):        print("测试类内的方法test_add")        assert add(1,2) == 3    def test_true(self):        print("测试类内的方法test_true")        assert 1==1class TestClassTwo(object):    @classmethod    def setup_class(self):        print("setup_class(self):TestClassTwo之前执行一次,只执行一次")    @classmethod    def teardown_class(self):        print("teardown_class(self):TestClassTwo类之后执行一次,只执行一次")    def setup(self):        print('setup:每个用例前开始执行')    def teardown(self):        print('teardown:每个用例后开始执行')    def setup_method(self):        print("TestClassTwo类内方法前执行")    def teardown_method(self):        print("TestClassTwo类内方法后执行")    # 测试是否符合预期    def test_addTwo(self):        print("测试类内的方法test_add2")        assert add(1,2) == 3        assert add(2,2) == 4    def test_trueTwo(self):        print("测试类内的方法test_true2")        assert 1==1        assert 2==2if __name__ == '__main__':    pytest.main(["-v","-s","test_pytest.py"])

直观的看到第一个运行和最后运行的是setup_module和teardown_module(红色)。setup_function/teardown_function(绿色)分别在类外的测试函数前和后执行。

然后是类内的执行顺序,运行的优先级:setup_class(粉色)>>setup_method>(蓝色)setup (青色) >用例>teardown(青色)>teardown_method(蓝色)>>teardown_class(粉色)。

接下来讲下重点-pytest的装饰器:

fixture:

fixture可以当做参数传入,使用方法为在函数上加个装饰器@pytest.fixture(),fixture是有返回值的,如果没有返回值则默认为None。

新建一个文件取名test_fixture.py,内容如下:

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 21:12
#@Author: 怪盗LLYL
#@File: test_fixture.py
#@Software: PyCharm
import pytest
@pytest.fixture()
def function_fixture():
    return "function_fixture"
def test_fixture(function_fixture):
    assert function_fixture=="function_fixture"
if __name__ == '__main__':
    pytest.main(["-v","-s","test_fixture.py"])

多个fixture的使用:

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 21:12
#@Author: 怪盗LLYL
#@File: test_fixture.py
#@Software: PyCharm
import pytest
@pytest.fixture()
def function_fixture():
    return "function_fixture"
@pytest.fixture()
def function_fixture2():
    return "function_fixture2"
def test_fixture(function_fixture,function_fixture2):
    print("n第一个参数:" function_fixture)
    print("n第二个参数:" function_fixture2)
if __name__ == '__main__':
    pytest.main(["-v","-s","test_fixture.py"])

参数的使用,示例如下:

代码语言:javascript复制
@pytest.fixture(scope='module',params=None, autouse=False, ids=None, name=None)
def init():
    print("开始执行测试用例")
    a,b=1,2
    yield a,b
    print("结束执行测试用例")
参数说明:
代码语言:javascript复制
scope:作用域,"function"(默认),"class","module","session"四个。
params:可选参数列表,它将导致多个参数调用fixture函数和所有测试使用它。
autouse:默认:False,需要用例手动调用该fixture;如果是True,所有作用域内的测试用例都会自动调用该fixture
ids:params测试ID的一部分。如果没有将从params自动生成.
name:默认:装饰器的名称,同一模块的fixture相互调用建议写个不同的name。
session的作用域:是整个测试会话,即开始执行pytest到结束测试

fixture的作用范围scope:

代码语言:javascript复制
fixture里面有个scope参数可以控制fixture的作用范围:session>module>class>function-function:每一个函数或方法都会调用-class:每一个类调用一次,一个类中可以有多个方法-module:每一个.py文件调用一次,该文件内又有多个function和class-session:是多个文件调用一次,可以跨.py文件调用,每个.py文件就是modul

修改我们的文件:验证无返回值时默认为None,scope="function和不设置scope相同效果。

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 21:12
#@Author: 怪盗LLYL
#@File: test_fixture.py
#@Software: PyCharm
import pytest
@pytest.fixture(scope="function")
def function_fixture():
    return "function_fixture"
@pytest.fixture(scope="function")
def function_fixture2():
    print("function_fixture2")
def test_fixture(function_fixture,function_fixture2):
    print("n第一个参数:" function_fixture)
class Testclass(object):
    def test_defaultScope2(self, function_fixture2):
        print('nfunction_fixture2,被调用,无返回值时,默认为None')
        assert function_fixture2 == None
if __name__ == '__main__':
    pytest.main(["-v","-s","test_fixture.py"])

scope="class":

fixture为class级别的时候,如果一个class里面有多个用例,都调用了次fixture,那么此fixture只在此class里所有用例开始前执行一次。

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 21:12
#@Author: 怪盗LLYL
#@File: test_fixture.py
#@Software: PyCharm
import pytest
@pytest.fixture(scope="function")
def function_fixture():
    return "function_fixture"
@pytest.fixture(scope="function")
def function_fixture2():
    print("nfunction_fixture2")
@pytest.fixture(scope="class")
def scope_class():
    print("n输出scope_class")
    return "scope_class"
def test_fixture(function_fixture,function_fixture2):
    print("n第一个参数:" function_fixture)
class Testclass(object):
    def test_defaultScope2(self, function_fixture2):
        print('nfunction_fixture2,被调用,无返回值时,默认为None')
        assert function_fixture2 == None
    def test_defaultScope3(self, scope_class):
        print("ntest_defaultScope3方法获取scope_class值:" scope_class)
        assert scope_class == "scope_class"
    def test_defaultScope4(self, scope_class):
        print("ntest_defaultScope4方法获取scope_class值:" scope_class)
        assert scope_class == "scope_class"
if __name__ == '__main__':
    pytest.main(["-v","-s","test_fixture.py"])

test_defaultScope3方法和test_defaultScope4方法都获取到了scope_class的返回值,但是scope_class的输出语句只有一条日志,说明只执行了一次。

scope="module":

fixture为module时,在当前.py脚本里面所有用例开始前只执行一次。

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 21:12
#@Author: 怪盗LLYL
#@File: test_fixture.py
#@Software: PyCharm
import pytest
@pytest.fixture(scope="function")
def function_fixture():
    return "function_fixture"
@pytest.fixture(scope="function")
def function_fixture2():
    print("nfunction_fixture2")
@pytest.fixture(scope="class")
def scope_class():
    print("n输出scope_class")
    return "scope_class"
@pytest.fixture(scope="module")
def scope_module():
    print("n自己输出scope_module")
    return "module"
def test_fixture(function_fixture,function_fixture2,scope_module):
    print(scope_module)
    print("n第一个参数:" function_fixture)
class Testclass(object):
    def test_defaultScope2(self, function_fixture2):
        print('nfunction_fixture2,被调用,无返回值时,默认为None')
        assert function_fixture2 == None
    def test_defaultScope3(self, scope_class,scope_module):
        print("ntest_defaultScope3方法获取scope_class值:" scope_class)
        print("ntest_defaultScope3:输出" scope_module)
        assert scope_class == "scope_class"
    def test_defaultScope4(self, scope_class,scope_module):
        print("ntest_defaultScope4方法获取scope_class值:" scope_class)
        print("ntest_defaultScope4:输出" scope_module)
        assert scope_class == "scope_class"
if __name__ == '__main__':
    pytest.main(["-v","-s","test_fixture.py"])
scope="session"

fixture为session,允许跨.py模块调用,通过conftest.py 共享fixture。 也就是当我们有多个.py文件的用例的时候,如果多个用例只需调用一次fixture也是可以实现的。必须以conftest.py命名,才会被pytest自动识别该文件。放到项目的根目录下就可以全局调用了,如果放到某个package下,那就在该package内有效。

创建一个conftest.py文件,内容如下:

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 22:19
#@Author: 怪盗LLYL
#@File: conftest.py
#@Software: PyCharm
import pytest
@pytest.fixture(scope='session')
def mysession():
    str = 'mysession'
    print(f'自己输出:{str}')
    return str
if __name__ == '__main__':
    mysession()

创建测试脚本test_session1.py,示例代码如下:

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 22:25
#@Author: 怪盗LLYL
#@File: test_session1.py
#@Software: PyCharm
import pytest
def testScope1(mysession):
    print(f"test1{mysession}")
    assert mysession == 'mysession'

创建测试脚本test_session2.py,示例代码如下:

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 22:25
#@Author: 怪盗LLYL
#@File: test_session2.py
#@Software: PyCharm
import pytest
def testScope2(mysession):
    print(f"test2{mysession}")
    assert mysession == 'mysession'

python

然后同时执行两个文件,输入命令:

shell

代码语言:javascript复制
pytest -s -k test_sessio

可以看到被两个py文件调用只执行了一次。

一个项目下可以有多个conftest.py文件,在根目录下设置的conftest文件可以全局使用。而在不同子目录下放conftest.py的文件,可以在当前目录及子目录下使用,不能跨模块调用。

fixture的params:

@pytest.fixture有一个params参数,接受一个列表,列表中每个数据都可以作为用例的输入。也就说有多少数据,就会形成多少用例,具体示例代码如下:新建文件test_params.py。

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 22:47
#@Author: 怪盗LLYL
#@File: test_params.py
#@Software: PyCharm
import pytest

list=['1','2','3']
class Test_params(object):
    @pytest.fixture(params=list)
    def myparams(self,request):
    # request用来接收param列表数据
        return request.param
    def test_params(self,myparams):
        print(f"n输出:{myparams}")
        assert '1' == myparams

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

分别返回了"1","2","3"执行了三次用例。正常情况params参数不能动态取值,如果需要动态取值,可以采用在params放入函数的方法解决。

autouse:

默认:False,需要用例手动调用该fixture;如果是True,所有作用域内的测试用例都会自动调用该fixture。

新建文件test_autouse.py,内容如下。

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 23:07
#@Author: 怪盗LLYL
#@File: test_autouse.py
#@Software: PyCharm
import pytest

@pytest.fixture(scope='function',autouse=True)
def func_scope():
    print("n函数级别的fixtue")
@pytest.fixture(scope='module',autouse=True)
def module_scope():
    print("n模块级别的fixtue")
@pytest.fixture(scope='session',autouse=True)
def session_scope():
    print("n会话级别的fixtue")
@pytest.fixture(scope='class',autouse=True)
def class_scope():
    print("n类级别的fixtue")

def test_func_01():
    print("n执行函数一")
def test_func_02():
    print("n执行函数2")
class Test_class():
    def test_func_03(self):
        print("n执行类函数3")
    def test_func_04(self):
        print("n执行类函数4")
if __name__ == '__main__':
    pytest.main(["-v","-s","test_autouse.py"])

可以看到自动调用了参数的输出。

fixture的调用:

除了上面讲的作为参数调用,自动调用还可以在要测试的用例加上装饰器:@pytest.mark.usefixtures(fixture_name)。

修改我们的test_params.py文件:

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 22:47
#@Author: 怪盗LLYL
#@File: test_params.py
#@Software: PyCharm
import pytest

list=['1','2','3']
class Test_params(object):
    # @pytest.fixture(params=list)
    # def myparams(self,request):
    # # request用来接收param列表数据
    #     return request.param
    # def test_params(self,myparams):
    #     print(f"n输出:{myparams}")
    #     assert '1' == myparams

    @pytest.fixture
    def myfixture(self):
        print("usefixtures调用")
    @pytest.mark.usefixtures("myfixture")
    def test_case3(self):
        print("n测试用例3")

if __name__ == '__main__':
    pytest.main(["-v","-s","test_params.py"])
知识点:
代码语言:javascript复制
出自:https://www.cnblogs.com/poloyy/p/12642602.html
在类声明上面加 @pytest.mark.usefixtures() ,代表这个类里面所有测试用例都会调用该fixture
可以叠加多个 @pytest.mark.usefixtures() ,先执行的放底层,后执行的放上层
可以传多个fixture参数,先执行的放前面,后执行的放后面
如果fixture有返回值,用 @pytest.mark.usefixtures() 是无法获取到返回值的,必须用传参的方式(方式一)将fixture名称作为测试用例函数的输入参数
fixture的实例化顺序
较高 scope 范围的fixture(session)在较低 scope 范围的fixture( function 、 class )之前实例化【session > package > module > class > function】
具有相同作用域的fixture遵循测试函数中声明的顺序,并遵循fixture之间的依赖关系【在fixture_A里面依赖的fixture_B优先实例化,然后到fixture_A实例化】
自动使用(autouse=True)的fixture将在显式使用(传参或装饰器)的fixture之前实例化
代码语言:javascript复制
# -*- codeing = utf-8 -*-
# @Time: 2021/8/29 22:47
# @Author: 怪盗LLYL
# @File: test_params.py
# @Software: PyCharm
import pytest
order = []
@pytest.fixture(scope="session")
def s1():
    print("n实例化session")
    order.append("s1")
@pytest.fixture(scope="module")
def m1():
    print("n实例化module")
    order.append("m1")
@pytest.fixture
def f1(f3, a1):
    # 先实例化f3, 再实例化a1, 最后实例化f1
    order.append("f1")
    assert f3 == 123
@pytest.fixture
def f3():
    print("n实例化f3")
    order.append("f3")
    a = 123
    yield a
@pytest.fixture
def a1():
    print("n实例化a1")
    order.append("a1")
@pytest.fixture
def f2():
    print("n实例化f2")
    order.append("f2")
def test_order(f1, m1, f2, s1):
    # m1、s1在f1后,但因为scope范围大,所以会优先实例化
    assert order == ["s1", "m1", "f3", "a1", "f1", "f2"]
if __name__ == '__main__':
    pytest.main(["-v", "-s", "test_params.py"])

fixture之yield实现teardown:

fixture里面的teardown,可以用yield来唤醒teardown的执行,新建文件test_yield.py。

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 23:41
#@Author: 怪盗LLYL
#@File: test_yield.py
#@Software: PyCharm
import pytest
@pytest.fixture(scope="module")
def mymodule():
    print("n开始测试准备数据")
    test = "测试变量是否返回"
    yield test
    print("n测试结束清理数据")
@pytest.fixture
def myfunction(mymodule):
    # 方法级别前置操作setup
    print(f"myfunction方法开始输出:{mymodule}")
    a = "a"
    b = "b"
    # 返回变量a,b給test_s1函數
    yield a, b
    # 方法级别后置操作teardown
    print("myfunction方法结束")
def test_s1(myfunction):
    print("==用例1==")
    # 返回的是一个元组
    print(myfunction)
    # 分别赋值给不同变量
    a, b = myfunction
    print(a, b)
    assert "a" in a
    assert "b" in b
def test_s2(myfunction):
    a, b = myfunction
    print("==用例2==")
    print(a, b)
if __name__ == '__main__':
    pytest.main(["-v", "-s", "test_yieldpy"])

yield注意事项

如果yield前面的代码,即setup部分已经抛出异常了,则不会执行yield后面的teardown内容(存疑)

如果测试用例抛出异常,yield后面的teardown内容还是会正常执行

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/29 23:41
#@Author: 怪盗LLYL
#@File: test_yield.py
#@Software: PyCharm
import pytest
@pytest.fixture(scope="module")
def mymodule():
    print("n开始测试准备数据")
    test = "测试变量是否返回"
    yield test
    print("n测试结束清理数据")
@pytest.fixture
def myfunction(mymodule):
    # 方法级别前置操作setup
    print(f"myfunction方法开始输出:{mymodule}")
    a = "a"
    b = "b"
    # 返回变量a,b給test_s1函數
    yield a, b
    # 方法级别后置操作teardown
    print("myfunction方法结束")
def test_s1(myfunction):
    print("==用例1==")
    raise Exception
    a,b = myfunction
    # raise Exception
def test_s2(myfunction):
    a, b = myfunction
    print("==用例2==")
    print(a, b)
if __name__ == '__main__':
    pytest.main(["-v", "-s", "test_yieldpy"])

yield with的结合:

代码语言:javascript复制
# 官方例子
@pytest.fixture(scope="module")
def smtp_connection():
    with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
        yield smtp_connection  # provide the fixture value

该smtp_connection 连接将测试完成执行后已经关闭,因为 smtp_connection 对象自动关闭时, with 语句结束。

addfinalizer函数:

我们也可以通过request.addfinalizer()的方式实现“teardown”。

代码语言:javascript复制
#-*- codeing = utf-8 -*-
#@Time: 2021/8/30 0:19
#@Author: 怪盗LLYL
#@File: test_addfinalizer.py
#@Software: PyCharm
import pytest

@pytest.fixture(scope="module")
def test_addfinalizer(request):
    # 前置操作setup
    print("n==测试开始准备数据==")
    test = "返回值"
    def fin():
        # 后置操作teardown
        print("n==测试结束清洗数据==")
    request.addfinalizer(fin)
    # 返回前置操作的变量
    return test
def test_anthor(test_addfinalizer):
    print("==开始测试输出:==", test_addfinalizer)

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

今天就写到这里了,最近比较忙下次大概会更新pytest加Allure生成测试报告,其实最开始接触单元测试用的unittest HTMLtestrunner所以一直想用pytest加HTMLtestrunner,不过找不到学习资料遂放弃。测试报告之后应该会写下appium环境搭建,最近工作用到,再之后jmeter环境搭建,或者docker Jenkins Django部署或者gitlab Django部署。主要看有没有时间吧,没空就穿插爬虫小程序或者操作图片视频之类模块做的小工具分享下。

0 人点赞