自动化测试-pytest框架-进阶
在我们进行使用某一个接口的时候,会发现有一些接口会有一些前置条件或者是后置条件,俗称上下游,比如在使用图书管理系统的查询图书接口之前,我们要先进行登录,或者说在借阅一本书之前要先去查这本书是否还有剩余,这样的一些额外的操作可以保证我们流程正常的运行,模拟用户最真实的使用步骤,确保测试的真实性
pytest框架有以下三种设置前后置的方法:
分别使用
setup_method和teardown_method进行对方法的前后置操作
import pytestclass TestFacing:def setup_method(self):print("这是前置操作")def test_1(self):print("测试用例1")def test_2(self):print("测试用例2")def teardown_method(self):执行代码之后,可以看到两个用例都分别输出了前后置:

分别使用 setup_class 和 teardown_class 对整个测试类进行前后置操作
import pytestclass TestFacing:# 使用类前后置def setup_class(self):print("这是类前置操作")def teardown_class(self):print("这是类后置操作")def test_1(self):print("测试用例1")def test_2(self):print("测试用例2")执行代码之后,可以看到只有在整个运行流程的开头和结尾输出了前后置:

使用
fixture,这是pytest推荐的实现测试用例前后置的操作
断言
断言(assert)是一种辅助测试的工具,用来检查程序的运行结果是否符合预期.如果断言失败(即条件为假,结果不符合期待值),python解释器就会抛出一个 AssertionError 异常.
pytest 允许你在 Python 测试中使用标准的 Python assert 语句来验证预期和值。
# 条件语句必须是一个布尔表达式,报错信息时断言失败会显示的错误信息
assert 条件语句,报错信息(非必要)例子:
基本数据类型的断言
import pytestclass Test:def test_1(self):a = 1b = 1assert a == bdef test_2(self):a = 1b = 2assert a == b , "两个数据不相等!"
数据结构断言
class Test:def test1(self):print("断言列表")a = [1,"test",3]b = [1,"test",3]assert a == bdef test2(self):print("断言元组")a = ("test",4,5)b = ("test",4,5)assert a == bdef test3(self):print("断言字典")a = {"test1":1,"test":2}b = {"test1":1,"test":2}assert a == bdef test4(self):print("断言集合")a = {4,"test6",89}b = {4,"test6",89}assert a == b
函数断言
def get_result(a,b):assert a!=0return a/bclass Test:def test1(self):print(get_result(4, 2))def test2(self):print(get_result(0, 2))
接口返回值断言
有一个免费的API可以用来进行测试学习: https://jsonplaceholder.typicode.com/
断言整个返回值
import requestsclass Test:def test1(self):print("断言接口返回值-完整字段/值")url = "https://jsonplaceholder.typicode.com/posts/1"actual_val = requests.get(url=url).json()export_val = {"userId": 1,"id": 1,"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit","body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}assert actual_val == export_val
断言返回值的重要字段
import requestsclass Test:def test(self):url = "https://jsonplaceholder.typicode.com/posts"actual_val = requests.get(url=url).json()for i in range(1,5):assert i == actual_val[i-1]["id"]
断言接口html返回值
import requestsclass Test:def test1(self):print("期望值存在,断言成功")url = "https://jsonplaceholder.typicode.com/"actual_val = requests.get(url=url).textassert "Use your own data" in actual_valdef test2(self):print("期望值不存在,断言失败")url = "https://jsonplaceholder.typicode.com/"actual_val = requests.get(url=url).textassert "Ue yur on data" in actual_val

参数化
参数化是自动化测试的一个重要组成部分,通过定义设计参数和规则,让测试流程更加灵活可控
通过使用pytest内置的 pytest.mark.parametrize 装饰器来对测试函数的参数进行参数化
方法参数化
import pytestclass Test:@pytest.mark.parametrize("input,except_val",[("8+7",15),("8*7",56)])def test(self,input,except_val):assert eval(input) == except_val使用装饰器定义了两组不同的元组,并作为参数进行传递,测试函数使用参数运行了两次,分别判断不同的参数元组

类参数化
除了对某一个测试方法进行参数化,我们还可以对全局进行参数化
import pytest@pytest.mark.parametrize("input,except_val",[(1,2),(3,6),(5,10)])
class Test:def test1(self,input,except_val):assert input*2 == except_valdef test2(self,input,except_val):assert input*input < except_val*except_val
自定义参数化数据源
import pytestdef get_result():return ["a","b"]class Test:@pytest.mark.parametrize("data",get_result())def test(self,data):assert data is not Nonedata += "c"print(f"data: {data}")
Fixture
Fixture 是pytest的一种机制,用于提供测试函数所需要的资源(参数化)以及处理前后置操作.用于测试环境和数据的准备
函数调用
没有进行标记的fixture方法和标记之后的方法的调用完全不一样,前者需要在方法体中进行调用,后者可以将函数名作为参数进行调用
如果在测试脚本中存在的很多重复或者公共的代码以及数据对象时,可以使用fixture
没有标记的方法调用
def fixture():print("这是调用的fixture方法")class Test:def test(self):fixture()print("测试结束")
标记的方法调用
import pytestclass Test:@pytest.fixturedef fixture(self):print("这是调用的fixture方法")def test(self,fixture):print("测试结束")
前置操作
可以使用fixture来进行前置的设置操作
我们先进行了登录操作,然后在进行查询操作,这样才能进行借书操作,而借书操作就是我们想要测试的接口
import pytestclass Test:@pytest.fixturedef login(self):print("登录操作")@pytest.fixturedef search(self,login):print("查询操作")def test1(self,search):print("成功借书,<book1>")def test2(self,search):print("成功借书,<book2>")
fixture嵌套
在测试当中,并不局限于只是用单个的fixture,我们可以使用任意数量的fixture来得到自己想要的测试效果,并且fixture方法也可以使用其他的fixture方法
import pytestclass Test:@pytest.fixturedef fixture_1(self):return ["a"]@pytest.fixturedef fixture_2(self,fixture_1):return fixture_1+["b"]def test(self,fixture_2):fixture_2 = fixture_2+["c"]except_val = ["a","b","c"]assert except_val == fixture_2print("断言成功")
请求多个fixture
我们也可以一次性使用多个fixture
import pytestclass Animals:def __init__(self,name):self.name = namedef __eq__(self, other):return self.name == other.nameclass Test:@pytest.fixturedef fixture_1(self):return Animals("cat")@pytest.fixturedef fixture_2(self):return Animals("dog")@pytest.fixturedef create_zoo(self,fixture_1,fixture_2):return [fixture_1,fixture_2,Animals("wolf")]def test(self,fixture_1,create_zoo):assert fixture_1 in create_zooprint("断言成功")
yield fixture
进行测试的时候,我们并不希望当前运行的测试用例影响到其他的测试用例,所以就需要用例进行自我清洁,所以fixture有一个拆卸系统
而这里并不能使用 return 来进行清理,同时fixture的所有拆卸代码都是放在 yield 之后
当确定了 fixture 的线性顺序,将运行的每个 fixture 直到他返回 yield ,然后继续执行列表中的下一个 fixture 做同样的操作.测试结束之后, pytest 就会逆向遍历 fixture 列表,对于每个 yield 的 fixture ,运行 yield 语句之后的代码
前后置操作
import pytestclass Test:@pytest.fixturedef login_logout(self):print("前置操作,登录中……")yieldprint("后置操作,注销中……")def test(self,login_logout):print("系统登录成功!")
创建文件句柄+关闭文件
import pytestclass Test:@pytest.fixturedef open_close(self):print("打开文件")file = open(r"C:\测试\接口自动化\ApiAutoTest01\test_module\fixtureUse\text.txt", "r", encoding="utf-8")yield filefile.close()print("文件关闭")@pytest.fixturedef file_write(self):print("进行文件内部操作")file = open(r"C:\测试\接口自动化\ApiAutoTest01\test_module\fixtureUse\text.txt", "w",encoding="utf-8")return filedef test(self,open_close,file_write):file1 = file_writefile1.write("进行测试!!!")file1.close()file2 = open_closestr = file2.read(20)print(f"文件内容:{str}")
参数化
格式:
@pytest.fixture(scope='',params='',autouse='',ids='',name='')参数介绍:
scope参数用于控制 fixture 的作用范围,决定了 fixture 的生命周期。可选值有:function(默认):每个测试函数都会调用一次 fixture。class:在同一个测试类中共享这个 fixture。module:在同一个测试模块中共享这个 fixture。(一个文件里)session:整个测试会话中共享这个 fixture。
autouse参数默认为False。如果设置为True,则每个测试函数都会自动调用该 fixture,无需显式传入params参数用于参数化 fixture,支持列表传入。每个参数值都会使 fixture 执行一次,类似于 for 循环ids参数与params配合使用,为每个参数化实例指定可读的标识符(给参数取名字)name参数用于为 fixture 显式设置一个名称。如果使用了name,则在测试函数中需要使用这个名称来引用 fixture(给 fixture 取名字)
scope使用
除开 module 和 session 需要多个测试文件进行配合,构造全局的场景以外,其余两个都是可以单独进行使用的
Function
import pytestclass Test:@pytest.fixture(scope='function')def init(self):print("初始化……")yieldprint("注销中……")def test1(self,init):print("测试用例1")def test2(self,init):print("测试用例2")
Class
import pytestclass Test:@pytest.fixture(scope='class')def init(self):print("初始化……")yieldprint("注销中……")def test1(self, init):print("测试用例1")def test2(self, init):print("测试用例2")
全局前后置
需要配合 conftest.py 文件一起使用,这个文件是固定的名字,在这个文件中进行前后置的配置
可以在不同的目录下创建多个文件,每个文件都是对其所在的文件以及子文件生效
module
# conftest.py
import pytest@pytest.fixture(scope='module',autouse=True)
def init():print("正在初始化……")yieldprint("正在注销中……")# 代码1
class Test:def test1(self):print("测试系统1的用例1")def test2(self):print("测试系统1的用例2")# 代码2
def test():print("这是额外的测试用例")class Test:def test1(self):print("测试系统2的用例1")def test2(self):print("测试系统2的用例2")
Session
# conftest.py
import pytest@pytest.fixture(scope='session',autouse=True)
def init():print("正在初始化……")yieldprint("正在注销中……")# 代码1
class Test:def test1(self):print("测试系统1的用例1")def test2(self):print("测试系统1的用例2")# 代码2
def test():print("这是额外的测试用例")class Test:def test1(self):print("测试系统2的用例1")def test2(self):print("测试系统2的用例2")
autouse使用
autouse 默认为 false ,也就是当前的 fixture 需要手动显示调用,而当 autouse=True 时, fixture 会在测试函数执行之前进行自动调用,无论测试函数时候显示的使用了 fixture
import pytestclass Test:@pytest.fixture(scope='class',autouse=True)def init(self):print("正在进行初始化……")yieldprint("正在进行注销……")def test1(self):print("测试用例1")def test2(self):print("测试用例2")
params使用
可以使用 params 进行参数化,我们需要使用 request 来继续取值(变量名是固定的)
import pytestclass Test:@pytest.fixture(params=["a","b"])def get_result(self,request):return request.paramdef test(self,get_result):assert get_result is not Noneprint(f"结果为:{get_result}")
