如何模拟 Python 静态方法和类方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23257470/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to mock Python static methods and class methods
提问by John McGehee
How do I mock a class that has unbound methods? For example, this class has a @classmethodand a @staticmethod:
如何模拟具有未绑定方法的类?例如,这个类有 a@classmethod和 a @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
The above pretty much describes my question. The following is a working example that demonstrates the things I have tried.
以上几乎描述了我的问题。以下是一个工作示例,演示了我尝试过的事情。
Class Machinecontains an instance of Calculator. I will be testing Machinewith a mock of Calculator. To demonstrate my issue, Machinecalls the unbound methods via an instance of Calculatorand via the Calculatorclass:
类Machine包含 的实例Calculator。我将Machine使用模拟进行测试Calculator。为了证明我的问题,Machine呼吁通过实例的非绑定方法Calculator,并通过Calculator类:
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
All the functional code above works fine. Next is the part that does not work.
上面的所有功能代码都可以正常工作。接下来是不起作用的部分。
I create a mock of Calculatorto use in testing Machine:
我创建了一个Calculator用于测试的模拟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 the unit test below, the bound methods use MockCalculatoras I had hoped. However, the calls to Calculator.increment()and Calculator.decrement()still use Calculator:
在下面的单元测试中,绑定方法MockCalculator如我所愿地使用。但是,调用Calculator.increment()并 Calculator.decrement()仍然使用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 I try to patch Calculator.increment()and Calculator.decrement():
所以我尝试修补Calculator.increment()和 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)
Even after patching, the unbound methods want an instance of Calculatoras an argument:
即使在修补之后,未绑定的方法也需要一个实例Calculator作为参数:
TypeError: unbound method increment_proxy() must be called with Calculator instance as first argument (got int instance instead)
类型错误:必须使用 Calculator 实例作为第一个参数调用未绑定的方法 increment_proxy()(改为使用 int 实例)
How do I mock out class method Calculator.increment()and static method Calculator.decrement()?
如何模拟类方法Calculator.increment()和静态方法Calculator.decrement()?
采纳答案by John McGehee
C#, Java and C++ programmers tend to overuse class and static methods in Python. The Pythonic approach is to use module functions.
C#、Java 和 C++ 程序员倾向于在 Python 中过度使用类和静态方法。Pythonic 的方法是使用模块函数。
So first, here is the refactored software under test, with methods increment()and decrement()as module functions. The interface does change, but the functionality is the same:
所以首先,这里是被测试的重构软件,带有方法increment()和decrement()模块功能。界面确实发生了变化,但功能是相同的:
# Module machines
class Calculator(object):
def __init__(self, multiplier):
self._multiplier = multiplier
def multiply(self, n):
return self._multiplier * n
def increment(n):
return n + 1
def decrement(n):
return n - 1
calculator = Calculator(2)
assert calculator.multiply(3) == 6
assert increment(3) == 4
assert decrement(3) == 2
class Machine(object):
'''A larger machine that has a calculator.'''
def __init__(self, calculator):
self._calculator = calculator
def mult(self, n):
return self._calculator.multiply(n)
def incr(self, n):
return increment(n)
def decr(self, n):
return decrement(n)
machine = Machine(Calculator(3))
assert machine.mult(3) == 9
assert machine.incr(3) == 4
assert machine.decr(3) == 2
Add functions increment_mock()and decrement_mock()to mock increment()and decrement():
添加函数increment_mock()anddecrement_mock()来模拟increment()and decrement():
from mock import Mock
import machines
def MockCalculator(multiplier):
mock = Mock(spec=machines.Calculator, name='MockCalculator')
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_mock(n):
'''Increment by 2 instead of 1 so we can see the difference.'''
return n + 2
def decrement_mock(n):
'''Decrement by 2 instead of 1 so we can see the difference.'''
return n - 2
And now for the good part. Patch increment()and decrement()to replace them with their mocks:
现在是好的部分。修补increment()并decrement()用它们的模拟替换它们:
import unittest
from mock import patch
import machines
@patch('machines.increment', increment_mock)
@patch('machines.decrement', decrement_mock)
class TestMachine(unittest.TestCase):
def test_mult(self):
'''The bound method of Calculator is replaced with MockCalculator'''
machine = machines.Machine(MockCalculator(3))
self.assertEqual(machine.mult(3), 18)
def test_incr(self):
'''increment() is replaced with increment_mock()'''
machine = machines.Machine(MockCalculator(3))
self.assertEqual(machine.incr(3), 5)
def test_decr(self):
'''decrement() is replaced with decrement_mock()'''
machine = machines.Machine(MockCalculator(3))
self.assertEqual(machine.decr(3), 1)
回答by Alex Palcuie
You were patching the wrong object. You must patch the Calculatorfrom the Machineclass, not the general Calculatorclass. Read about it here.
您正在修补错误的对象。您必须Calculator从Machine类中修补,而不是一般Calculator类。在这里阅读。
from mock import patch
import unittest
from calculator import Calculator
from machine import Machine
class TestMachine(unittest.TestCase):
def my_mocked_mult(self, multiplier):
return 2 * multiplier * 3
def test_bound(self):
'''The bound methods of Calculator are replaced with MockCalculator'''
machine = Machine(Calculator(3))
with patch.object(machine, "mult") as mocked_mult:
mocked_mult.side_effect = self.my_mocked_mult
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(Calculator(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

