文章目录
- 2.接口自动化测试
-
- [2.5 pytest](#2.5 pytest)
-
- [2.5.6 断言](#2.5.6 断言)
- [2.5.7 参数化](#2.5.7 参数化)
- [2.5.8 fixture](#2.5.8 fixture)
-
- [2.5.8.1 基本使用](#2.5.8.1 基本使用)
- [2.5.8.2 fixture嵌套](#2.5.8.2 fixture嵌套)
- [2.5.8.3 请求多个fixture](#2.5.8.3 请求多个fixture)
- [2.5.8.4 yield fixture](#2.5.8.4 yield fixture)
2.接口自动化测试
2.5 pytest
2.5.6 断言
断言(
assert)是一种调试辅助工具,用于检查程序的状态是否符合预期。如果断言失败(即条件为假),Python解释器将抛出一个AssertionError异常。断言通常用于检测程序中的逻辑错误。
pytest允许你在Python测试中使用标准的Python assert语句来验证预期和值。
基本语法:
assert 条件, 错误信息
- 条件 :必须是一个布尔表达式。
- 错误信息 :当条件为假时显示的错误信息,可选。
免费学习API资源:http://jsonplaceholder.typicode.com/
示例1:基本数据类型的断言
python
#断言整数
a = 1
b = 2
assert a == b
#断言字符串
str = "hello"
assert "hello" == str
示例2:数据结构断言
python
def test():
# 断言列表
expect_list = [1, 'apple', 3.14]
actual_list = [1, 'apple', 3.14]
# 断言元组
expect_tuple = (1, 'apple', 3.14)
actual_tuple = (1, 'apple', 3.14)
# 断言字典
expect_dict = {'name': 'Alice', 'age': 25}
actual_dict = {'name': 'Alice', 'age': 25}
# 断言集合
expect_set = {1, 2, 3, 'apple'}
actual_set = {1, 2, 3, 'apple'}
assert expect_list == actual_list
assert expect_tuple == actual_tuple
assert expect_dict == actual_dict
assert expect_set == actual_set
运行:

示例3:函数断言
python
def divide(a, b):
assert b != 0, "除数不能为0"
return a / b
# 正常情况
print(divide(10, 2)) # 输出 5.0
# 触发断言
print(divide(10, 0)) # 抛出 AssertionError: 除数不能为0
打印:

示例4:接口返回值断言
python
import requests
# 断言接口返回值完整字段和值
def test1():
url = "http://jsonplaceholder.typicode.com/posts/1"
r = requests.get(url=url)
expect_data = {
"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"
}
print(r.json())
assert r.json() == expect_data
assert r.json()['userId'] == 1
# 断言接口返回值重要字段
def test2():
url = "http://jsonplaceholder.typicode.com/comments?postId=1"
r = requests.get(url=url)
print(r.json())
assert r.json()[0]['id'] == 1
# 断言接口html返回值
def test3():
url = "http://jsonplaceholder.typicode.com/"
r = requests.get(url=url)
assert "Use your own data" in r.text
运行:

2.5.7 参数化
参数化设计是自动化设计中的一个重要组成部分,它通过定义设计参数和规则,使得设计过程更加灵活和可控。
pytest中内置的pytest.mark.parametrize装饰器允许对测试函数的参数进行参数化。
示例1:在用例上使用参数化
python
import pytest
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6),
("6*9", 42)])
def test_eval(test_input, expected):
assert eval(test_input) == expected
打印:

这里,
@parametrize装饰器定义了三个不同的(test_input,expected)元组,以便test_eval函数将依次使用它们运行三次。
也可以在类或模块上使用
parametrize标记,这将使用参数集调用多个函数
示例2:在类上使用参数化
python
import pytest
@pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
def test_simple_case(self, n, expected):
assert n + 1 == expected
def test_weird_simple_case(self, n, expected):
assert (n * 1) + 1 == expected
打印:

如果要对模块中的所有测试进行参数化,可以将
pytestmark全局变量赋值:
python
import pytest
pytestmark = pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
def test_simple_case(self, n, expected):
assert n + 1 == expected
def test_weird_simple_case(self, n, expected):
assert (n * 1) + 1 == expected
打印:

除了使用
@parametrize添加参数化外,pytest.fixture()允许对fixture函数进行参数化。
示例3:自定义参数化数据源
python
def data_provider(): # 一个普通的Python函数,返回一个列表 ["a", "b"]作为测试数据的数据源
return ["a", "b"]
# 定义一个测试函数,它依赖于上面函数的返回值
@pytest.mark.parametrize("data", data_provider())
'''
这是pytest的参数化装饰器
"data":参数名(测试函数的参数)
data_provider():数据源(返回列表 ["a", "b"])
'''
def test_data(data):
assert data != None
print(f"Testing with data provider: {data}")
'''
接受一个参数 data
执行断言 assert data != None
打印测试信息
'''
运行:

2.5.8 fixture
pytest中的fixture是一种强大的机制,用于提供测试函数所需的资源或上下文。它可以用于设置测试环境、准备数据等。以下是
fixture的一些核心概念和使用场景
2.5.8.1 基本使用
示例1:使用与不使用fixture标记
python
def fixture_01():
print("第一个fixture标记的方法")
def test_01():
fixture_01()
print("第一个测试用例")
打印:

fixture标记的方法调用
python
import pytest
@pytest.fixture
def fixture_01():
print("第一个fixture标记的方法")
def test_01(fixture_01):
print("第一个测试用例")
打印:

虽然看起来结果一模一样,但是执行机制完全不同
代码1的执行流程:
- 调用
test_01()- 在
test_01()内部调用fixture_01()- 执行
fixture_01()的print- 返回继续执行
test_01()的print代码2的执行流程:
- pytest发现
test_01需要fixture_01- pytest执行
fixture_01(作为setup)- 执行
fixture_01的print- 执行
test_01的print
前者需要在方法体中调用,而后者可以将函数名作为参数进行调用。测试脚本中存在的很多重复的代码、公共的数据对象时,使用
fixture最为合适
示例2:访问列表页和详情页之前都需要执行登录操作
python
import pytest
@pytest.fixture
def login():
print("---执行登陆操作-----")
def test_list(login):
print("---访问列表页")
def test_detail(login):
print("---访问详情页")
打印:

通过使用
@pytest.fixture装饰器来告诉pytest一个特定函数是一个fixture,通过运行结果可见,在执行列表页和详情页之前都会先执行login方法。
2.5.8.2 fixture嵌套
代码:
python
import pytest
# 安排
@pytest.fixture
def first_entry():
return "a"
# 安排
@pytest.fixture
def order(first_entry):
return [first_entry]
def test_string(order):
# 行动
order.append("b")
# 断言
assert order == ["a", "b"]
打印:

测试不必局限于单个
fixture,它们可以依赖于自己想要的任意数量的fixture,并且fixture也可以使用其他fixture。
pytest最伟大的优势之一是其极其灵活的fixture系统,它允许我们将测试的复杂需求简化为更简单和有组织的函数,我们只需要每个函数描述它们所依赖的事物。
2.5.8.3 请求多个fixture
测试和
fixture不仅限于一次请求单个fixture,它们可以请求任意多个。
python
import pytest
class Fruit:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
@pytest.fixture
def my_fruit():
return Fruit("apple")
@pytest.fixture
def fruit_basket(my_fruit):
return [Fruit("banana"), my_fruit]
def test_my_fruit_in_basket(my_fruit, fruit_basket):
assert my_fruit in fruit_basket
打印:

2.5.8.4 yield fixture
当我们运行测试时,我们希望确保它们能够自我清理,以便它们不会干扰其他测试(同时也避免留下大量测试数据来膨胀系统)。
pytest中的fixture提供了一个非常有用拆卸系统,它允许我们为每个fixture定义具体的清理步骤。
"Yield" fixture使用yield而不是return。有了这些fixture,我们可以运行一些代码,并将对象返回给请求的fixture/test,就像其他fixture一样。唯一的不同是:
return被替换为yield。- 该
fixture的任何拆卸代码放置在yield之后。
一旦pytest确定了fixture的线性顺序,它将运行每个fixture直到它返回或yield,然后继续执行列表中的下一个fixture做同样的事情。测试完成后,
pytest将逆向遍历fixture列表,对于每个yield的fixture,运行yield语句之后的代码。
示例1:
python
import pytest
@pytest.fixture
def open_close():
print("前置操作,初始化.....")
yield
print("后置操作,清理数据.....")
def test_01(open_close):
print("第一个测试用例")
打印:

示例2:创建文件句柄与关闭文件
python
import pytest
@pytest.fixture
def file_read():
print("打开文件句柄")
fo = open("test.txt", "r", encoding="utf-8")
yield fo
print("关闭打开的文件")
fo.close()
@pytest.fixture
def file_write():
print("打开文件句柄")
fo = open("test.txt", "w", encoding="utf-8")
return fo
# yield fo
#
# print("关闭文件句柄")
# fo.close()
def test_file(file_write, file_read):
# 写入数据
w = file_write
w.write("测试数据")
w.close() # 写入后关闭文件句柄,以便读取
# 读取数据
r = file_read
str = r.read(10)
print("文件内容:", str)
运行:

代码2:
python
import pytest
@pytest.fixture
def file_read():
print("打开文件句柄")
fo = open("test.txt", "r", encoding="utf-8")
yield fo
print("关闭打开的文件")
fo.close()
@pytest.fixture
def file_write():
print("打开文件句柄")
fo = open("test.txt", "w", encoding="utf-8")
# return fo
yield fo
print("关闭文件句柄")
fo.close()
def test_file(file_write, file_read):
# 写入数据
w = file_write
w.write("测试数据")
w.close() # 写入后关闭文件句柄,以便读取
# 读取数据
r = file_read
str = r.read(10)
print("文件内容:", str)
运行:

两个代码的区别是:
python
第一个代码:
return fo
第二个代码:
yield fo # 返回文件对象,暂停
# 测试执行...
print("关闭文件句柄") # 测试结束后执行
fo.close() # 测试结束后执行
第一个代码:错误
python
打开文件句柄 # file_write setup
打开文件句柄 # file_read setup
文件内容: 测试数据 # 测试执行
# 没有teardown执行!文件句柄可能未关闭
第二个代码:正确
python
打开文件句柄 # file_write setup
打开文件句柄 # file_read setup
文件内容: 测试数据 # 测试执行
关闭文件句柄 # file_write teardown
关闭打开的文件 # file_read teardown