web-dev-qa-db-ja.com

モジュールの内部機能にモックでパッチを当てる方法は?

「内部関数」とは、で定義されているのと同じモジュール内から呼び出される関数を意味します。

ユニットテストでは、 mock ライブラリ、特に patch デコレータを使用しています。それらはDjangoユニットテストですが、これはすべてのpythonテストに適用されます。

私はいくつかの関数を持つ1つのモジュールを持っており、それらの多くは相互に呼び出します。例(架空のコード、decimal.Decimalの欠如を無視してください):

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)の一部であり、すべて同じモジュール内にあります。

私の単体テストでは、一貫した結果を得るために税金を無効にしたいと思います。私が見ているように、私の2つのオプションは、1)add_taxが実際には何もしないようにTAX_LOCATION(たとえば、空の文字列を使用)をパッチアウトするか、2)add_taxをパッチアウトして(0、price)を返すだけです。

ただし、これらのいずれかにパッチを適用しようとすると、パッチは外部で機能しているように見えます(パッチを適用した部分をテスト内にインポートして印刷し、期待値を取得できます)が、内部では効果がないようです(コードは、パッチが適用されていないかのように動作します)。

私のテストは次のようになります(これも架空のコードです)。

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

モックがこのように内部で使用される関数にパッチを当てることが可能かどうか誰かが知っていますか、それとも私は運が悪いですか?

23
eternicode

答え:あなたのとんでもない輸入品を片付けなさい

@patch('mymodule.TAX_LOCATION', '')は確かに適切にパッチを適用しましたが、当時のインポートは非​​常に無計画だったため、mymodule.build_cartをインポートすることもあれば、project.mymodule.build_cartをインポートすることもありました-「フル」のインスタンスインポートにはまったくパッチが適用されていません。とにかく、明示的に言われなければ、モックは2つの別々のインポートパスについて知ることを期待できませんでした。

それ以来、すべてのインポートをより長いパスで標準化し、物事は今でははるかにうまく動作します。

15
eternicode

別のオプションは、関数でパッチを明示的に呼び出すことです。

mock.patch('function_name')

直接実行またはpy.testなどからの実行の両方をサポートするには:

mock.patch(__name__ + '.' + 'function_name')
8
vim

受け入れられたもの以外の解決策を追加したいのですが。他のモジュールにインポートする前にモジュールにパッチを適用し、テストケースの最後でパッチを削除することもできます。

#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()
3
Dmitriy

あなたの問題は、テスト関数内に「mymodule」をインポートしていることであると確信しています。したがって、パッチデコレータには実際にパッチを適用する機会がありません。他のインポートと同様に、モジュールの上部でインポートを実行します。

1
Michael Kent