2011-03-09 5 views
4

내 클래스 A는 여기 클래스 B. 에 의존하는 코드 나는 다음과 같은 솔루션 (단위 테스트)이 종속성을 중단 할단위 테스트를 위해이 종속성을 깨는 방법이 있습니까?

//declaration 
class A 
{ 
    public: 
    A(B *b); 
    ~A(); 
    void m1(); 
    private: 
    B *ptr_b; 
}; 

//implementation 
A::A(B *b) 
{ 
    ptr_b = b; 
} 

A::~A() 
{ 
    delete ptr_b; 
} 

void A::m1() 
{ 
    ptr_b->m2(); 
} 

입니다. 다음은 코드

class FakeB : public B 
    {  
    public: 
     FakeB(); 
     ~FakeB(); 
     virtual void m2() = 0; 
    }; 

class StubB : public FakeB 
{ 
    public: 
     StubB(); 
     ~StubB(); 
     void m2(); 
} 

입니다하지만()

A *ptr_a = new A(new StubB); 
ptr_a->m1(); 

방법 (M1)를 클래스 A의 인스턴스를 다음과 같은 코드의 방법 M1()를 호출하는 경우는 B의 방법 m2() B의 m2 때문에을 (호출) 가상하지 않습니다. 클래스 B는 다른 모듈의 레거시 코드입니다. 코드 을 변경하고 싶지 않지만 클래스 A의 코드를 변경하고 싶지는 않습니다.

이 종속성을 벗어나는 해결책은 무엇입니까?

답변

6

먼저 A 클래스의 생성자에 new B()이 없으므로 클래스 A의 소멸자에 delete ptr_b;이있는 것은 좋지 않은 디자인입니다. 즉, A 인스턴스가 생성 될 때마다 B 개체의 소유권을 A를 사용하여 A를 사용하는 사람이 내부를 모르는 경우 delete 중복 위험이 발생할 수 있습니다.

둘째, "실제 B"가 아닌 "스텁"(또는 "가짜"또는 "가짜") 객체를 제공하려는 경우 BFakeB은 B의 모든 메소드 가상 방법으로 필요 :

class FakeB : public InterfaceB 

class B : public InterfaceB 

이렇게 (A)의 모든 멤버 함수 타입 InterfaceB * 대신 B *의 파라미터를 사용할 수있다. 그런 다음 AFakeB 개체를 주입하는 것이 분명 쉽습니다.

불행히도, 그것은 당신이 B (적어도 조금)를 변경해야한다는 것을 의미합니다.

class WrapperB: public InterfaceB 
{ 
    B _b; 
public: 
    WrapperB(/* some parameters */) : _b(/* same parameters */){} 

    // Here you need to implement all methods of 
    // InterfaceB and delegate them to the original method calls 
    // of _b. You should give them the same name and signature as 
    // the corresponding (non-virtual) methods in B. 
    // For example, if there is a method m2 in B, 
    // there should be a pure virtual method m2 in InterfaceB, and 
    // an implementation here like this: 
    virtual void m2(){ _b.m2(); } 
}; 

WrapperB은 매우 간단에만 포함, 간단한 방법 : 그 옵션이없는 경우, 일부 클래스 WrapperB (이 대부분 고전 Adapter pattern에서와 같은 생각이다)에 의해 B 포장의 가능성은 항상있다 단위 테스트를 생략 할 수있는 위임 코드 그리고 A와 함께 사용할 때 B 대신 WrapperB을 사용해야합니다. 그러나 얻을 수있는 것은 완벽하게 단위 테스트 할 수있는 class A입니다.

당신이 그것으로 외부에서 B 객체에 대한 참조를 주입 할 경우 또 다른 (어쩌면 더 나은) 변형 방식으로 WrapperB 클래스를 건설하고있다 :

class WrapperB: public InterfaceB 
{ 
    B& _b; 
public: 
    WrapperB(B& b) :_b(b){} 

    // implement InterfaceB methods as above 
    virtual void m2(){ _b.m2(); } 

} 

당신은 단지 다음과 같이 사용할 수 있습니다 :

B b; 
A a(WrapperB(b)); 

FakeB fb; 
A a_for_test(fb); 
+1

두 번째 솔루션을 좀 더 자세히 설명 하시겠습니까? – metdos

+0

나쁜 디자인 트릭을 가져 주셔서 감사합니다. 또한 두 번째 해결책에 대한 추가 설명이 필요합니다. – onurozcelik

1

종속성을 깨뜨릴 가능성은 메이크 파일의 포함 경로를 변경하고 클래스 B 버전을 포함시키는 것입니다.이 방법이 유닛 테스트 체계에서 작동하는지는 알 수 없습니다.

2

Merhaba하기 Onur

또 다른 아이디어는 정상 및 단위 테스트 모드 사이 클래스에게 코드를 전환 할 몇 가지 처리기 기호를 사용하는 것입니다. 예를 들어 :

파일 A.hpp

#ifndef UNIT_TESTING 
# include "B.hpp" // contains "normal" class B 
#else 
# include "Testable_B.hpp" // contains "fake" class B, dedicated for unit testing. 
#endif 

UNIT_TESTING는 단위 테스트를 작성할 때 만 가능하게 할 전 처리기 기호 것입니다.

Testable_B.hpp 파일에 "B"이외의 다른 이름 (예 : Testable_B)의 클래스가있는 경우 이러한 지시문을 A 클래스의 정의에 추가해야합니다. 단점은 필요하다면, 이것은 클래스 정의에서 엉망이 될 것이다.

는 또 다른 방법은 형식 정의 사용하는 것입니다 :

#ifndef UNIT_TESTING 
# include "B.hpp" // contains "normal" class B 
#else 
# include "Testable_B.hpp" // contains "fake" class B, dedicated for unit testing. 
    typedef Testable_B B; 
#endif 

나는 그것이 매우 우아한 해결책 아니라는 것을 알고,하지만 어쩌면 당신은 유용 당신이 클래스에게 코드를 수정하지 않으려면 찾을 수 있습니다. 소스 코드를 전혀 변경하고 싶지 않다면 아마도 stefaanv의 해결책이 될 것입니다.

+0

Merhaba tomac. 나는 당신이 터키어를 조금은 알고 있다고 생각한다. 나는 최소한 A의 코드를 약간 바꾸어야한다고 생각한다. – onurozcelik

관련 문제