How to mock Python static methods and class methods
如何模拟具有未绑定方法的类?例如,这个类有一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 |
上面几乎描述了我的问题。下面是一个工作示例,演示了我尝试过的事情。
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 |
上面所有的功能代码都可以正常工作。接下来是不起作用的部分。
我创建了一个用于测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 |
在下面的单元测试中,绑定方法如我所希望的那样使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
所以我试着给
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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) |
即使在修补之后,未绑定的方法也希望使用
TypeError: unbound method increment_proxy() must be called with Calculator instance as first argument (got int instance instead)
如何模拟类方法
你在修补错误的对象。您必须从
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 |
C++、Java和C++程序员往往在Python中过度使用类和静态方法。Python疗法是使用模块功能。
因此,首先,这里是正在测试的重构软件,方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | # 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 |
将函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 |
现在就好了。补丁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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) |