Python Mocking di una funzione da un modulo importato


125

Voglio capire come eseguire @patchuna funzione da un modulo importato.

Questo è dove sono finora.

app / mocking.py:

from app.my_module import get_user_name

def test_method():
  return get_user_name()

if __name__ == "__main__":
  print "Starting Program..."
  test_method()

app / my_module / __ init__.py:

def get_user_name():
  return "Unmocked User"

test / mock-test.py:

import unittest
from app.mocking import test_method 

def mock_get_user():
  return "Mocked This Silly"

@patch('app.my_module.get_user_name')
class MockingTestTestCase(unittest.TestCase):

  def test_mock_stubs(self, mock_method):
    mock_method.return_value = 'Mocked This Silly')
    ret = test_method()
    self.assertEqual(ret, 'Mocked This Silly')

if __name__ == '__main__':
  unittest.main()

Questo non funziona come mi sarei aspettato. Il modulo "patchato" restituisce semplicemente il valore unmocked di get_user_name. Come faccio a simulare i metodi da altri pacchetti che sto importando in uno spazio dei nomi in prova?


1
La domanda riguarda "prendere in giro le migliori pratiche" o se quello che stai facendo ha senso o no? Per quanto riguarda il primo, direi di utilizzare una libreria beffarda come Mock, che è inclusa in python3.3 + as unittest.mock.
Bakuriu

Sto chiedendo se sto andando bene. Ho guardato Mock, ma non vedo un modo per risolvere questo particolare problema. C'è un modo per ricreare quello che ho fatto sopra in Mock?
nsfyn55

Risposte:


167

Quando si utilizza il patchdecoratore dal unittest.mockpacchetto non si applica la patch allo spazio dei nomi da cui è importato il modulo (in questo caso app.my_module.get_user_name) lo si applica nello spazio dei nomi sotto test app.mocking.get_user_name.

Per fare quanto sopra con Mockprova qualcosa come il seguente:

from mock import patch
from app.mocking import test_method 

class MockingTestTestCase(unittest.TestCase):

    @patch('app.mocking.get_user_name')
    def test_mock_stubs(self, test_patch):
        test_patch.return_value = 'Mocked This Silly'
        ret = test_method()
        self.assertEqual(ret, 'Mocked This Silly')

La documentazione della libreria standard include una sezione utile che lo descrive.


questo arriva al mio problema. get_user_nameè in un modulo diverso da test_method. C'è un modo per deridere qualcosa in un sottomodulo? L'ho risolto in modo brutto di seguito.
nsfyn55

6
Non importa che get_user_namesi trovi in ​​un modulo diverso da test_methodquando stai importando la funzione in app.mockingessi si trovano nello stesso spazio dei nomi.
Matti John

2
Da dove viene test_patch, che cos'è esattamente?
Mike G

2
test_patch viene passato dal decoratore di patch ed è l'oggetto mocked get_user_name (cioè un'istanza della classe MagicMock). Potrebbe essere più chiaro se fosse chiamato qualcosa di simile get_user_name_patch.
Matti John

Come stai facendo riferimento a test_method? Risulterà in errore, NameError: il nome globale 'test_method' non è definito
Aditya

12

Sebbene la risposta di Matti John risolva il tuo problema (e ha aiutato anche me, grazie!), Tuttavia, suggerirei di localizzare la sostituzione della funzione originale "get_user_name" con quella derisa. Ciò ti consentirà di controllare quando la funzione viene sostituita e quando non lo è. Inoltre, questo ti consentirà di effettuare diverse sostituzioni nello stesso test. Per fare ciò, usa l'affermazione 'with' in un modo abbastanza simile:

from mock import patch

class MockingTestTestCase(unittest.TestCase):

    def test_mock_stubs(self):
        with patch('app.mocking.get_user_name', return_value = 'Mocked This Silly'):
            ret = test_method()
            self.assertEqual(ret, 'Mocked This Silly')

6
Questo è un po 'irrilevante per la domanda posta. Che tu usi patchcome decoratore o gestore di contesto è specifico per il caso d'uso. Ad esempio, puoi usare patchcome decoratore per deridere un valore per tutti i test in una classe xunito pytestmentre in altri casi è utile avere il controllo a grana fine offerto dal gestore del contesto.
nsfyn55
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.