Как пропатчить внутренние функции модуля с помощью mock? - PullRequest
23 голосов
/ 17 марта 2011

Под «внутренней функцией» я подразумеваю функцию, которая вызывается из того же модуля, в котором она определена.

Я использую библиотеку mock , в частности, декораторы patch , в моих модульных тестах. Это модульные тесты Django, но они должны применяться к любым тестам на Python.

У меня есть один модуль с несколькими функциями, многие из которых вызывают друг друга. Например (вымышленный код, игнорируйте отсутствие десятичного. Десятичного):

TAX_LOCATION = 'StateName, United States'

def add_tax(price, user):
    tax = 0
    if TAX_LOCATION == 'StateName, UnitedStates':
        tax = price * .75
    return (tax, price+tax)

def build_cart(...):
    # build a cart object for `user`
    tax, price = add_tax(cart.total, cart.user)
    return cart

Это часть более глубокой цепочки вызовов (func1 -> func2 -> build_cart -> add_tax), все из которых находятся в одном модуле.

В моих модульных тестах я хотел бы отключить налоги, чтобы получить согласованные результаты. На мой взгляд, у меня есть два варианта: 1) исправить TAX_LOCATION (скажем, с пустой строкой), чтобы add_tax ничего не делал, или 2) исправить add_tax для простого возврата (0, цена).

Однако, когда я пытаюсь исправить одно из этих исправлений, кажется, что исправление работает внешне (я могу импортировать исправленную часть внутри теста и распечатать его, получая ожидаемые значения), но, похоже, не имеет никакого внутреннего эффекта ( получить из кода вести себя так, как будто патч не был применен).

Мои тесты такие (опять же, вымышленный код):

from mock import patch
from django.test import TestCase

class MyTests(TestCase):

    @patch('mymodule.TAX_LOCATION', '')
    def test_tax_location(self):
        import mymodule
        print mymodule.TAX_LOCATION # ''
        mymodule.func1()
        self.assertEqual(cart.total, original_price) # fails, tax applied

    @patch('mymodule.add_tax', lambda p, u: (0, p))
    def test_tax_location(self):
        import mymodule
        print mymodule.add_tax(50, None) # (0, 50)
        mymodule.func1()
        self.assertEqual(cart.total, original_price) # fails, tax applied

Кто-нибудь знает, возможно ли в mock исправлять такие функции, используемые внутри компании, или мне не повезло?

Ответы [ 4 ]

15 голосов
/ 19 января 2013

Ответ: Очистите свой проклятый импорт

@patch('mymodule.TAX_LOCATION', '') действительно исправил вещи соответствующим образом, но так как наш импорт в то время был очень случайным - иногда мы импортировали mymodule.build_cartиногда мы импортировали project.mymodule.build_cart - экземпляры «полного» импорта вообще не исправлялись.Нельзя было ожидать, что Mock будет знать о двух разных путях импорта ... в любом случае, без явного указания.

С тех пор мы стандартизировали весь наш импорт по более длинному пути, и теперь все идет гораздо лучше.

7 голосов
/ 15 февраля 2016

другой вариант заключается в явном вызове patch для функции:

mock.patch('function_name')

и поддержке как прямого запуска, так и из py.test и т.д.

3 голосов
/ 20 февраля 2018

Я бы хотел добавить решение, отличное от принятого. Вы также можете исправить модуль до его импорта в другие модули и удалить исправление в конце теста.

#import some modules that don't use module you are going to patch
import unittest
from mock import patch
import json
import logging
...


patcher = patch('some.module.path.function', lambda x: x)
patcher.start()

import some.module.path

class ViewGetTests(unittest.TestCase):

  @classmethod
  def tearDownClass(cls):
      patcher.stop()
1 голос
/ 18 марта 2011

Я почти уверен, что ваша проблема в том, что вы импортируете 'mymodule' в свои тестовые функции, и поэтому у декоратора патчей нет шансов на самом деле установить патчи. Выполните импорт в верхней части модуля, как и любой другой импорт.

...