Verwenden von Pythons Mock-Patch.object, um den Rückgabewert einer Methode zu ändern, die in einer anderen Methode aufgerufen wird

74

Ist es möglich, einen Rückgabewert einer Funktion zu verspotten, die in einer anderen Funktion aufgerufen wird, die ich testen möchte? Ich möchte, dass die verspottete Methode (die in vielen Methoden, die ich teste, aufgerufen wird) bei jedem Aufruf meine angegebenen Variablen zurückgibt. Zum Beispiel:

class Foo:
    def method_1():
       results = uses_some_other_method()
    def method_n():
       results = uses_some_other_method()

Im Unit-Test möchte ich mock verwenden, um den Rückgabewert von uses_some_other_method()so zu ändern , dass bei jedem Aufruf Foodas zurückgegeben wird, was ich definiert habe@patch.object(...)

mdoc-2011
quelle
1
Als Referenz kann jeder auf diesen gut erzählten Artikel realpython.com/python-mock-library
Jinesh

Antworten:

113

Es gibt zwei Möglichkeiten, wie Sie dies tun können. mit patch und mit patch.object

Patch geht davon aus, dass Sie das Objekt nicht direkt importieren, sondern dass es von dem zu testenden Objekt wie im Folgenden verwendet wird

#foo.py
def some_fn():
    return 'some_fn'

class Foo(object):
    def method_1(self):
        return some_fn()
#bar.py
import foo
class Bar(object):
    def method_2(self):
        tmp = foo.Foo()
        return tmp.method_1()
#test_case_1.py
import bar
from mock import patch

@patch('foo.some_fn')
def test_bar(mock_some_fn):
    mock_some_fn.return_value = 'test-val-1'
    tmp = bar.Bar()
    assert tmp.method_2() == 'test-val-1'
    mock_some_fn.return_value = 'test-val-2'
    assert tmp.method_2() == 'test-val-2'

Wenn Sie das zu testende Modul direkt importieren, können Sie patch.object wie folgt verwenden:

#test_case_2.py
import foo
from mock import patch

@patch.object(foo, 'some_fn')
def test_foo(test_some_fn):
    test_some_fn.return_value = 'test-val-1'
    tmp = foo.Foo()
    assert tmp.method_1() == 'test-val-1'
    test_some_fn.return_value = 'test-val-2'
    assert tmp.method_1() == 'test-val-2'

In beiden Fällen wird some_fn nach Abschluss der Testfunktion nicht verspottet.

Bearbeiten: Um mehrere Funktionen zu verspotten, fügen Sie der Funktion einfach weitere Dekoratoren hinzu und fügen Sie Argumente hinzu, um die zusätzlichen Parameter zu übernehmen

@patch.object(foo, 'some_fn')
@patch.object(foo, 'other_fn')
def test_foo(test_other_fn, test_some_fn):
    ...

Beachten Sie, dass der Dekorator umso früher in der Parameterliste steht, je näher er an der Funktionsdefinition liegt.

Silfheed
quelle
2
Vielen Dank für die Erklärung des Unterschieds zwischen patch und patch.object.
Christian Long
2
Was ist, wenn foo eine Bibliothek ist, auf die ich keinen Zugriff habe, und ich mich über den Aufruf von method_1 lustig machen möchte?
Skytreader
1
Tolle Antwort, sehr informativ und klar erklärt.
Josh Fischer
12
Vielen Dank für diesen Satz: "Je näher der Dekorateur an der Funktionsdefinition ist, desto früher steht er in der Parameterliste." Ich habe gerade 1 Stunde damit verbracht, dies zu debuggen ...
Lana Nova
1
@LanaNova gleich hier. Ich habe erwartet, dass die Reihenfolge der Parameter der Reihenfolge entspricht, in der sie von patch.object aufgelistet wurden.
Trendsetter37
17

Dies kann mit so etwas geschehen:

# foo.py
class Foo:
    def method_1():
        results = uses_some_other_method()


# testing.py
from mock import patch

@patch('Foo.uses_some_other_method', return_value="specific_value"):
def test_some_other_method(mock_some_other_method):
    foo = Foo()
    the_value = foo.method_1()
    assert the_value == "specific_value"

Hier ist eine Quelle, die Sie lesen können: Patchen an der falschen Stelle

Chipz
quelle
Danke vielmals. Der Artikel, auf den Sie verwiesen haben, ist wirklich hilfreich.
Lane vor
7

Lassen Sie mich klarstellen, wovon Sie sprechen: Sie möchten Fooin einem Testfall testen, der eine externe Methode aufruft uses_some_other_method. Anstatt die eigentliche Methode aufzurufen, möchten Sie den Rückgabewert verspotten.

class Foo:
    def method_1():
       results = uses_some_other_method()
    def method_n():
       results = uses_some_other_method()

Angenommen, der obige Code befindet sich im Modul foo.pyund uses_some_other_methodist im Modul definiert bar.py. Hier ist das Unittest:

import unittest
import mock

from foo import Foo


class TestFoo(unittest.TestCase):

    def setup(self):
        self.foo = Foo()

    @mock.patch('foo.uses_some_other_method')
    def test_method_1(self, mock_method):
        mock_method.return_value = 3
        self.foo.method_1(*args, **kwargs)

        mock_method.assert_called_with(*args, **kwargs)

Wenn Sie den Rückgabewert jedes Mal ändern möchten, wenn Sie verschiedene Argumente übergeben haben, wird mockFolgendes bereitgestellt side_effect.

Cizixs
quelle