如何模拟Python静态方法和类方法

如何模拟Python静态方法和类方法,python,unit-testing,mocking,Python,Unit Testing,Mocking,如何模拟具有未绑定方法的类?例如,此类有一个@classmethod和一个@staticmethod: class Calculator(object): def __init__(self, multiplier): self._multiplier = multiplier def multiply(self, n): return self._multiplier * n @classmethod def increment(

如何模拟具有未绑定方法的类?例如,此类有一个
@classmethod
和一个
@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
以上差不多描述了我的问题。下面是一个工作示例,演示了我尝试过的事情

计算机
包含
计算器
的实例。我将用模拟的
计算器测试
机器
。为了演示我的问题,
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
上面的所有功能代码都可以正常工作。接下来是不起作用的部分

我创建了一个模拟的
计算器
,用于测试
机器

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
在下面的单元测试中,绑定方法如我所希望的那样使用
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
所以我尝试修补
Calculator.increment()
Calculator.decrement()

即使在修补之后,未绑定的方法也需要一个
Calculator
的实例作为参数:

TypeError:必须使用计算器实例作为第一个参数调用未绑定的方法increment_proxy()(改为使用int instance)


如何模拟类方法
Calculator.increment()
和静态方法
Calculator.decrement()

您修补了错误的对象。您必须从
机器
类而不是常规
计算器
类中修补
计算器
。读一读

C++,java和C++程序员往往过度使用Python中的类和静态方法。Pythonic方法是使用模块函数

首先,这里是正在测试的重构软件,使用方法
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
将函数
increment\u mock()
decrement\u mock()
添加到mock
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)
一种方法是

def test_increment(mocker):
    mocker.patch.object(Calculator, attribute='increment', return_value=10)
    ...actual test code...

谢谢你的回复。我正在测试类机器,所以对Machine.mult()之类的方法进行修补并不令人满意。此外,mock MockComputer.multiplier()工作正常。我的问题是关于模拟或修补静态和类方法Computer.increment()和Computer.decrement()。这是正确的答案。[关于另一个问题]讨论了静态方法与模块方法的问题](),得出的结论是,静态方法是一种代码味道,是对Java风格的模仿,在Java风格中,模块函数定义不存在,静态方法是唯一的替代品。这并没有回答问题
staticmethod
是一个有效的python构造,了解如何模拟这些函数很有价值。“做点别的”不是正确的答案,尤其是考虑到你可以模拟静态方法。读了这篇文章,我意识到我也过度使用了静态方法,而我本应该使用模块函数。尽管这个问题似乎离题了,但它确实帮助了我,谢谢。。。。Java开发人员与Python中过度使用静态方法有什么关系?优秀的Java开发人员都知道不要过度使用静态方法,因为它很难测试。我个人主要使用它来编写有用的简单逻辑代码,这些代码可以被重用很多时间,然后您可以将它们分组到一些util、helpers类中。我很少看到我需要模拟静态代码,因为它应该是简单的,并根据它们的逻辑进行测试。所有调用静态代码的类,都需要在调用前后测试它们的输出,而不是模拟静态代码本身。我知道这很旧,但静态方法在返回实例或获取实例时不是“代码气味”(我讨厌这个短语,对不起),所以请具体点,以免初级程序员误入歧途。
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
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)
def test_increment(mocker):
    mocker.patch.object(Calculator, attribute='increment', return_value=10)
    ...actual test code...