如何模拟 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 @classmethod
and 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 Machine
contains an instance of Calculator
. I will be testing Machine
with a mock of Calculator
. To demonstrate my issue, Machine
calls the unbound methods via an instance of Calculator
and via the Calculator
class:
类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 Calculator
to 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 MockCalculator
as 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 Calculator
as 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 Calculator
from the Machine
class, not the general Calculator
class. 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