mock测试_在Python中进行测试:依赖注入与Mock

什么时候应该使用依赖注入? 什么时候应该使用Mock?

e5fe93cf0c2c06d0fac68602750013f7.png

> Photo by Helloquence on Unsplash.

我喜欢Python的一件事是它的测试设施。 当您需要模拟与外部依赖项的交互时,可以选择:

· 使用依赖项注入将依赖项替换为测试double。

· 使用Python的模拟库劫持实际的函数调用。

· 使用模拟响应对假服务器进行测试。

有了我们提供的所有这些测试策略,可能不清楚要使用哪种策略。 在本文中,我将讨论何时在依赖注入上选择模拟,反之亦然。

编码

让我们看一些代码来说明模拟和依赖注入之间的区别。 假设我正在测试调用库存服务的代码。 库存服务会跟踪用户持有的物品。 这是我们用来与广告资源服务进行交互的客户:

import requestsINVENTORY_SERVICE_URL = "http://inventory-service.com/api"def add_item(user_id, item_id):    requests.post(f"{INVENTORY_SERVICE_URL}/{user_id}/items", json={        "item_id": item_id,    })def get_items(user_id):    r = requests.get(f"{INVENTORY_SERVICE_URL}/{user_id}/items")    return r.json()

被测代码:

from src import inventoryBASIC_VOUCHER = 0SPECIAL_VOUCHER = 1VOUCHERS = {BASIC_VOUCHER, SPECIAL_VOUCHER}def send_vouchers(voucher_data):    for user_id, voucher_id in voucher_data:        inventory.add_item(user_id, voucher_id)def verify_has_voucher(user_id):    items = inventory.get_items(user_id)    for item_id in items:        if item_id in VOUCHERS:            return True    return False

使用Mock方法的有效测试

尽管这不是有关如何使用模拟库的教程,但我将逐步通过代码来建立上下文:

from src import systemfrom unittest import mock@mock.patch("src.inventory.get_items")@mock.patch("src.inventory.add_item")def test_vouchers(add_mock, get_mock):    mock_user = 0    data = [(mock_user, system.SPECIAL_VOUCHER)]    get_mock.return_value = [system.SPECIAL_VOUCHER]    system.send_vouchers(data)    assert add_mock.called    assert system.verify_has_voucher(mock_user)    assert get_mock.called

· 我们不想调用库存服务,因此我们修补了get_items和add_item函数。

· 我们已将对库存服务的调用的返回值设置为我们期望的返回值。

使用依赖项注入的有效测试

在使用依赖注入之前,我们需要建立一个生产/测试环境以及一种选择在每个环境中运行的代码的方法。 让我们更改Client:

import osimport requestsfrom abc import ABC, abstractmethodclass Inventory(ABC):    @abstractmethod    def add_item(self, user_id, item_id):        raise NotImplementedError    @abstractmethod    def get_items(self, user_id):        raise NotImplementedErrorclass InventoryService(Inventory):    INVENTORY_SERVICE_URL = "http://inventory-service.com/api"    def add_item(self, user_id, item_id):        requests.post(f"{self.INVENTORY_SERVICE_URL}/{user_id}/items", json={            "item_id": item_id,        })    def get_items(self, user_id):        r = requests.get(f"{self.INVENTORY_SERVICE_URL}/{user_id}/items")        return r.json()class InventoryMock(Inventory):    def __init__(self):        self.data = {}    def add_item(self, user_id, item_id):        self.data[user_id] = self.data.get(user_id, []) + [item_id]    def get_items(self, user_id):        return self.data.get(user_id, [])client = Nonedef get_client():    global client    if client is None:        if os.environ.get("ENV") == "prod":            client = InventoryService()        else:            client = InventoryMock()    return client

免责声明:此代码足以说明该概念。 您需要为生产代码库进行更复杂的设置。

我们定义了两个具体的类。 如果我们不在产品环境中,则将使用InventoryMock类将数据保存在内存中。 不再需要使用模拟库。 我们的新测试如下所示:

from src import systemdef test_vouchers():    mock_user = 0    data = [(mock_user, system.SPECIAL_VOUCHER)]    system.send_vouchers(data)    assert system.verify_has_voucher(mock_user)

我应该使用什么?

两种策略都使我们能够在不调用清单服务的情况下测试代码。 在选择最合适的策略时,我会考虑以下几点:

范围/成本

根据您的代码状态,一种策略会比另一种便宜。 如果您需要模拟少数用例,则修补功能而不是创建模拟类可能更容易/更快。 如果您的代码库已经具有支持依赖项注入的基础结构和工具,那么编写简单的模拟类可能比打补丁更简单。

您还应该考虑所采用方法的未来后果。 如果您要模拟的交互方式可能会发生变化,请考虑选择修改最快的方法。

规模经济

依赖注入是扩展模拟方法的一种方式。 如果很多用例都依赖于您要模拟的交互,那么投资依赖注入是有意义的。 易于依赖注入的系统:

· 身份验证/授权服务。

· 负责分布式跟踪和跟踪指标的日志记录解决方案。

· 常用的基础结构,例如缓存和消息代理。

这些系统通常在整个代码库中使用:

f970e499ef4bbc4cbd140cc4a5016000.png

这就是依赖注入的亮点-必须修补每个交互都会很痛苦。 如果有问题的系统跨多个存储库使用,那么您可以更进一步,并将依赖项注入类形式化到客户端库中。

总结思想

依赖注入和模拟都是测试外部依赖的值得推荐的方法。 依赖项注入需要更多的工作来设置,但对于高频使用来说它处于适当的位置。 模拟/修补方法是快速/容易的,但是随着依赖性使用的增加/更改,它开始变成技术债务。 还有两件事要牢记:

· 一致性:如果代码使用依赖性注入(或修补)来模拟交互,除非有明显的优势,否则没有理由偏离。

· 能力:如果工程师精通Python的模拟库,并且没有面向对象风格的依赖注入的经验(反之亦然),那么迁移到依赖注入的量化收益可能不会超过质量上的危害。

(本文翻译自Talha Malik的文章《Testing in Python: Dependency Injection vs. Mocking》,参考:https://medium.com/better-programming/testing-in-python-dependency-injection-vs-mocking-5e542783cb20)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值