目录

深度剖析: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

go 复制代码
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

go 复制代码
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

go 复制代码
import pytest  
from calculator.core import Calculator  
  
@pytest.fixture  
def calculator():  
    return Calculator(2, 3)

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

go 复制代码
def test_add(calculator):  
    assert calculator.add() == 5


deftest_subtract(calculator):  
    assert calculator.subtract() == -1


deftest_multiply(calculator):  
    assert calculator.multiply() == 6


deftest_divide(calculator):  
    assert calculator.divide() == 0.6666666666666666


deftest_divide_by_zero(calculator):  
    calculator.b = 0
    with pytest.raises(ZeroDivisionError):  
        calculator.divide()

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

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

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

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

test_calculator_api.py

go 复制代码
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 == 200
    assert response.json == 3


deftest_subtract(client, json_headers, json_data):  
    response = client.post("/api/subtract/", headers=json_headers, json=json_data)  
    assert response.status_code == 200
    assert response.json == -1


deftest_multiply(client, json_headers, json_data):  
    response = client.post("/api/multiply/", headers=json_headers, json=json_data)  
    assert response.status_code == 200
    assert response.json == 2


deftest_divide(client, json_headers, json_data):  
    response = client.post("/api/divide/", headers=json_headers, json=json_data)  
    assert response.status_code == 200
    assert response.json == 0.5


deftest_divide_by_zero(client, json_headers):  
    response = client.post("/api/divide/", headers=json_headers, json={"a": 1, "b": 0})  
    assert response.status_code == 400
    assert response.json == "除数不能为零"

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

Flask客户端。

API请求的JSON头部信息。

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

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

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

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

conftest.py

go 复制代码
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。

例如:

go 复制代码
@pytest.fixture  
def custom_calculator(scope="module"):  
    def _calculator(a, b):  
        return Calculator(a, b)  
  
    return _calculator

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

Fixtures依赖注入

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

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

go 复制代码
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的示例:

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

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

return的示例:

go 复制代码
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使你的测试过程更快、更高效、更有效。

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
南部余额1 天前
使用python反射,实现pytest读取yaml并发送请求
python·pytest
程序员念姐2 天前
Selenium+Pytest自动化测试框架实战
selenium·测试工具·pytest
Test.X2 天前
学习15天:pytest
学习·pytest
one day3213 天前
pytest 框架学习总结
学习·pytest
天才测试猿3 天前
Pytest自动化测试框架pytest-xdist分布式测试插件
自动化测试·软件测试·分布式·python·测试工具·测试用例·pytest
qq_433716953 天前
压力测试Monkey命令参数和报告分析!
自动化测试·selenium·测试工具·单元测试·pytest·接口测试·压力测试
测试渣3 天前
API自动化测试实战:Postman + Newman/Pytest的深度解析
pytest·postman
qq_433716954 天前
UI自动化测试 —— web端元素获取&元素等待实践!
自动化测试·软件测试·selenium·测试工具·ui·pytest·测试工程师
大霞上仙5 天前
pytest+allure+jenkins 实现接口自动化测试
运维·jenkins·pytest