2014-04-24 2 views
7

언 바운드 메소드가있는 클래스를 조롱하려면 어떻게해야합니까? 예를 들어,이 클래스는 @classmethod@staticmethod 있습니다 위의 꽤 많이 내 질문에 설명Python 정적 메소드 및 클래스 메소드 모의 방법

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의 인스턴스가 포함됩니다. 나는 Machine을 모의하고 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 

: Calculator의 인스턴스를 통해와 Calculator 클래스를 통해 언 바운드 메소드를 호출 Machine 내 문제를 설명합니다. 다음은 작동하지 않는 부분입니다.

나는 시험 Machine에서 사용할 Calculator의 모의를 만들 : 내가 기대했던대로 아래의 단위 테스트에서

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() 패치하려고 :

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) 

, 언 바운드 방법은 인수로 Calculator의 인스턴스를 원하는 심지어 패치 후 :

TypeError: unbound method increment_proxy() must be called with Calculator instance as first argument (got int instance instead)

클래스 방법 Calculator.increment() 및 고정 방법 Calculator.decrement()?

답변

2

해결책은 대신 모듈 기능을 사용하는 것입니다. 모듈 기능은 파이썬 적입니다. 클래스 및 정적 메서드의 과도한 사용은 C#의 과거 경험의 영향을 받았습니다.

먼저, 테스트중인 리팩토링 된 소프트웨어를 모듈 기능으로 increment()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 

을 그리고 :

# 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_mock()을 추가하고 decrement_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) 
+2

정답입니다. [다른 질문에] 정적 메서드 대 모듈 방법 문제] (http://programmers.stackexchange.com/questions/112137/is-staticmethod-proliferation-a-code-smell)에 대해 논하고 결론은 정적 메서드가 코드 냄새와 모듈 스타일 정의가없고 정적 메소드가 유일한 대체품 인 자바 스타일의 모방. –

+14

이것은 질문에 대답하지 않습니다. 'staticmethod'는 유효한 파이썬 구조이며 모방하는 법을 아는 것이 중요합니다. 정적 방법을 모의 할 수 있다고 생각하면 "다른 일을하십시오"는 정답이 아닙니다. – AnilRedshift

5

잘못된 개체를 패치하고 있습니다. Calculator 클래스는 일반 Calculator 클래스가 아닌 Machine 클래스에서 패치해야합니다. 그것에 대해 읽어보십시오 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 
+0

감사의 말을 보내 주셔서 감사합니다.Machine 클래스를 테스트 중이므로 Machine.mult()와 같은 메소드를 패치하는 것은 만족스럽지 않습니다. 게다가 모의 MockComputer.multiplier()는 잘 동작합니다. 내 질문은 정적 및 클래스 메서드를 Computer.increment() 및 Computer.decrement()에 조롱하거나 패치하는 것입니다. –