Verspotten von zwei Funktionen mit Patch für einen Unit-Test

78

Ich habe eine Funktion, die ich Unit-Test möchte Aufrufe zwei weitere Funktionen enthält. Ich bin mir nicht sicher, wie ich beide Funktionen gleichzeitig mit Patch richtig verspotten kann. Ich habe ein Beispiel dafür gegeben, was ich unten meine. Wenn ich Nasentests durchführe, bestehen die Tests, aber ich bin der Meinung, dass es einen saubereren Weg geben muss, und ich verstehe das Stück in Bezug auf f.close () nicht wirklich ...

Die Verzeichnisstruktur sieht folgendermaßen aus:

program/
  program/
    data.py
  tests/
    data_test.py

data.py:

import cPickle

def write_out(file_path, data):
    f = open(file_path, 'wb')
    cPickle.dump(data, f)
    f.close()

data_test.py:

from mock import MagicMock, patch

def test_write_out():
    path = '~/collection'
    mock_open = MagicMock()
    mock_pickle = MagicMock()
    f_mock = MagicMock()
    with patch('__builtin__.open', mock_open):
        f = mock_open.return_value
        f.method.return_value = path
        with patch('cPickle.dump', mock_pickle):
            write_out(path, 'data')
            mock_open.assert_called_once_with('~/collection', 'wb')
            f.close.assert_any_call()
            mock_pickle.assert_called_once_with('data', f)

Ergebnisse:

$ nosetests
.
----------------------------------------------------------------------
Ran 1 test in 0.008s
OK
cnodell
quelle
1
Ich denke, meine ursprüngliche Frage war unklar, also habe ich sie aufgeräumt. Ich hoffe das zeigt was ich suche genauer!
cnodell

Antworten:

110

Sie können Ihren Test vereinfachen, indem Sie den Patch-Dekorator verwenden und sie wie folgt verschachteln (sie sind MagicMockstandardmäßig Objekte):

@patch('cPickle.dump')
@patch('__builtin__.open')
def test_write_out(mock_open, mock_pickle):
    path = '~/collection'
    f = mock_open.return_value
    f.method.return_value = path

    write_out(path, 'data')

    mock_open.assert_called_once_with('~/collection', 'wb')
    mock_pickle.assert_called_once_with('data', f)
    f.close.assert_any_call()

Aufrufe einer MagicMockInstanz geben eine neue MagicMockInstanz zurück, sodass Sie überprüfen können, ob der zurückgegebene Wert wie jedes andere verspottete Objekt aufgerufen wurde. In diesem Fall fist ein MagicMockbenannter 'open()'(versuchen Sie zu drucken f).

Matti John
quelle
8
In Ihrem Vorschlag führen Sie zwei Parameter ein, einen für jedes Modell. Woher weiß Python, welches welches ist? Ich konnte die Antwort darauf nicht in den Dokumenten finden.
Schritte
49
Die Dekorateure werden von unten nach oben angewendet und die Reihenfolge der Parameter muss dieser entsprechen. Siehe hier: voidspace.org.uk/python/mock/…
Matti John
1
Ja, ich habe das gelesen, aber es nicht klar genug gefunden. Vielen Dank!
Schritte
4
Beachten Sie, dass die Parameter hier in umgekehrter Reihenfolge sind, in der Patches angewendet wurden.
Shubham Chaudhary
Ausgezeichnet! Danke dafür.
R Claven
55

Zusätzlich zur Antwort @Matti John können Sie auch die patchInside-Funktion verwenden test_write_out:

from mock import MagicMock, patch

def test_write_out():
    path = '~/collection'
    with patch('__builtin__.open') as mock_open, \
            patch('cPickle.dump') as mock_pickle:

        f = mock_open.return_value
        ...
Alex Lisovoy
quelle
2

Hier ist ein einfaches Beispiel, wie man Test Erhöhung ConflictErrorin create_collectionFunktion Mock mit:

import os
from unittest import TestCase
from mock import patch
from ..program.data import ConflictError, create_collection


class TestCreateCollection(TestCase):
    def test_path_exists(self):
        with patch.object(os.path, 'exists') as mock_method:
            mock_method.return_value = True

            self.assertRaises(ConflictError, create_collection, 'test')

Bitte lesen Sie auch die Mock-Dokumente und Michael Foords großartige Einführung in das Mock .

Alecxe
quelle
Danke, dass du überhaupt versucht hast, mir zu helfen. Das hilft mir zwar, aber ich habe mich mehr darauf konzentriert, wie man mehrere Funktionen mit Patch verspottet. Leider hat meine Frage das nicht klar gemacht. Ich habe die Frage jetzt aufgeräumt.
cnodell