2017-12-11 14 views
1

데이터베이스에서 데이터를 가져 오는 데이터 액세스 클래스가 있습니다. 그것은 mixin 즉 ReferenceData로 구현됩니다.이 클래스를 MockReferenceData 클래스로 바꾸려면 단위 테스트에서 DB에 액세스 할 필요가 없습니다. 그러나 작동하지 않습니다.런타임에 Python Mixin 클래스 바꾸기

* 참고 : 종속성 삽입을 사용할 수 없습니다. 팀은 매우 큰 코드 변경이 될 것이라고 생각하면서 사용하지 않으려합니다. 그래서 런타임에 mixin을 대체하려고합니다. 풋 아웃

class MockReferenceData(object): 
    def dbName(self): 
    return 'mock' 

    def totalNumberOfSeats(self): 
    return '10' 

class ReferenceData(object): 
    def dbName(self): 
    return 'real DB' 

    def totalNumberOfSeats(self): 
    return 'Fetch from DB' 

class Car(ReferenceData): 
    def showNumberOfSeats(self): 
    print self.totalNumberOfSeats() 


class Train(ReferenceData): 
    def showNumberOfSeats(self): 
    print self.totalNumberOfSeats() 

c = Car() 
c.showNumberOfSeats() 
t = Train() 
t.showNumberOfSeats() 

def extend_instance(obj, cls): 
    """Apply mixins to a class instance after creation""" 
    base_cls = obj.__class__ 
    base_cls_name = obj.__class__.__name__ 
    obj.__class__ = type(base_cls_name, (base_cls, cls),{}) 

extend_instance(c, MockReferenceData) 
c.showNumberOfSeats() // output now should be 10 

은 다음과 같습니다

Fetch from DB 
Fetch from DB 
Fetch from DB 
내가 할 수있는 새로운 조롱 수준의 출력을 가리 키도록 extend_instance에게 방법을 사용하는 것 같이 내가 기대했다

:

Fetch from DB 
Fetch from DB 
10 

답변

0

당신이 볼 수 Car 당신이 클래스의 MRO 보면 :

>>> Car.__mro__ 
(<class '__main__.Car'>, <class '__main__.ReferenceData'>, <type 'object'>) 

그래서 방법이 object에 마지막으로 ReferenceData에 다음 Car에 처음으로 고개 있습니다.

(나는 명확성을 위해 이름으로 NewCar 사용) 새로운 클래스의 MRO이 비교 :

>>> type('NewCar', (Car, MockReferenceData), {}).__mro__ 
(<class '__main__.NewCar'>, <class '__main__.Car'>, <class '__main__.ReferenceData'>, <class '__main__.MockReferenceData'>, <type 'object'>) 

이는 Car 클래스의 MRO를 포함한다. 여기에있는 메소드는 Car에서 계속 찾은 다음 ReferenceData에서 찾을 수 있으므로 totalNumberOfSeatsReferenceData에서 찾을 수 있으며 MockReferenceData에서 구현을 사용하지 않습니다.

은 당신이 할 수있는 것은 전에 MRO Car 클래스를 당신의 모의 클래스를 삽입하는 것입니다

>>> type('NewCar', (MockReferenceData, Car), {}).__mro__ 
(<class '__main__.NewCar'>, <class '__main__.MockReferenceData'>, <class '__main__.Car'>, <class '__main__.ReferenceData'>, <type 'object'>) 

이제 방법은 MockReferenceData 클래스에 고개를 befirst하고 그들이 존재하지 않는 경우 다시이 가을 이전 버전으로. 따라서이 extend_instance 방법이 간단한 경우에 작동합니다 :

def extend_instance(obj, cls): 
    """Apply mixins to a class instance after creation""" 
    obj.__class__ = type(obj.__class__.__name__, (cls, obj.__class__),{}) 
0

돈 ' 상속을 사용하지 마십시오. 많은 경우에 과도하게 사용 된 추상화이며 이는 예제입니다. 의존성 주입을 사용하면 실제 DB를 쉽게 기본값으로 설정할 수 있지만 백엔드를 조롱 한 것으로 바꿀 수 있습니다.

class MockReferenceData(object): 
    def dbName(self): 
    return 'mock' 

    def totalNumberOfSeats(self): 
    return '10' 

class ReferenceData(object): 
    def dbName(self): 
    return 'real DB' 

    def totalNumberOfSeats(self): 
    return 'Fetch from DB' 

class Car(object): 

    def __init__(self, datasource_class=ReferenceData): 
     self._datasource = datasource_class() # instead of creation here, you could also pass in an 

    def showNumberOfSeats(self): 
     print(self._datasource.totalNumberOfSeats()) 


real_car = Car() 
real_car.showNumberOfSeats() 

mocked_car = Car(datasource_class=MockReferenceData) 
mocked_car.showNumberOfSeats() 

또한 Python의 코딩 규칙에 대해서는 PEP8을 따르십시오. "잘못된"들여 쓰기 깊이를 2로 사용하면 나와 같은 동료 개발자에게 편집 문제가 발생합니다. method-names도 동일합니다.

+0

안녕하세요, 문제는 코드 기반이 매우 오래되고 매우 큽니다. 나는 DI를 사용하도록 요청했지만 팀은 그것을 사용하지 않을 것입니다. 그래서 런타임에 mixin을 변경하려고합니다. 가능한가? – Rahul

+0

@ Rahul 본 것처럼, 가능합니다. 나는 여전히 그것을하지 않을 것이다.이와 같이 클래스를 수정하면 모든 종류의 디버깅 문제를 해결할 수 있습니다. 팀이 주장한다면, 그들은 나쁜 일을 주장합니다. 문제를 해결할 수있는 또 다른 옵션은 mixin과 원래 자동차를 상속 한 자동차의 하위 클래스를 만드는 것입니다. 전역 인터프리터 상태를 수정하는 대신 명시적인 클래스를 만들기 때문에보다 안전합니다. – deets

+0

감사합니다. @deets, 나는 그것이 가장 깨끗한 방법이 아니라는 데 동의합니다. 하지만 팀이 원하지 않는다면 나는 선택의 여지가 없습니다. 또한 유닛 테스트에서이 클래스를 확장한다면 설정에서 오브젝트를 만들고 티 아웃에서 파괴 할 때 어떤 영향도 미치지 않을 것이라고 생각합니다. 어떻게 생각해? – Rahul

0

덕분에 나는 또한 내가 순서를 변경하고 일 같은했다 @mata.

def extend_instance(obj, cls): 
    """Apply mixins to a class instance after creation""" 
    base_cls = obj.__class__ 
    base_cls_name = obj.__class__.__name__ 
    obj.__class__ = type(base_cls_name, (cls, base_cls),{})