Wie mock Python statische Methoden und Klassenmethoden

Wie kann ich simulieren Sie eine Klasse, die ungebundene Methoden? Zum Beispiel, diese Klasse hat eine @classmethod und ein @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

Oben ziemlich genau beschreibt meine Frage. Das folgende ist ein Beispiel, das veranschaulicht, die Dinge, die ich ausprobiert habe.

Klasse Machine enthält eine Instanz von Calculator. Ich werde testen Machine mit einem mock Calculator. Um zu demonstrieren, dass mein Problem Machine ruft die ungebundene Methoden über eine Instanz von Calculator und über die Calculator Klasse:

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

Alle funktionalen code oben funktioniert einwandfrei. Als Nächstes ist der Teil, der nicht funktioniert.

Ich erstelle ein mock Calculator für die Verwendung in Tests 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

In der unit-test unter die gebundenen Methoden verwenden MockCalculator wie ich gehofft hatte. Jedoch, die Anrufe zu Calculator.increment() und Calculator.decrement() noch verwenden 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

So, ich versuche den patch Calculator.increment() und 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)

Sogar nach dem patchen, ungebundene Methoden soll eine Instanz von Calculator als argument:

TypeError: unbound method increment_proxy() muss aufgerufen werden, mit Taschenrechner-Beispiel als erstes argument (got int-Instanz statt)

Wie kann ich die mock-out-Methode der Klasse Calculator.increment() und statische Methode Calculator.decrement()?

InformationsquelleAutor John McGehee | 2014-04-24

Schreibe einen Kommentar