인터페이스가없는 클래스를 어떻게 모의합니까?


82

Windows 7에서 C #을 사용하여 .NET 4.0에서 작업하고 있습니다.

mock을 사용하여 일부 방법 간의 통신을 테스트하고 싶습니다. 유일한 문제는 인터페이스를 구현하지 않고하고 싶다는 것입니다. 가능합니까?

모의 객체에 대한 많은 주제와 튜토리얼을 읽었지만 모두 클래스가 아닌 인터페이스를 모의하는 데 사용되었습니다. Rhino와 Moq 프레임 워크를 사용해 보았습니다.


1
이러한 도구가 "IInterfaces"를 독점적으로 사용하는 관점에서 만들어 졌다는 사실은 정말 놀랍습니다.
AR

4
인터페이스 기반 DI를 사용한다고 가정하여 생성되었습니다. 이것은 요즘 꽤 표준적인 패턴입니다.
Maess dec. 05

불행하게도, 불변의 유형 "패턴":(와 그 패턴의 충돌
매튜 왓슨

3
여기에 언급 된 여러 방법하지 있습니다 이 답변이
BlueRaja - 대니 Pflughoeft

답변:


67

가짜가 필요한 모든 방법을 virtual비공개가 아닌 것으로 표시하기 만하면 됩니다. 그러면 메서드를 재정의 할 수있는 가짜를 만들 수 있습니다.

사용 new Mock<Type>하고 매개 변수가없는 생성자가없는 경우 매개 변수를 위의 호출 인수로 전달할 수 있습니다.param Objects


1
이것은 내가 조롱하려는 모든 클래스에 대한 인터페이스를 만드는 것이 전혀 필요한지 궁금해합니다. 인터페이스가 없으면 조롱을 위해 구체적인 클래스를 사용할 수 없습니까?
orad

14
@orad 사실 저는 클래스를 먼저 만들고 공통 기능을 분리해야 할 때만 인터페이스를 만드는 경향이 있습니다.
저스틴 Pihony

그런 다음 지정된 유형을 예상하는 객체에 모의 객체를 어떻게 전달합니까? 모의 'new Mock <MyType>'을 만들고 MyType을 기대하는 개체에 전달하려고하면 "Cannot convert Mock <MyType> to MyType"오류가 발생합니다.
Neutrino

2
모의를 'var myMock = new Mock <MyType> ()'으로 정의하면 모의를 myMock.Object로 사용하는 클래스에 전달해야합니다.
Neutrino

4
주요 문제는 매개 변수없는 생성자가없고 닫힌 Concrete 클래스의 매개 변수화 된 생성자가 공용이 아닐 때 발생합니다. (여기서 '닫힌'이란 외부 패키지를 의미합니다.) 그런 다음 인터페이스를 구현할 수없고 해당 메서드를 가상으로 만들 수 없으며 매개 변수가있는 생성자를 사용할 수도 없습니다.
Abhilash Pokhriyal

22

대부분의 모의 프레임 워크 (Moq 및 RhinoMocks 포함)는 모의 클래스를 대신하여 프록시 클래스를 생성하고 사용자가 정의한 동작으로 가상 메서드를 재정의합니다. 이로 인해 구체적 또는 추상 클래스의 인터페이스 또는 가상 메서드 만 모의 처리 할 수 ​​있습니다. 또한 구체적인 클래스를 모의하는 경우 모의 프레임 워크가 클래스를 인스턴스화하는 방법을 알 수 있도록 거의 항상 매개 변수없는 생성자를 제공해야합니다.

코드에서 인터페이스 생성을 싫어하는 이유는 무엇입니까?


107
테스트 프레임 워크의 기술적 제한이 아니라면 완전히 불필요 할 수있는 수많은 인터페이스로 코드베이스를 막기 때문입니까?
BlueRaja-Danny Pflughoeft

9
"손이 닿는 것이 이해를 뛰어 넘는다"라는 표현을 들었습니다. 이것이 개발자들이 항상 불평하는 이유를 설명합니다. C # 언어는 단위 테스트를 염두에두고 설계되지 않았습니다. 그렇기 때문에 무의미한 인터페이스 나 가상 메서드가 엄청나게 복잡하지 않으면 모의 종속성을 주입하기가 정말 어렵습니다. 아마도 누군가는 곧 단위 테스트가 쉬운 언어를 발명하게 될 것입니다. 그때까지 우리는 불평 할 다른 무언가를 갖게 될 것입니다.
John Henckel

13
인터페이스와 가상 메서드의 유일한 목적이 테스트를 제공하는 것이라면 어수선하다고 생각하는 사람이 나만이 아니라는 것을 알게되어 기쁩니다.
aaaaaa

13
"오직 목적은 테스트를 제공하는 것"이지만 테스트 가능한 코드를 만드는 것이 중요하지 않습니까?
MakkyNZ

13
"왜 당신의 코드에서 인터페이스를 만드는 것을 싫어하는가?" 이 개체를 작성하지 않았고 코드에 액세스 할 수 없기 때문입니다. 인터페이스를 구현하지 않는 내가 소유하지 않은 닫힌 개체의 모의를 만들어야합니다.
Sean Worle

16

MoQ를 사용하면 구체적인 클래스를 모의 할 수 있습니다.

var mocked = new Mock<MyConcreteClass>();

그러나이를 통해 virtual코드 (메서드 및 속성) 를 재정의 할 수 있습니다 .


1
시도했지만 테스트 프로젝트를 실행하면 프로그램에서 "클래스의 프록시를 인스턴스화 할 수 없습니다" "매개 변수없는 생성자를 찾을 수 없습니다."라는 예외가 발생합니다.
Vinicius Seganfredo 2013

2
생성자 매개 변수를 Mock <> 생성자에 전달하기 만하면됩니다. 예new Mock<MyConcreteClass>(param1, anotherParam, thirdParam, evenMoreParams);
Bozhidar Stoyneff

1
클래스를위한 인터페이스를 만드는 것은 어떨까요?
로버트 페리

11

그 클래스에 대한 인터페이스를 만드는 것이 더 낫다고 생각합니다. 그리고 인터페이스를 사용하여 단위 테스트를 만듭니다.

해당 클래스에 액세스 할 수없는 경우 해당 클래스에 대한 어댑터를 만들 수 있습니다.

예를 들면 :

public class RealClass
{
    int DoSomething(string input)
    {
        // real implementation here
    }
}

public interface IRealClassAdapter
{
    int DoSomething(string input);
}

public class RealClassAdapter : IRealClassAdapter
{
    readonly RealClass _realClass;

    public RealClassAdapter() => _realClass = new RealClass();

    int DoSomething(string input) => _realClass.DoSomething(input);
}

이렇게하면 IRealClassAdapter를 사용하여 클래스에 대한 모의 객체를 쉽게 만들 수 있습니다.

작동하기를 바랍니다.


4
이것은 유지 보수에 끔찍합니다. RealClass에 새 메서드를 추가하려면 IReadClassAdapter와 RealClassAdapter에도 추가해야합니다. 트리플 노력! 더 나은 해결책은 RealClass의 각 공용 메소드에 "가상"키워드를 추가하는 것입니다.
John Henckel

12
@JohnHenckel 저는 RealClass에 대한 액세스 권한이 없다고 가정합니다. 그래서 "가상"방법을 추가하는 것은 제 생각에 옵션이 아닙니다. 실제 클래스에 액세스 할 수있는 경우 새 클래스에 직접 인터페이스를 구현하는 것이 좋습니다. 이것이 가장 좋은 방법이라고 생각합니다.
Anang Satria

감사. 이것은 액세스 권한이없는 클래스를 조롱하는 유일한 해결책 인 것 같습니다. 심지어 그것의 많은, 내가이가 "부적절한 환경"에서 코드를 실행해야 "바보"코드의의
마이클 Spannbauer

7

표준 모의 프레임 워크는 프록시 클래스를 만듭니다. 이것이 기술적으로 인터페이스와 가상 방법으로 제한되는 이유입니다.

'정상적인'메소드도 모의하려면 프록시 생성 대신 계측과 함께 작동하는 도구가 필요합니다. 예를 들어 MS Moles와 Typemock은 그렇게 할 수 있습니다. 그러나 전자에는 끔찍한 'API'가 있고 후자는 상업적입니다.


공용 메서드로 일반 클래스를 조롱 할 수 있습니까?
SivaRajini


2

더 나빠질 경우 인터페이스와 어댑터 쌍을 만들 수 있습니다. 대신 인터페이스를 사용하도록 ConcreteClass의 모든 사용을 변경하고 항상 프로덕션 코드에서 콘크리트 클래스 대신 어댑터를 전달합니다.

어댑터는 인터페이스를 구현하므로 모의 객체도 인터페이스를 구현할 수 있습니다.

메서드를 가상으로 만들거나 인터페이스를 추가하는 것보다 더 비계에 가깝지만 구체적인 클래스에 대한 소스에 액세스 할 수없는 경우 바인딩에서 벗어날 수 있습니다.


1

인터페이스 나 모범 사례가 포함되지 않은 기존 및 레거시 프로젝트 중 하나에서 이와 같은 문제에 직면했으며 프로젝트 비즈니스의 성숙으로 인해 다시 빌드하거나 코드를 리팩토링하기가 너무 어렵습니다. 내 UnitTest 프로젝트에서 모의하려는 클래스에 대한 래퍼를 만들고 설정하고 작업하려는 모든 필요한 메서드를 포함하는 래퍼 구현 인터페이스를 만들었습니다. 이제 실제 클래스 대신 래퍼를 모의 할 수 있습니다.

예를 들면 :

가상 메소드를 포함하지 않거나 인터페이스를 구현하지 않은 테스트하려는 서비스

public class ServiceA{

public void A(){}

public String B(){}

}

moq에 래퍼

public class ServiceAWrapper : IServiceAWrapper{

public void A(){}

public String B(){}

}

래퍼 인터페이스

public interface IServiceAWrapper{

void A();

String B();

}

단위 테스트에서 이제 래퍼를 모의 할 수 있습니다.

    public void A_Run_ChangeStateOfX()
    {
    var moq = new Mock<IServiceAWrapper>();
    moq.Setup(...);
    }

이것은 모범 사례가 아닐 수도 있지만 프로젝트 규칙이 이런 방식으로 강제한다면 그렇게하십시오. 또한 불필요한 래퍼 또는 어댑터로 프로젝트에 과부하가 걸리지 않도록 단위 테스트 프로젝트 또는 단위 테스트 전용 도우미 프로젝트에 모든 래퍼를 넣습니다.

업데이트 : 이 답변은 1 년이 넘었지만 올해에는 다른 솔루션으로 비슷한 시나리오가 많이 발생했습니다. 예를 들어 Microsoft Fake Framework를 사용하여 모의, 가짜 및 스텁을 만들고 인터페이스없이 개인 및 보호 된 메서드를 테스트하는 것은 매우 쉽습니다. 읽을 수 있습니다. https://docs.microsoft.com/en-us/visualstudio/test/isolating-code-under-test-with-microsoft-fakes?view=vs-2017

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.