Bei meinem Versuch, TDD zu lernen, versuche ich, Unit-Tests zu lernen und Mock mit Python zu verwenden. Langsam den Dreh raus, aber unsicher, ob ich das richtig mache. Vorgewarnt: Ich arbeite nicht mit Python 2.4, da die Hersteller-APIs als vorkompilierte 2.4-Pyc-Dateien vorliegen. Daher verwende ich Mock 0.8.0 und unittest (nicht unittest2).
Angesichts dieses Beispielcodes in 'mymodule.py'
import ldap
class MyCustomException(Exception):
pass
class MyClass:
def __init__(self, server, user, passwd):
self.ldap = ldap.initialize(server)
self.user = user
self.passwd = passwd
def connect(self):
try:
self.ldap.simple_bind_s(self.user, self.passwd)
except ldap.INVALID_CREDENTIALS:
# do some stuff
raise MyCustomException
Jetzt möchte ich in meiner Testfalldatei 'test_myclass.py' das ldap-Objekt verspotten. ldap.initialize gibt das ldap.ldapobject.SimpleLDAPObject zurück, also dachte ich mir, das wäre die Methode, die ich verspotten müsste.
import unittest
from ldap import INVALID_CREDENTIALS
from mock import patch, MagicMock
from mymodule import MyClass
class LDAPConnTests(unittest.TestCase):
@patch('ldap.initialize')
def setUp(self, mock_obj):
self.ldapserver = MyClass('myserver','myuser','mypass')
self.mocked_inst = mock_obj.return_value
def testRaisesMyCustomException(self):
self.mocked_inst.simple_bind_s = MagicMock()
# set our side effect to the ldap exception to raise
self.mocked_inst.simple_bind_s.side_effect = INVALID_CREDENTIALS
self.assertRaises(mymodule.MyCustomException, self.ldapserver.connect)
def testMyNextTestCase(self):
# blah blah
Führt mich zu ein paar Fragen:
- Sieht das richtig aus :) :)
- Ist das der richtige Weg, um ein Objekt zu verspotten, das innerhalb der Klasse, die ich teste, instanziiert wird?
- Ist es in Ordnung, den @ Patch-Dekorateur bei setUp anzurufen, oder führt dies zu seltsamen Nebenwirkungen?
- Gibt es überhaupt eine Möglichkeit, die Ausnahme ldap.INVALID_CREDENTIALS auszulösen, ohne die Ausnahme in meine Testfalldatei importieren zu müssen?
- Sollte ich stattdessen patch.object () verwenden und wenn ja, wie?
Vielen Dank.
python
unit-testing
mocking
sjmh
quelle
quelle
import ldap
stattdessen und setzenside_effect = ldap.INVALID_CREDENTIALS
?Antworten:
Sie können
patch()
als Klassendekorateur verwenden, nicht nur als Funktionsdekorateur. Sie können dann die verspottete Funktion wie zuvor übergeben:@patch('mymodule.SomeClass') class MyTest(TestCase): def test_one(self, MockSomeClass): self.assertIs(mymodule.SomeClass, MockSomeClass)
Siehe: Anwenden des gleichen Patches auf jede Testmethode (in der auch Alternativen aufgeführt sind)
Es ist sinnvoller, den Patcher auf setUp auf diese Weise einzurichten, wenn das Patching für alle Testmethoden durchgeführt werden soll.
quelle
setUp()
Methode bereits vorhanden ist. DAS IST NICHT DER FALL; Mocks auf Klassenebene werden nicht rechtzeitig für die Verwendung in angewendetsetUp()
. Ich habe das Problem gelöst, indem ich stattdessen eine Hilfsmethode erstellt habe, die ich in all meinen Tests verwende. Ich bin mir nicht sicher, ob dies der beste Ansatz ist, aber er funktioniert.Ich beantworte zunächst Ihre Fragen und gebe dann ein detailliertes Beispiel dafür, wie
patch()
und wie SiesetUp()
interagieren.@patch()
Dekorateur fast nie verwendensetUp()
. Sie haben Glück gehabt, denn das Objekt wird in erstelltsetUp()
und wird während der Testmethode nie erstellt.patch.object()
. Sie können damit nur Attribute eines Objekts patchen, anstatt das Ziel als Zeichenfolge anzugeben.Um meine Antwort auf Frage 3 zu erweitern, besteht das Problem darin, dass der
patch()
Dekorateur nur angewendet wird, während die dekorierte Funktion ausgeführt wird. Sobald SiesetUp()
zurückkehren, wird der Patch entfernt. In Ihrem Fall funktioniert das, aber ich wette, es würde jemanden verwirren, der sich diesen Test ansieht. Wenn Sie wirklich nur möchten, dass der Patch während ausgeführt wirdsetUp()
, würde ich vorschlagen, diewith
Anweisung zu verwenden, um zu verdeutlichen, dass der Patch entfernt wird.Das folgende Beispiel enthält zwei Testfälle.
TestPatchAsDecorator
zeigt, dass beim Dekorieren der Klasse der Patch während der Testmethode angewendet wird, jedoch nicht währendsetUp()
.TestPatchInSetUp
zeigt, wie Sie den Patch so anwenden können, dass er sowohl während als auch währendsetUp()
der Testmethode vorhanden ist. Durch das Aufrufenself.addCleanUp()
wird sichergestellt, dass der Patch während entfernt wirdtearDown()
.import unittest from mock import patch @patch('__builtin__.sum', return_value=99) class TestPatchAsDecorator(unittest.TestCase): def setUp(self): s = sum([1, 2, 3]) self.assertEqual(6, s) def test_sum(self, mock_sum): s1 = sum([1, 2, 3]) mock_sum.return_value = 42 s2 = sum([1, 2, 3]) self.assertEqual(99, s1) self.assertEqual(42, s2) class TestPatchInSetUp(unittest.TestCase): def setUp(self): patcher = patch('__builtin__.sum', return_value=99) self.mock_sum = patcher.start() self.addCleanup(patcher.stop) s = sum([1, 2, 3]) self.assertEqual(99, s) def test_sum(self): s1 = sum([1, 2, 3]) self.mock_sum.return_value = 42 s2 = sum([1, 2, 3]) self.assertEqual(99, s1) self.assertEqual(42, s2)
quelle
Wenn Sie viele Patches anwenden müssen und diese auch auf Dinge angewendet werden sollen, die mit den setUp-Methoden initialisiert wurden, versuchen Sie Folgendes:
def setUp(self): self.patches = { "sut.BaseTestRunner._acquire_slot": mock.Mock(), "sut.GetResource": mock.Mock(spec=GetResource), "sut.models": mock.Mock(spec=models), "sut.DbApi": make_db_api_mock() } self.applied_patches = [mock.patch(patch, data) for patch, data in self.patches.items()] [patch.apply for patch in self.applied_patches] . . rest of setup . def tearDown(self): patch.stopall()
quelle
patch.stop_all()
intearDown()
.for patch in self.applied_patches: patch.start()
stopall
nichtstop_all
.self.addCleanup(patch.stopall)
Ich möchte auf eine Variation der akzeptierten Antwort hinweisen, in der ein
new
Argument an denpatch()
Dekorateur weitergegeben wird:from unittest.mock import patch, Mock MockSomeClass = Mock() @patch('mymodule.SomeClass', new=MockSomeClass) class MyTest(TestCase): def test_one(self): # Do your test here
Beachten Sie, dass in diesem Fall nicht mehr
MockSomeClass
zu jeder Testmethode das zweite Argument hinzugefügt werden muss, wodurch viel Code-Wiederholung eingespart werden kann.Eine Erklärung hierzu finden Sie unter https://docs.python.org/3/library/unittest.mock.html#patch :
Die Antworten lassen vor allem neue aus , aber es kann bequem sein, sie aufzunehmen.
quelle
Sie können eine gepatchte innere Funktion erstellen und von dort aus aufrufen
setUp
.Wenn Ihre ursprüngliche
setUp
Funktion ist:def setUp(self): some_work()
Dann können Sie es patchen, indem Sie es ändern in:
def setUp(self): @patch(...) def mocked_func(): some_work() mocked_func()
quelle
patch
als Kontextmanager verwenden?with patch(...):