Comment se moquer de Python méthodes statiques et les méthodes de la classe
Comment puis-je simuler une classe qui a délié les méthodes? Par exemple, cette classe a @classmethod
et un @staticmethod
:
class Calculator(object):
def __init__(self, multiplier):
self._multiplier = multiplier
def multiply(self, n):
return self._multiplier * n
@classmethod
def increment(cls, n):
return n + 1
@staticmethod
def decrement(n):
return n - 1
calculator = Calculator(2)
assert calculator.multiply(3) == 6
assert calculator.increment(3) == 4
assert calculator.decrement(3) == 2
assert Calculator.increment(3) == 4
assert Calculator.decrement(3) == 2
Ci-dessus assez bien décrit ma question. Ce qui suit est un exemple qui montre les choses que j'ai essayé.
Classe Machine
contient une instance de Calculator
. Je vais tester le Machine
avec un simulacre de Calculator
. Pour montrer ma question, Machine
appelle la unbound méthodes via une instance de Calculator
et via le Calculator
classe:
class Machine(object):
def __init__(self, calculator):
self._calculator = calculator
def mult(self, n):
return self._calculator.multiply(n)
def incr_bound(self, n):
return self._calculator.increment(n)
def decr_bound(self, n):
return self._calculator.decrement(n)
def incr_unbound(self, n):
return Calculator.increment(n)
def decr_unbound(self, n):
return Calculator.decrement(n)
machine = Machine(Calculator(3))
assert machine.mult(3) == 9
assert machine.incr_bound(3) == 4
assert machine.incr_unbound(3) == 4
assert machine.decr_bound(3) == 2
assert machine.decr_unbound(3) == 2
Fonctionnel code ci-dessus fonctionne très bien. Suivant est la partie qui ne fonctionne pas.
J'ai créer une maquette de Calculator
à des fins de test Machine
:
from mock import Mock
def MockCalculator(multiplier):
mock = Mock(spec=Calculator, name='MockCalculator')
def multiply_proxy(n):
'''Multiply by 2*multiplier instead so we can see the difference'''
return 2 * multiplier * n
mock.multiply = multiply_proxy
def increment_proxy(n):
'''Increment by 2 instead of 1 so we can see the difference'''
return n + 2
mock.increment = increment_proxy
def decrement_proxy(n):
'''Decrement by 2 instead of 1 so we can see the difference'''
return n - 2
mock.decrement = decrement_proxy
return mock
Dans l'unité de test ci-dessous, les méthodes liées utilisation MockCalculator
comme je l'avais espéré. Toutefois, les appels à Calculator.increment()
et Calculator.decrement()
toujours utiliser Calculator
:
import unittest
class TestMachine(unittest.TestCase):
def test_bound(self):
'''The bound methods of Calculator are replaced with MockCalculator'''
machine = Machine(MockCalculator(3))
self.assertEqual(machine.mult(3), 18)
self.assertEqual(machine.incr_bound(3), 5)
self.assertEqual(machine.decr_bound(3), 1)
def test_unbound(self):
'''Machine.incr_unbound() and Machine.decr_unbound() are still using
Calculator.increment() and Calculator.decrement(n), which is wrong.
'''
machine = Machine(MockCalculator(3))
self.assertEqual(machine.incr_unbound(3), 4) # I wish this was 5
self.assertEqual(machine.decr_unbound(3), 2) # I wish this was 1
J'ai donc essayer de patch Calculator.increment()
et Calculator.decrement()
:
def MockCalculatorImproved(multiplier):
mock = Mock(spec=Calculator, name='MockCalculatorImproved')
def multiply_proxy(n):
'''Multiply by 2*multiplier instead of multiplier so we can see the difference'''
return 2 * multiplier * n
mock.multiply = multiply_proxy
return mock
def increment_proxy(n):
'''Increment by 2 instead of 1 so we can see the difference'''
return n + 2
def decrement_proxy(n):
'''Decrement by 2 instead of 1 so we can see the difference'''
return n - 2
from mock import patch
@patch.object(Calculator, 'increment', increment_proxy)
@patch.object(Calculator, 'decrement', decrement_proxy)
class TestMachineImproved(unittest.TestCase):
def test_bound(self):
'''The bound methods of Calculator are replaced with MockCalculator'''
machine = Machine(MockCalculatorImproved(3))
self.assertEqual(machine.mult(3), 18)
self.assertEqual(machine.incr_bound(3), 5)
self.assertEqual(machine.decr_bound(3), 1)
def test_unbound(self):
'''machine.incr_unbound() and Machine.decr_unbound() should use
increment_proxy() and decrement_proxy(n).
'''
machine = Machine(MockCalculatorImproved(3))
self.assertEqual(machine.incr_unbound(3), 5)
self.assertEqual(machine.decr_unbound(3), 1)
Même après la mise à jour, non liées à des méthodes voulez une instance de Calculator
comme argument:
TypeError: unbound méthode increment_proxy() doit être appelé avec la Calculatrice de l'instance en tant que premier argument (got int instance à la place)
Comment se moquer de la classe de la méthode Calculator.increment()
et méthode statique Calculator.decrement()
?
Vous devez vous connecter pour publier un commentaire.
Vous étiez de la correction du mauvais objet. Vous devez raccorder le
Calculator
de laMachine
de la classe, pas le généralCalculator
classe. Lire à ce sujet ici.C#, Java et C++ pour les programmeurs ont tendance à abuser de la classe et des méthodes statiques dans Python. Le Pythonic approche consiste à utiliser les fonctions du module.
Alors d'abord, voici la refonte du logiciel sous test, avec des méthodes
increment()
etdecrement()
que les fonctions du module. L'interface ne change pas, mais la fonctionnalité est la même:Ajouter des fonctions
increment_mock()
etdecrement_mock()
pour se moquer deincrement()
etdecrement()
:Et maintenant, pour la bonne partie. Patch
increment()
etdecrement()
pour les remplacer par leurs simulacres:staticmethod
est un python construire et savoir se moquer de ces fonctions est précieux. "Faire autre chose" n'est pas la bonne réponse, surtout en considérant que vous pouvez se moquer staticmethods.