2010-05-25 5 views
4

저는 C++로 RPC 미들웨어를 작성하고 있습니다. 나는 내부 소켓 클라이언트를 포함하는 RPCClientProxy라는 클래스가 있습니다TDD, 단위 테스트 및 아키텍처 변경

class RPCClientProxy { 
    ... 
    private: 
    Socket* pSocket; 
    ... 
} 

생성자 : 당신이 볼 수 있듯이

RPCClientProxy::RPCClientProxy(host, port) { 
    pSocket = new Socket(host, port); 
} 

가, 내가 내부 소켓을 가지고있는 사용자에게 필요하지 않습니다 .

내 프록시에 단위 테스트를하려면 소켓 용 모의 객체를 만들어 프록시에 전달해야하므로 setter를 사용하거나 프록시 생성자의 소켓에 팩토리를 전달해야합니다.

내 질문 : TDD에 따르면, 테스트 때문에 만 가능합니까? 보시다시피, 이러한 변화는 프로그래머가 라이브러리를 사용하는 방식을 변경합니다.

+0

당신이 볼 수 있듯이 질문에 "정확한"대답이 없을 수도 있습니다. 당신은 찬성과 반대 의견에 무게를 달아야합니다. –

답변

3

당신이 묘사 한 것은 완벽하게 정상적인 상황이며, 프로덕션 코드에 영향을 미치지 않는 방식으로 테스트를 구현하는 데 도움이되는 확립 된 패턴이 있습니다.

이 문제를 해결하는 한 가지 방법은 Test Specific Subclass을 사용하는 것입니다. 여기서 소켓 멤버에 대한 설정자를 추가하고 테스트의 경우 모의 소켓을 사용할 수 있습니다. 물론 변수를 개인이 아닌 보호해야 할 필요가 있지만 큰 문제는 아닙니다. 예를 들어 :

class RPCClientProxy 
{ 
    ... 
    protected: 
    Socket* pSocket; 
    ... 
}; 

class TestableClientProxy : public RPCClientProxy 
{ 
    TestableClientProxy(Socket *pSocket) 
    { 
     this->pSocket = pSocket; 
    } 
}; 

void SomeTest() 
{ 
    MockSocket *pMockSocket = new MockSocket(); // or however you do this in your world. 
    TestableClientProxy proxy(pMockSocket); 
    .... 
    assert pMockSocket->foo; 
} 

결국 당신이 자주 (종종 C++에 비해)가 테스트 할 수 있도록하는 방식으로 코드를 설계해야하고 아무 문제가 함께 없다는 사실에 온다 그. 이러한 결정이 공개 인터페이스에 누출되는 것을 피할 수 있다면 가끔은 더 좋을 수도 있지만 다른 경우에는 특정 인스턴스에 대한 액세스를 제공하기 위해 싱글 톤을 사용하여 위와 같은 생성자 매개 변수를 통한 종속성 주입을 선택하는 것이 좋습니다. .

사이드 노트 : xunitpatterns.com 사이트의 나머지 부분을 살펴볼 필요가 있습니다. 이전에 있었던 사람들의 지식을 이해하고 잘 이해할 수있는 잘 구성된 단위 테스트 패턴이 많이 있습니다. 당신은 :)

나는 당신이 그것을 할 모의 소켓을 통해 테스트 혜택을 누릴 것이라고 생각하면 내가 말을 특정 규범을 준수하지 않는
+0

이것은 아마 갈 가장 좋은 경로입니다 – Gutzofter

+0

그 ' RPCClientProxy에 기본 생성자가 없으면 작동하지 않습니다. –

+0

@ 노아 로버츠 : 확실하지 않습니다. 그것은 필요에 따라 적용되는 예입니다! 여전히 특정 테스트 하위 클래스를 가질 수 있으며 추가 매개 변수를 추가하는 생성자를 구현하여 아이디어에 아무런 문제가 없도록 할 수 있습니다. – jkp

3

, 당신은 병렬 생성자

RPCClientProxy::RPCClientProxy(Socket* socket) 
{ 
    pSocket = socket 
} 

또 다른 옵션을 구현할 수 당신이 인증서를 기대하도록 구성 할 수있는 테스트를 위해 연결할 호스트를 구현하는 것입니다. ain 메시지

3

귀하의 문제는 디자인의 문제입니다.

Socket에 대한 다른 동작을 구현 한 경우 소켓을 생성 한 모든 코드를 다시 작성해야하므로 토스트됩니다.

일반적으로 추상 기본 클래스 (인터페이스) Socket을 사용하고 추상 팩토리를 사용하여 상황에 따라 원하는 소켓을 만듭니다. 공장 자체는 Singleton 일 수도 있지만 (Monoid를 선호 함) 또는 인수로서 (Dependency Injection의 세입자에 따라) 전달 될 수 있습니다. 후자는 테스트를 위해 훨씬 더 좋은 전역 변수가 없다는 것을 의미합니다.

그래서 난의 라인을 따라 뭔가를 권합니다 :

int main(int argc, char* argv[]) 
{ 
    SocketsFactoryMock sf; 

    std::string host, port; 
    // initialize them 

    std::unique_ptr<Socket> socket = sf.create(host,port); 
    RPCClientProxy rpc(socket); 
} 

이 클라이언트에 영향을 : 당신은 더 이상 당신이 뒤에서 소켓을 사용한다는 사실을 숨길 수 없습니다. 반면에, 사용자 정의 소켓을 개발하고자하는 클라이언트를 제어 할 수 있습니다 (로그, 트리거 동작 등)

그래서 디자인 변경이지만 TDD 자체가 원인이 아닙니다 . TDD는 더 높은 수준의 제어를 이용합니다.

또한 unique_ptr을 사용하여 표시되는 명확한 리소스 소유권에 유의하십시오.

+0

저는 공장이 마음에 들지만, 소켓은 제가 조롱하고 싶은 미들웨어의 유일한 구성 요소는 아닙니다. RPCClientProxy에서 : RPCCallHash : 클라이언트가 요청을 보내면 실행중인 호출을 나타내는 해시에 항목을 넣어야합니다. RPCCallQueue : 서버가 응답을 반환하면 미들웨어는 응답 객체를 대기열에 넣습니다. vector : 응답을 큐에서 제거하고 콜백 함수를 호출하는 스레드입니다. 스레드 수는 구성 가능합니다. 그래서, 어떻게 생각하니? 모의 실험을하고자하는 미들웨어의 모든 구성 요소를 공장으로 만드는 것이 좋은 생각일까요? – Leandro

+0

그냥 메모. 당신은 그런 상황에서 실제로 구워지는 것이 아닙니다. 새 추상화를 만들려는 지점이 클라이언트에 의해 생성되는 객체라는 가정하에 모든 생성자를 추상 팩토리로 전환 할 수 있습니다. http://stackoverflow.com/questions/2884814/is-using-a-c-virtual-constructor-generally-considered-good-practice/2884907#2884907 –

+0

또한 unique_ptr은 C++ 0x입니다. 게다가, 이것은 물론 '정확한'대답입니다. –

0

다른 사람들도 지적했듯이 공장 아키텍처 또는 테스트 특정 하위 클래스는이 상황에서 좋은 옵션입니다. 완성도를 들어, 하나의 다른 가능성은 기본 인수를 사용하는 것입니다

RGCClientProxy::RPCClientProxy(Socket *socket = NULL) 
{ 
    if(socket == NULL) { 
     socket = new Socket(); 
    } 
    //... 
} 

이 아마 어딘가에 공장 패러다임 사이의 내부 소켓 newing까지 (궁극적으로 가장 유연하지만, 사용자에게 더 고통이다)이며, 귀하의 생성자. 기존 클라이언트 코드를 수정할 필요가 없다는 이점이 있습니다.