How to simulate static methods and methods of the Python class

How do I make fun of a class that has unrelated methods? For example, this class has @classmethod and @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 describes my question in some detail. Below is a working example demonstrating the things I tried.

The Machine class contains an instance of Calculator . I will test Machine with the Calculator layout. To demonstrate my problem, Machine calls unrelated methods through the Calculator instance and through the Calculator class:

 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 functional codes above work fine. The next part does not work.

I am creating a mock Calculator for use in testing 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, related methods use the MockCalculator as I hoped. However, calls to Calculator.increment() and Calculator.decrement() still use 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'm trying to set Calculator.increment() and 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 fixing, unrelated methods want an instance of Calculator as an argument:

TypeError: unbound increment_proxy () method should be called with the Calculator instance as the first argument (int int is used instead)

How do I make fun of the Calculator.increment() class method and the static Calculator.decrement() method?

+8
python unit-testing mocking
source share
2 answers

C #, Java, and C ++ programmers tend to abuse classes and static methods in Python. Pythonic's approach is to use module features.

So, firstly, here we are testing the reorganized software with the increment() and decrement() methods as module functions. The interface is changing, but the functionality is the same:

 # 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 increment_mock() and decrement_mock() functions to 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 most part. Patch increment() and decrement() to replace them with their layouts:

 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) 
0
source share

You corrected the wrong object. You should fix the Calculator from the Machine class, not the general Calculator class. Read about it here .

 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 
+6
source share

All Articles