⾃动化框架pytest
⽀持Python语⾔的接⼝⾃动化框架有很多,以下是⽀持Python的接⼝⾃动化主流框架对⽐分析:
主流框架对⽐表:

pytest介绍
pytest官⽅⽂档:https://docs.pytest.org/en/stable/getting-started.html
pytest 是⼀个⾮常流⾏且⾼效的Python测试框架,它提供了丰富的功能和灵活的⽤法,使得编写和 运⾏测试⽤例变得简单⽽⾼效。
pytest的优点:
简单易⽤: pytest 的语法简洁清晰,对于编写测试⽤例⾮常友好,⼏乎可以在⼏分钟内上⼿。 • 强⼤的断⾔库: pytest 内置了丰富的断⾔库,可以轻松地进⾏测试结果的判断。
⽀持参数化测试: pytest ⽀持参数化测试,允许使⽤不同的参数多次运⾏同⼀个测试函数,这 ⼤ 提⾼了测试效率。
丰富的插件⽣态系统: pytest 有着丰富的插件⽣态系统,可以通过插件扩展各种功能,⽐如覆 盖率测试b、测试报告⽣成(如 pytest-html 插件可以⽣成完美的HTML测试报告)、失败⽤例重 复执⾏(如 pytest-rerunfailures 插件)等。此外, pytest 还⽀持与selenium、 requests、appinum等结合,实现Web⾃动化、接⼝⾃动化、App⾃动化测试。
灵活的测试控制: pytest 允许跳过指定⽤例,或对某些预期失败的case标记成失败,并⽀持重 复执⾏失败的case。
pytest的安装
安装 pytest 8.3.2 要求 python 版本在3.8及以上。
cpp
pip install pytest==8.3.2

可以使用pytest就算安装成功,根据python版本的不同可能安装pytest的版本也需要不同
安装好 pytest 后,确认pycharm中python解释器已经更新,来看⼀下有 pytest 框架和没有 pytest 框架编写代码的区别:

未安装pytest框架的情况下需要编写 main 函数,在 main 函数中⼿动 调⽤测试⽤例test01;安装了 pytest 框架后⽅法名前有直接运⾏标志。
注意:并不是所有的⽅法都可以直接运⾏,需要遵循 pytest 中的⽤例命名规则。
使用pytest的命名规则
- ⽂件名必须以 test_ 开头 或者 _test 结尾
- 测试类必须以 Test 开头 ,并且不能有 init ⽅法
- 测试⽅法必须以 test 开头
当满⾜以上要求后,可通过命令⾏参数 pytest 直接运⾏符合条件的⽤例:

注意:使用pytest框架之后,python类中不可以添加init⽅法

由于 pytest 的测试收集机制,测试类中不可以定义 init ⽅法。 pytest 采⽤⾃动发现机制 来收集测试⽤例。它会⾃动实例化测试类并调⽤其所有以 test 结尾的⽅法作为测试⽤例。如果测试 类中定义了 init ⽅法,那么当 pytest 实例化该类时, init ⽅法会被调⽤,这可能 会掩盖测试类的实际测试逻辑,并引⼊额外的副作⽤,影响测试结果的准确性。
pytest命令参数
pytest 提供了丰富的命令⾏选项来控制测试的执⾏。以下是⼀些常⽤的pytest 命令⾏参数及其 使⽤说明。

pytest配置文件
在当前项⽬下创建 pytest.ini ⽂件,该⽂件为 pytest 的配置⽂件,以下为常⻅的配置选项:

pytest.ini文件中
python
[pytest]
addopts = -vs
testpaths = ./cases
python_files = test_*.py
python_classes = Test*
配置好 pytest.ini ⽂件后,命令⾏执⾏ pytest 命令即可,⽆需再额外指定其他参数:

pytest.ini ⽂件通常位于项⽬的根⽬录下。通过在pytest.ini 中定义配置项,可以覆盖 pytest 的默认⾏为,以满⾜项⽬的需求。
前后置
使⽤pytest框架,测试类中不可以添加init()⽅法,如何进⾏数据的初始化?
pytest 框架提供三种⽅法做前后置的操作:
- setup_method 和 teardown_method :这两个⽅法⽤于类中的每个测试⽅法的前置和后置操作。
- setup_class 和 teardown_class :这两个⽅法⽤于整个测试类的前置和后置操作。
- fixture :这是 pytest 推荐的⽅式来实现测试⽤例的前置和后置操作。 fixture 提供了更 灵活的控制和更强⼤的功能。
⽰例1: setup_method 和 teardown_method
python
class Test():
def setup_method(self):
print("setup_method")
def teardown_method(self):
print("teardown_method")
def test_01(self):
print("test01")

⽰例2: setup_class 和 teardown_class
python
class Test():
def setup_class(self):
print("setup_class")
def teardown_class(self):
print("teardown_class")
def test_01(self):
print("test01")

断言
断⾔( assert )是⼀种调试辅助⼯具,⽤于检查程序的状态是否符合预期。如果断⾔失败(即条件 为假),Python解释器将抛出⼀个 AssertionError 异常。断⾔通常⽤于检测程序中的逻辑错误。pytest 允许你在Python测试中使⽤标准的Python assert 语句来验证预期和值。
基本语法:
python
assert 条件, 错误信息
条件 :必须是⼀个布尔表达式。
错误信息 :当条件为假时显⽰的错误信息,可选。
⽰例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
#断⾔接⼝返回值完整字段和值
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()[1]['id'] == 1
#断⾔接⼝html返回值
def test3():
url = "http://jsonplaceholder.typicode.com/"
r = requests.get(url=url)
assert "Use your own data" in r.text
参数化
参数化设计是自动化设计中的一个重要组成部分,它通过定义设计参数和规则,使得设计过程更加灵 活和可控。
pytest中内置的 pytest.mark.parametrize 装饰器允许对测试函数的参数进⾏参数化。
⽰例1:在⽤例上使⽤参数化
python
import pytest
@pytest.mark.parametrize("test_input,expected", [("2+4", 6), ("5+3", 8)])
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
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():
return ["a", "b"]
# 定义⼀个测试函数,它依赖于上⾯函数的返回值
@pytest.mark.parametrize("data", data_provider())
def test_data(data):
assert data != None
print(f"Testing with data provider: {data}")
fixture
fixture的介绍
pytest 中的 fixture 是⼀种强⼤的机制,⽤于提供测试函数所需的资源或上下⽂。它可以⽤于 设置测试环境、准备数据等。以下是 fixture 的⼀些核⼼概念和使⽤场景.
基本使用
示例1:使用与不使用fixture标记
未标记fixtrue方法标记:
python
#不使用fixtrue标记
def fixtrue_01():
print("第一个fixtrue_01")
def test_01():
fixtrue_01()
print("第一个测试用例")
使用fixtrue标记:
python
@pytest.fixture
def fixtrue_01():
print("第一个fixtrue_01")
def test_01(fixtrue_01):
print("第一个测试用例")
未标记 fixture ⽅法的调⽤与 fixture 标记的⽅法调⽤完全不⼀样,前者需要在⽅法体中调⽤, ⽽后者可以将函数名作为参数进⾏调⽤。
测试脚本中存在的很多重复的代码、公共的数据对象时,使⽤ fixture 最为合适
⽰例2:访问列表⻚和详情⻚之前都需要执⾏登录操作
python
@pytest.fixture
def login():
print("登录")
def test_list(login):
print("访问列表页")
def test_detail(login):
print("访问详情页")
运行结果:

通过使用@pytest.fixture 装饰器来告诉pytest⼀个特定函数是⼀个fixture,通过运⾏结果可 ⻅,在执⾏列表⻚和详情⻚之前都会先执⾏ login ⽅法。
fixture嵌套
python
@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 系 统,它允许我们将测试的复杂需求简化为更简单和有组织的函数,我们只需要每个函数描述它们所依 赖的事物
请求多个fixture
python
class Fruit:
def __init__(self, fruitname):
self.fruitname = fruitname
#类似C++中的重写
def __eq__(self, otherfruit):
return self.fruitname == otherfruit.fruitname
@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
测试和 fixture 不仅限于⼀次请求单个 fixture ,它们可以请求任意多个。
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
@pytest.fixture
def open_close():
print("前置操作,初始化.....")
yield
print("后置操作,清理数据.....")
def test_01(open_close):
print("第⼀个测试⽤例")
⽰例2:创建⽂件句柄与关闭⽂件
python
@pytest.fixture
def file_read():
print("打开文件巨柄")
fo = open("C:\\code\\python\\apiautotest_1\\cases\\test.txt", "r")
yield fo
print("关闭文件")
fo.close()
@pytest.fixture
def file_write():
print("关闭打开的句柄")
fo = open("C:\\code\\python\\apiautotest_1\\cases\\test.txt", "w", encoding="utf-8")
yield fo
def test_file(file_read, file_write):
w = file_write
w.write("写入数据")
w.close() #写入之关闭句柄,方便后续读取
#读取数据
r = file_read
str = r.read(10)
print("文件内容:", str)
带参数的fixture
python
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取名字)
⽰例1: scope 的使⽤
python
#scope = "function"
@pytest.fixture(scope="function")
def fixture_01():
print("初始化")
yield print("清理")
class TestCase():
def test_01(self, fixture_01):
print("test01")
def test_02(self, fixture_01):
print("test02")
结果:

python
#scope = "function"
@pytest.fixture(scope="class")
def fixture_01():
print("初始化")
yield
print("清理")
class TestCase():
def test_01(self, fixture_01):
print("test01")
def test_02(self, fixture_01):
print("test02")
结果:

结论:
- scope 默认为 function ,这⾥的 function 可以省略不写,当 scope="function" 时, 每个测试函数都会调⽤⼀次 fixture 。 scope="class" 时,在同⼀个测试类中, fixture 只会在类中的第⼀个测试函数开始前执⾏⼀次,并在类中的最后⼀个测试函数结束后执⾏清理。
- 当 scope="moudle" 、 scope="session" 时可⽤于实现全局的前后置应⽤,这⾥需要多个 ⽂件的配合
conftest.py 和 @pytest.fixture 结合使⽤实现全局的前后置应⽤
@pytest.fixture 与 conftest.py ⽂件结合使⽤,可以实现在多个测试模块( .py )⽂件中共 享前后置操作,这种结合的⽅式使得可以在整个测试项⽬中定义和维护通⽤的前后置逻辑,使测试代码 更加模块化和可维护。
规则:
- conftest.py 是⼀个单独存放的夹具配置⽂件,名称是固定的不能修改
- 你可以在项⽬中的不同⽬录下创建多个 conftest.py ⽂件,每个 conftest.py ⽂件都会对其 所在⽬录及其⼦⽬录下的测试模块⽣效
- 在不同模块的测试中需要⽤到 conftest.py 的前后置功能时,不需要做任何的import导⼊操作
- 作⽤:可以在不同的 .py ⽂件中使⽤同⼀个 fixture 函数
⽰例2: scope="moudle" 、 scope="session" 实现全局的前后置应⽤
当scope是moudle时
项目结构: 
test_01.py: 
test_02.py: 
结果:每个测试实例都执行一遍

当scope是session时
test_01.py: 
test_02.py: 
结果:每个.py模块执行一次

⽰例3: autouse 的使⽤
python
@pytest.fixture(scope="class", autouse=True)
def fixture_02():
print("初始化")
yield
print("清理")
class TestCase1():
def test_01(self):
print("test01")
def test_02(self):
print("test02")
结果:

autouse 默认为 False ,即当前的 fixture 需要⼿动显⽰调⽤,在该案例之前我们默认使⽤的 都是 autouse=False
当 autouse=True 时, fixture 会在所有测试函数执⾏之前⾃动调⽤,⽆论这些测试函数是否显 式地引⽤了该 fixture
⽰例4:通过 params 实现参数化
python
@pytest.fixture(params=["a", "b"])
def data_provider(request):
return request.param
def test_03(data_provider):
assert data_provider != None
print(f"Testing with data provider: {data_provider}")
结果:


