欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 能源 > 深度剖析:Pytest Fixtures如何重塑自动化测试的可读性与高效性

深度剖析:Pytest Fixtures如何重塑自动化测试的可读性与高效性

2025/3/18 10:29:35 来源:https://blog.csdn.net/lyy51/article/details/146325522  浏览:    关键词:深度剖析:Pytest Fixtures如何重塑自动化测试的可读性与高效性

关注开源优测不迷路

大数据测试过程、策略及挑战

测试框架原理,构建成功的基石

在自动化测试工作之前,你应该知道的10条建议

在自动化测试中,重要的不是工具

在编写单元测试时,是否发现自己写了很多相同/相似代码呢?

像数据库的设置与清理、API客户端或测试数据这类单调乏味的代码,如果要在几十甚至几百个单元测试中重复编写,会非常痛苦。

在编写测试时,通常需要在运行实际的测试代码之前设置一些初始状态。

编写这些设置代码可能很耗时,尤其是当有多个测试都需要相同的步骤时。

在项目的整个生命周期中,测试代码应该易于理解、重构、扩展和维护。

Pytest中的Fixtures解决了一些代码重复和样板代码的问题。

它们帮助你定义可复用的设置或清理代码,这些代码可以在多个测试中使用。

无需在每个测试中都重复相同的设置代码,只需定义一次Fixtures,就可以在多个测试中使用。

这不仅减少了代码重复,还使维护更加容易,因为任何更改都只需在一个地方进行。

在本文中,你将进一步了解Pytest Fixtures、它们的优点,以及它们如何帮助你编写更好、更简单的单元测试。

学习目标

在本教程结束时,你应该能够:

定义什么是Pytest Fixtures。

理解Pytest Fixtures的优点。

在单元测试中使用Fixtures。

理解Fixtures作用域和参数化Fixtures。

编写有效、更易于维护且利用Fixtures的单元测试。

使用Flask构建一个简单的计算器API,并使用Pytest Fixtures对其进行测试。

什么是Pytest Fixtures

在深入探讨如何应用Fixtures之前,让我们先快速了解一下Fixtures到底是什么。

Fixtures是Pytest中的一些方法,它们为测试的运行提供了一个固定的基础。

Fixtures可用于为测试设置前置条件、提供数据,或者在测试完成后执行清理操作。

在Python中,它们是使用@pytest.fixture装饰器来定义的,并且可以作为参数传递给测试函数。

Fixtures极大地简化了编写测试的过程,它允许你在多个测试中复用代码,并为每个测试提供一个一致的起点。

Pytest有几个内置的Fixtures,适用于常见的用例,例如设置测试数据库、模拟外部依赖项以及设置测试数据。

Fixtures也有各种作用域,比如函数作用域、类作用域、模块作用域和会话作用域。

Fixtures的作用域定义了在测试会话期间Fixtures的可用时长。

这使你能够控制Fixtures的生命周期,并根据你的测试需求为Fixtures选择合适的作用域。我们将在本文后面详细讨论作用域。

总的来说,Fixtures是Pytest的一个强大功能,有助于减少代码重复,提高测试的可靠性,并使测试更具模块化和可维护性。

如何使用Pytest Fixtures

现在让我们进入本文的重点部分:如何通过创建一个真正简单的应用程序来使用Fixtures。

为了理解Fixtures,我们将构建一个基本的计算器应用程序,并使用Flask API来提供服务。

如果你不熟悉Flask,别担心,你可以跳过API及其测试部分,只关注核心逻辑(计算器部分)。

项目设置

按以下项目结构构建本文的测试代码组织:

源代码

这个示例的源代码是一个Flask API,它包含一个基本的计算器应用程序,可以计算两个数字的和、差、积和商。

计算器 /calculator/core.py

class Calculator:    def__init__(self, a: int | float = None, b: int | float = None) -> None:    self.a = a    self.b = b    defadd(self) -> int | float:    """    计算两个数字的和    返回:两个数字的和    """    return self.a + self.b    defsubtract(self) -> int | float:    """    计算两个数字的差    返回:两个数字的差    """    return self.a - self.b    defmultiply(self) -> int | float:    """    计算两个数字的积    返回:两个数字的积    """    return self.a * self.b    defdivide(self) -> int | float:    """    计算两个数字的商    返回:两个数字的商    """    if self.b != 0:    return self.a / self.b    else:    raise ZeroDivisionError("除数不能为零")    defsquare(self) -> int | float:    """    计算一个数字的平方    返回:一个数字的平方    """    return self.a**2

上面是一个非常简单的Calculator类,用于执行基本的计算操作。

Flask应用程序

这个Flask应用程序为计算器提供了一个API包装器,允许我们向服务器发送远程请求并获取计算结果。

Flask应用程序如下所示:

/app/app.py

from flask import Flask, jsonify, request   
from calculator.core import Calculator   # 创建Flask应用程序   
app = Flask(__name__)   # 创建路由   
@app.route('/')   
defindex():   return'Index Page'   # 为加法函数添加一个路由   
@app.route('/api/add/', methods=['POST'])   
defadd():   data = request.get_json()   a = data['a']   b = data['b']   result = Calculator(a, b).add()   return jsonify(result)   # 为减法函数添加一个路由   
@app.route('/api/subtract/', methods=['POST'])   
defsubtract():   data = request.get_json()   a = data['a']   b = data['b']   result = Calculator(a, b).subtract()   return jsonify(result)   # 为乘法函数添加一个路由   
@app.route('/api/multiply/', methods=['POST'])   
defmultiply():   data = request.get_json()   a = data['a']   b = data['b']   result = Calculator(a, b).multiply()   return jsonify(result)   # 为除法函数添加一个路由   
@app.route('/api/divide/', methods=['POST'])   
defdivide():   data = request.get_json()   a = data['a']   b = data['b']   if b == 0:   return jsonify("除数不能为零"), 400   result = Calculator(a, b).divide()   return jsonify(result)   if __name__ == '__main__':   app.run()

我们有4个简单的路由(每个操作对应一个),每个路由都接受一个POST请求,请求负载中包含两个值a和b。

这个应用程序调用上面的Calculator类来执行计算。

单元测试

单元测试定义在两个单独的文件中:

test_calculator_class.py——测试Calculator类。 test_calculator_api.py——使用自定义负载测试API端点。

让我们来看看每个文件,以及它们是如何使用Pytest Fixtures的。

测试中的Fixtures

定义Fixtures最简单的方法是在测试内部进行定义。让我们看看如何使用在测试中定义的Fixtures来测试我们的代码。

test_calculator_class.py

import pytest  
from calculator.core import Calculator  @pytest.fixture  
def calculator():  return Calculator(2, 3)

使用预定义Fixtures进行基本计算器测试

def test_add(calculator):  assert calculator.add() == 5deftest_subtract(calculator):  assert calculator.subtract() == -1deftest_multiply(calculator):  assert calculator.multiply() == 6deftest_divide(calculator):  assert calculator.divide() == 0.6666666666666666deftest_divide_by_zero(calculator):  calculator.b = 0with pytest.raises(ZeroDivisionError):  calculator.divide()

在这里,我们使用@pytest.fixture装饰器定义了Pytest Fixtures。

我们使用值(2, 3)初始化了Calculator类,并返回了该类的一个实例。

然后,我们将这个Fixtures传递给每个单元测试,这样就无需在每个测试中重新初始化Calculator类了。

让我们看看如何对API也进行这样的操作。

test_calculator_api.py

import pytest  
from app.app import app  @pytest.fixture  
defclient():  with app.test_client() as client:  yield client  @pytest.fixture  
defjson_headers():  return {"Content-Type": "application/json"}  deftest_add(client, json_headers, json_data):  response = client.post("/api/add/", headers=json_headers, json=json_data)  assert response.status_code == 200assert response.json == 3deftest_subtract(client, json_headers, json_data):  response = client.post("/api/subtract/", headers=json_headers, json=json_data)  assert response.status_code == 200assert response.json == -1deftest_multiply(client, json_headers, json_data):  response = client.post("/api/multiply/", headers=json_headers, json=json_data)  assert response.status_code == 200assert response.json == 2deftest_divide(client, json_headers, json_data):  response = client.post("/api/divide/", headers=json_headers, json=json_data)  assert response.status_code == 200assert response.json == 0.5deftest_divide_by_zero(client, json_headers):  response = client.post("/api/divide/", headers=json_headers, json={"a": 1, "b": 0})  assert response.status_code == 400assert response.json == "除数不能为零"

在这个测试中,我们定义了两个Fixtures:

Flask客户端。

API请求的JSON头部信息。

如果你不熟悉API和Flask,我建议你阅读一些基础知识以便更好地理解。

上面的Fixtures允许我们定义一次客户端和JSON头部信息,并在测试中进行POST请求时复用它们。

通过conftest在多个测试中使用Fixtures 一种更高效的方法是将通用的Fixtures放在一个名为conftest.py的文件中,这样所有的单元测试文件都会自动获取到这些Fixtures。

如果你不熟悉conftest,这篇关于Pytest conftest的文章会给你一个坚实的基础。

conftest.py

import pytest  
from calculator.core import Calculator  
from app.app import app  @pytest.fixture(scope="module")  
defcalculator():  return Calculator(2, 3)  @pytest.fixture  
defcustom_calculator(scope="module"):  def_calculator(a, b):  return Calculator(a, b)  return _calculator  @pytest.fixture(scope="module")  
defclient():  with app.test_client() as client:  yield client  @pytest.fixture(scope="module")  
defjson_headers():  return {"Content-Type": "application/json"}  @pytest.fixture(scope="module")  
defjson_data():  return {"a": 1, "b": 2}

在这里,我们定义了Fixtures,并且可以在单元测试中轻松使用它们。

我们有各种Fixtures:

Calculator Fixtures。 自定义CalculatorFixtures(参数化Fixtures)。 Flask客户端Fixtures。 JSON头部信息Fixtures。 JSON数据Fixtures。

参数化Fixtures

这些Fixtures可以接受一个或多个参数,并在运行时进行初始化。

在前面代码块的示例中,我们定义了custom_calculatorFixtures,它允许我们在测试中传递不同的(a, b)值。

你可以通过在一个Fixtures中定义另一个Fixtures来定义参数化Fixtures。

例如:

@pytest.fixture  
def custom_calculator(scope="module"):  def _calculator(a, b):  return Calculator(a, b)  return _calculator

这个强大的功能允许我们为每个测试使用自定义值来初始化Calculator类,非常方便。

Fixtures依赖注入

Fixtures也可以被其他Fixtures调用(或请求),这被称为依赖注入。

下面的代码示例展示了这一点:

import pytest   classMyObject:   def__init__(self, value):   self.value = value   @pytest.fixture   
defmy_object():   return MyObject("Hello, World!")   deftest_my_object(my_object):   assert my_object.value == "Hello, World!"   @pytest.fixture   
defmy_dependent_object(my_object):   return MyObject(my_object.value + " Again!")   deftest_my_dependent_object(my_dependent_object):   assert my_dependent_object.value == "Hello, World! Again!"

在这里你可以看到,my_dependent_objectFixtures使用了my_objectFixtures。

除非有必要,我建议避免使用有依赖关系的Fixtures,因为这会增加复杂性,并且将Fixtures层层嵌套会使未来的重构变得困难。

自动使用Fixtures

如果你想找到一个简单的方法来避免在每个测试中都定义Fixtures,你可以在Fixtures定义中使用autouse=True标志作为参数。

当使用autouse=True时,这个Fixtures函数将自动应用于所有测试函数,而无需在每个测试函数中显式地将其作为参数传递。

如果你只想在某些测试函数中使用该Fixtures,你可以将测试函数名作为参数指定给@pytest.fixture装饰器,而不是使用autouse=True。

Fixtures作用域

Fixtures作用域定义了Fixtures的生命周期和可见性。

Fixtures的作用域决定了它将被调用的次数,以及在测试会话期间它的存活时长。

Pytest中可用的Fixtures作用域有:

function(函数作用域):为每个使用该Fixtures的测试函数创建Fixtures,并在测试函数结束时销毁。这是Fixtures的默认作用域。 class(类作用域):为每个使用该Fixtures的测试类创建一次Fixtures,并在测试类结束时销毁。 module(模块作用域):为每个使用该Fixtures的模块创建一次Fixtures,并在测试会话结束时销毁。 session(会话作用域):为每个测试会话创建一次Fixtures,并在测试会话结束时销毁。

要指定Fixtures的作用域,你可以将scope参数传递给@pytest.fixture装饰器。

选择合适的Fixtures作用域取决于Fixtures的用途和使用方式。

如果创建一个Fixtures的成本很高,例如数据库连接,你可能希望使用更高的作用域,以便在多个测试中复用该连接。

另一方面,如果一个Fixtures很轻量级,并且特定于单个测试,你可以使用默认的“function”作用域。

Fixtures中yield与return的区别

你可以使用yield和return语句将Fixtures的值提供给测试函数,但它们的行为和含义有所不同。

当你在Fixtures函数中使用yield时,设置代码会在第一次yield之前执行,而清理代码会在最后一次yield之后执行。

yield的示例:

import pytest  @pytest.fixture  
def my_fixture():  # 设置代码  yield "Fixtures值"  # 清理代码

当你在Fixtures函数中使用return时,设置代码会在return语句之前执行,而清理代码会在return语句之后立即执行。

return的示例:

import pytest  @pytest.fixture  
def my_fixture():  # 设置代码  fixture_value = "Fixtures值"  # 清理代码  return fixture_value

一般来说,当你需要为每个测试函数设置和清理一些资源时,通常会使用yield;而当你只需要为测试函数提供一个简单的值时,则使用return。

何时应该使用Fixtures

一般来说,Fixtures的一个很好的用例是:

客户端——数据库客户端、AWS或其他云客户端、需要设置/清理的API客户端。 测试数据——JSON或其他格式的测试数据可以很容易地导入并在多个测试中共享。 函数——一些常用的函数可以用作Fixtures。

结论

在本文中,你了解了Pytest Fixtures的优点,以及它们如何使编写和维护测试变得更加容易。

你还学习了Pytest Fixtures的基础知识以及如何定义它们。

你构建了一个由Flask API驱动的基本计算器应用程序,并使用conftest.py和在测试内部定义了Fixtures。

最后,你了解了自动使用、作用域以及如何对Fixtures进行参数化,这些都是Pytest非常强大的功能。

Fixtures可用于设置数据库连接、加载测试数据、初始化复杂对象,或执行测试所需的任何其他设置或清理操作。

通过使用Fixtures,你可以编写干净、模块化且可维护的测试代码,这些代码易于阅读和理解。

通过一些练习和实践,你可以利用Pytest Fixtures使你的测试过程更快、更高效、更有效。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词