개인 메소드를 어떻게 단위 테스트합니까?


479

공개 및 개인 메소드가있는 클래스 라이브러리를 작성 중입니다. 개인 메소드를 단위 테스트 할 수 있기를 원합니다 (주로 개발하는 동안 나중에 리팩토링에 유용 할 수 있음).

이를 수행하는 올바른 방법은 무엇입니까?


3
pre-historic인터넷 연도 측면에서 뭔가 빠졌거나 아마도이 질문에 불과한 것일 수도 있습니다. 그러나 개인 메소드의 단위 테스트는 이제 쉽고 간단합니다 .Visual Studio는 필요할 때 필요한 접근 자 클래스를 생성하고 간단한 기능 테스트를 원할 수있는 스 니펫으로 테스트 로직을 미리 채 웁니다. 예를 들어보십시오. msdn.microsoft.com/ko-kr/library/ms184807%28VS.90%29.aspx
mjv

3
이것은 stackoverflow.com/questions/34571/…과 거의 비슷한 것으로 보입니다 .
Raedwald

질문자는 비주얼 스튜디오를 사용하지 않을 수 있습니다
Dave

3
:하지 단위 테스트 내부 수행 blog.ploeh.dk/2015/09/22/unit-testing-internals
마크 시만

답변:


122

.net을 사용하는 경우 InternalsVisibleToAttribute를 사용해야합니다 .


86
왝. 릴리즈 된 어셈블리로 컴파일됩니다.
Jay

14
@Jay - 하나 개 사용하지 못했습니다 #if DEBUG주위에 InternalsVisibleTo속성은 그렇지 릴리스 코드에 적용 만들려면?
mpontillo

20
@Mike, 가능하지만 릴리스 코드가 아닌 디버그 코드에서만 단위 테스트를 실행할 수 있습니다. 릴리스 코드가 최적화되었으므로 동작 및 타이밍이 다를 수 있습니다. 다중 스레드 코드에서 이것은 단위 테스트가 경쟁 조건을 적절하게 감지하지 못함을 의미합니다. 아래의 @AmazedSaint 제안을 통해 리플렉션을 사용하거나 내장 PrivateObject / PrivateType을 사용하는 것이 훨씬 좋습니다. 이를 통해 테스트 하네스가 완전 신뢰 (로컬에서 실행되는 MSTest)로 실행되고 있다고 가정하면 릴리스 빌드에서 개인을 볼 수 있습니다.
Jay

121
내가 무엇을 놓치고 있습니까? 개인 메소드 테스트의 특정 질문에 실제로 대답하지 않을 때 왜 이것이 허용되는 대답입니까? InternalsVisibleTo는 OP로 요청한대로 private으로 표시된 메서드가 아닌 내부로 표시된 메서드 만 표시합니다 (여기에 방문한 이유). Seven의 답변에 따라 PrivateObject를 계속 사용해야합니까?
대런 루이스

6
나는이 조금 늦게 오는 알고 @Jay하지만, 하나의 옵션은 같은 것을 사용하는 것입니다 #if RELEASE_TEST주위를 InternalsVisibleTo마이크가 제안처럼, 그리고 정의하는 당신의 릴리스 빌드 구성의 복사본을 만들 RELEASE_TEST. 최적화를 통해 릴리스 코드를 테스트 할 수 있지만 실제로 릴리스를 위해 빌드 할 때는 테스트가 생략됩니다.
Shaz

349

개인 메소드를 단위 테스트하려면 무언가 잘못되었을 수 있습니다. 단위 테스트는 (일반적으로 말해서) 클래스의 인터페이스를 테스트하기위한 것으로, 공개 (및 보호) 방법을 의미합니다. 물론이 방법에 대한 해결책을 "해킹"할 수 있지만 (방법을 공개하여도) 다음 사항을 고려할 수도 있습니다.

  1. 테스트하려는 메소드가 실제로 테스트 할 가치가있는 경우 자체 클래스로 옮기는 것이 좋습니다.
  2. 개인용 메소드를 호출하는 개인용 메소드에 테스트를 추가하여 개인용 메소드의 기능을 테스트하십시오. (평론가들이 지적했듯이, 이러한 개인용 메서드의 기능이 실제로 공용 인터페이스의 일부인 경우에만이 작업을 수행해야합니다. 실제로 사용자에게 숨겨진 기능 (예 : 단위 테스트)을 수행하는 경우 이는 아마도 좋지 않습니다).

38
옵션 2는 단위 테스트가 함수의 기본 구현에 대한 지식을 갖도록합니다. 나는 그 일을 좋아하지 않습니다. 나는 일반적으로 단위 테스트가 구현에 대해 가정하지 않고 함수를 테스트해야한다고 생각합니다.
Herms

15
테스트 구현의 단점은 구현에 변경 사항이 있으면 테스트가 깨지기 쉽다는 것입니다. 그리고 리팩토링이 TDD에서 테스트를 작성하는 것만 큼 중요하므로 바람직하지 않습니다.
JtR

30
구현을 변경하면 테스트 중단됩니다. TDD는 먼저 테스트를 변경하는 것을 의미합니다.
sleske

39
@ sleske-나는 동의하지 않습니다. 기능 이 변경되지 않은 경우 테스트가 구현이 아닌 동작 / 상태를 테스트해야하므로 테스트가 중단 될 이유가 없습니다. 이것이 jtr이 테스트를 취약하게 만드는 의미입니다. 이상적인 세계에서는 코드를 리팩터링하고 테스트를 계속 통과하여 리팩토링이 시스템의 기능을 변경하지 않았 음을 확인해야합니다.
Alconja 2016 년

26
순진함을위한 사과 (아직 테스트 경험이 많지 않음), 모든 코드 모듈을 자체적으로 테스트하기위한 단위 테스트의 아이디어가 아닙니까? 개인 아이디어를 왜이 아이디어에서 제외 해야하는지 이해하지 못합니다.
OMill

118

개인용 메소드를 테스트하는 것은 유용하지 않을 수 있습니다. 그러나 때로는 테스트 메소드에서 개인 메소드를 호출하고 싶습니다. 테스트 데이터 생성을위한 코드 중복을 방지하기 위해 대부분의 경우 ...

Microsoft는이를 위해 두 가지 메커니즘을 제공합니다.

접근 자

  • 클래스 정의의 소스 코드로 이동
  • 수업 이름을 마우스 오른쪽 버튼으로 클릭
  • "개인 접근 자 만들기"를 선택하십시오
  • 접근자를 만들 프로젝트를 선택하십시오. => 이름이 foo_accessor 인 새 클래스가 생깁니다. 이 클래스는 컴파일하는 동안 동적으로 생성되며 모든 멤버에게 공개됩니다.

그러나 원래 클래스의 인터페이스가 변경 될 때 메커니즘이 약간 다루기 어려운 경우가 있습니다. 그래서 대부분의 경우 이것을 사용하지 마십시오.

PrivateObject 클래스 다른 방법은 Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject를 사용하는 것입니다.

// Wrap an already existing instance
PrivateObject accessor = new PrivateObject( objectInstanceToBeWrapped );

// Retrieve a private field
MyReturnType accessiblePrivateField = (MyReturnType) accessor.GetField( "privateFieldName" );

// Call a private method
accessor.Invoke( "PrivateMethodName", new Object[] {/* ... */} );

2
개인 정적 메소드를 어떻게 호출합니까?
StuperUser 2016 년


3
개인 메서드를 테스트하는 접근 자 메서드는 VS 2011부터 더 이상 사용되지 않습니다. blogs.msdn.com/b/visualstudioalm/archive/2012/03/08/…
Sanjit Misra

1
Microsoft 웹 사이트 에서 찾은 문서를 읽으면 PrivateObject 클래스에 대한 언급이 더 이상 사용되지 않습니다. MSVS 2013을 사용하고 있으며 예상대로 작동합니다.
stackunderflow

3
@RyanGates 참조한 사이트의 첫 번째 솔루션은 개인 멤버에 액세스하기위한 솔루션은 "PrivateObject 클래스를 사용하여 코드의 내부 및 개인 API에 액세스 할 수 있도록하는 것입니다. 이것은 Microsoft.VisualStudio.QualityTools.UnitTestFramework에 있습니다. dll 어셈블리. "
stackunderflow

78

"외부 인터페이스 테스트에만 관심이 있어야합니다"라는 철학에 동의하지 않습니다. 자동차 수리점은 바퀴가 돌아가는지 확인하기위한 테스트 만해야한다고 말하는 것과 비슷합니다. 그렇습니다. 궁극적으로 나는 외부 행동에 관심이 있지만, 개인적이고 개인적인 내부 테스트가 좀 더 구체적이고 중요합니다. 예, 리팩터링하면 일부 테스트를 변경해야 할 수도 있지만 대규모 리 팩터가 아닌 한 몇 가지만 변경하면되고 다른 (변경되지 않은) 내부 테스트가 여전히 작동한다는 사실은 리팩토링이 성공했습니다.

공개 인터페이스 만 사용하여 모든 내부 사례를 다루려고 시도 할 수 있으며 이론적으로 공개 인터페이스를 사용하여 모든 내부 방법 (또는 적어도 모든 중요한 방법)을 완전히 테스트 할 수는 있지만 달성하기 위해 머리에서야 할 수도 있습니다 이것과 공개 인터페이스를 통해 실행되는 테스트 케이스와 테스트를 위해 설계된 솔루션의 내부 부분 사이의 연결은 식별하기 어렵거나 불가능할 수 있습니다. 내부 기계가 올바르게 작동하는지 확인하는 개별 테스트는 리팩토링으로 인한 사소한 테스트 변경에 가치가 있습니다. 적어도 그것은 저의 경험이었습니다. 모든 리팩토링에 대해 테스트를 크게 변경해야하는 경우에는이 방법이 적합하지 않지만이 경우 설계를 완전히 다시 생각해야합니다.


20
나는 여전히 당신과 동의하지 않을까 걱정됩니다. 각 구성 요소를 블랙 박스로 취급하면 문제없이 모듈을 교체 할 수 있습니다. 당신이있는 경우 FooService수행하는 것을 X, 당신에 대해 관심을 가져야 모두가 정말로 할 수 없다는 것입니다 X요청이있을 때. 어떻게 그것을합니까 그건 문제가 안된다. 인터페이스를 통해 식별 할 수없는 클래스에 문제가있는 경우 (아마도) 여전히 유효합니다 FooService. 이 문제의 경우 인터페이스를 통해 볼 수, 공용 멤버에 대한 테스트를 감지합니다. 요점은 바퀴가 제대로 회전하는 한 바퀴로 사용될 수 있다는 것입니다.
기본

5
내부 논리가 복잡하여 단위 테스트가 필요하다고 생각되면 단위 테스트가 가능한 공용 인터페이스를 사용하여 일종의 도우미 클래스로 추출해야 할 수도 있습니다. 그러면 당신의 '부모'클래스는이 도우미를 간단히 사용할 수 있으며, 모든 사람들은 적절하게 단위 테스트를받을 수 있습니다.
Mir

9
@Basic :이 답변에서 완전히 잘못된 논리. 개인 메서드가 필요한 고전적인 경우는 공용 메서드에서 재사용 할 코드가 필요한 경우입니다. 이 코드를 PrivMethod에 넣습니다. 이 방법은 공개적으로 노출되어서는 안되지만 PrivMethod에 의존하는 공개 방법이 실제로 의존 할 수 있는지 테스트해야합니다.
Dima

6
@Dima 확실하게에 문제가 있다면 PrivMethod, PubMethod어떤 호출 PrivMethod이 노출되어야하는지 테스트 합니까? 당신이 당신을 변경하면 어떻게됩니까 SimpleSmtpServiceA를 GmailService? 갑자기 응용 프로그램이 완벽하게 작동하더라도 응용 프로그램이 더 이상 존재하지 않거나 다르게 작동하고 실패 할 수있는 코드가 개인 테스트에서 갑자기 나타납니다. 두 이메일 발신자 모두에게 적용되는 복잡한 처리가 있다면, EmailProcessor둘 다 사용하고 별도로 테스트 할 수 있어야합니까?
기본

2
@miltonb 우리는 이것을 다른 개발 스타일에서보고있을 것입니다. 내부를 WRT로, 나는 그들을 단위 테스트하는 경향이 없습니다. 인터페이스 테스트로 식별 된 문제가있는 경우 디버거를 연결하여 추적하기가 쉬우거나 클래스가 너무 복잡하여 분할해야합니다 (테스트 된 새 클래스 단위의 공용 인터페이스와 함께) IMHO
기본

51

드문 경우지만 개인 함수를 테스트하고 싶었지만 일반적으로 대신 보호되도록 수정했으며 공용 래퍼 함수로 하위 클래스를 작성했습니다.

클래스:

...

protected void APrivateFunction()
{
    ...
}

...

테스트를위한 서브 클래스 :

...

[Test]
public void TestAPrivateFunction()
{
    APrivateFunction();
    //or whatever testing code you want here
}

...

1
실제 클래스를 어지럽히는 대신 하위 클래스를 단위 테스트 파일에 넣을 수도 있습니다. 교활함 +1
Tim Abell

가능한 경우 항상 모든 테스트 관련 코드를 단위 테스트 프로젝트에 넣습니다. 이것은 단지 의사 코드였습니다.
Jason Jackson

2
이 기능은 개인용이 아니며 보호 된 순수한 결과입니다. 코드의 보안 성이 떨어지거나 하위 유형의 개인 기능에 노출되었습니다.
War

22

더 근본적인 질문은 왜 개인 메소드를 먼저 테스트하려고하는지에 대한 것입니다. 그것은 클래스 냄새 공용 인터페이스를 통해 개인 메소드를 테스트하려고 시도하는 코드 냄새이지만, 그 메소드는 구현 세부 사항이므로 사유로 인해 비공개입니다. 공개 인터페이스의 작동 방식에 대해서는 다루지 않고 공개 인터페이스의 동작에만 관심을 가져야합니다.

공통 리팩토링을 사용하여 개인 메소드의 동작을 테스트하려면 코드를 다른 클래스로 추출 할 수 있습니다 (패키지 레벨 가시성으로 공개 API의 일부가 아닌지 확인). 그런 다음 해당 동작을 격리하여 테스트 할 수 있습니다.

리팩토링의 결과는 개인 메소드가 이제 원래 클래스의 공동 작업자가 된 별도의 클래스임을 의미합니다. 그것의 동작은 자체 단위 테스트를 통해 잘 이해 될 것입니다.

그런 다음 원래 클래스를 테스트하려고 할 때 동작을 조롱하여 공용 인터페이스의 조합 폭발과 모든 개인 메소드의 동작을 테스트하지 않고 해당 클래스의 공용 인터페이스 동작 테스트에 집중할 수 있습니다. .

나는 이것이 차를 운전하는 것과 유사한 것을 본다. 차를 운전할 때 보닛을 운전하지 않아서 엔진이 작동하고 있음을 알 수 있습니다. 나는 자동차가 제공하는 인터페이스, 즉 rev 카운터와 속도계를 사용하여 엔진이 작동하고 있음을 알 수 있습니다. 나는 가스 페달을 밟을 때 차가 실제로 움직인다는 사실에 의존합니다. 엔진을 테스트하고 싶다면 별도로 검사 할 수 있습니다. :디

물론 레거시 응용 프로그램이있는 경우 개인 메서드를 직접 테스트하는 것이 최후의 수단 일 수 있지만 더 나은 테스트를 위해 레거시 코드를 리팩터링하는 것이 좋습니다. Michael Feathers는이 주제에 관해 훌륭한 책을 썼습니다. http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052


16
이 답변에서 완전히 잘못된 논리. 개인 메서드가 필요한 고전적인 경우는 공용 메서드에서 재사용 할 코드가 필요한 경우입니다. 이 코드를 PrivMethod에 넣습니다. 이 방법은 공개적으로 노출되어서는 안되지만 PrivMethod에 의존하는 공개 방법이 실제로 의존 할 수 있는지 테스트해야합니다.
Dima

초기 개발 중에는 의미가 있지만 표준 회귀 소송에서 개인 방법에 대한 테스트를 원하십니까? 그렇다면 구현이 변경되면 테스트 스위트가 중단 될 수 있습니다. OTOH, 회귀 테스트가 외부에서 볼 수있는 공개 메소드에만 초점을 둔 경우 개인 메소드가 나중에 중단되면 회귀 스위트는 여전히 오류를 감지해야합니다. 그런 다음 필요한 경우 이전 개인 테스트를 먼지로 제거 할 수 있습니다.
Alex Blakemore

9
동의하지 않으면 공용 인터페이스 만 테스트해야하며, 그렇지 않은 경우 개인용 메소드가 필요한 이유입니다. 이 경우 모두 공개하고 모두 테스트하십시오. 개인 메서드를 테스트하는 경우 캡슐화가 중단됩니다. 개인 메소드를 테스트하고 여러 공용 메소드에서 사용하려면 자체 클래스로 이동하고 격리 테스트해야합니다. 모든 공용 메소드는 새 클래스에 위임해야합니다. 그러면 여전히 원래 클래스의 인터페이스와 동작이 변경되지 않았는지 확인하고 위임 된 개인용 메소드에 대한 별도의 테스트가 있는지 확인할 수 있습니다.
빅 카 후나

@ Big Kahuna-개인 메소드를 단위 테스트 할 필요가 없다고 생각하면 충분히 크고 복잡한 프로젝트로 작업하지 마십시오. 클라이언트 특정 유효성 검사와 같은 공용 함수는 코드를 읽기 쉽게 만들기 위해 매우 간단한 개인용 메서드를 호출하는 20 줄로 끝나는 경우가 많지만 여전히 각 개인용 메서드를 테스트해야합니다. 공용 기능을 20 번 테스트하면 유닛 테스트가 실패 할 때 데뷔하기가 매우 어려워집니다.
Pedro.The.Kid

1
저는 FTSE 100 회사에서 일합니다. 나는 내 시대에 여러 복잡한 프로젝트를 본 것 같아요, 감사합니다. 해당 수준으로 테스트해야하는 경우 별도의 공동 작업자 인 각 개인용 메서드는 테스트가 필요한 개별 동작을 의미하므로 개별적으로 테스트해야합니다. 그러면 주 중재자 개체에 대한 테스트가 상호 작용 테스트가됩니다. 올바른 전략이 호출되고 있는지 테스트하기 만하면 문제의 클래스가 SRP를 따르지 않는 것처럼 들립니다. 변경해야 할 단일 이유는 없지만 20 => SRP 위반입니다. GOOS 책이나 Bob 아저씨를 읽으십시오. YMWV
빅 카 후나

17

개인 유형, 내부 및 개인 구성원은 이유가 있기 때문에 종종 직접 엉망으로 만들고 싶지 않습니다. 그렇게하면 나중에 해당 어셈블리를 만든 사람이 개인 / 내부 구현을 유지할 것이라는 보장이 없기 때문에 나중에 중단 될 가능성이 있습니다.

그러나 때로는 컴파일되거나 타사 어셈블리를 해킹 / 탐색 할 때 개인 클래스 또는 개인 또는 내부 생성자를 사용하여 클래스를 초기화하려고했습니다. 또는 때로는 변경할 수없는 사전 컴파일 된 레거시 라이브러리를 처리 할 때 개인 메서드에 대한 테스트를 작성합니다.

따라서 AccessPrivateWrapper ( http://amazedsaint.blogspot.com/2010/05/accessprivatewrapper-c-40-dynamic.html) 는 C # 4.0 동적 기능 및 리플렉션을 사용하여 작업을 쉽게 수행 할 수있는 빠른 래퍼 클래스입니다.

당신은 같은 내부 / 개인 유형을 만들 수 있습니다

    //Note that the wrapper is dynamic
    dynamic wrapper = AccessPrivateWrapper.FromType
        (typeof(SomeKnownClass).Assembly,"ClassWithPrivateConstructor");

    //Access the private members
    wrapper.PrivateMethodInPrivateClass();

12

개인 메소드를 두 가지 방법으로 단위 테스트 할 수 있습니다.

  1. PrivateObject구문은 다음과 같이 클래스의 인스턴스를 만들 수 있습니다

    PrivateObject obj= new PrivateObject(PrivateClass);
    //now with this obj you can call the private method of PrivateCalss.
    obj.PrivateMethod("Parameters");
  2. 반사를 사용할 수 있습니다.

    PrivateClass obj = new PrivateClass(); // Class containing private obj
    Type t = typeof(PrivateClass); 
    var x = t.InvokeMember("PrivateFunc", 
        BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Public |  
            BindingFlags.Instance, null, obj, new object[] { 5 });

좋은 대답이지만 # 1의 구문이 잘못되었습니다. PrivateClass먼저 인스턴스를 선언 하고 사용해야합니다. stackoverflow.com/questions/9122708/…
SharpC

10

또한 InternalsVisibleToAttribute 메서드를 사용했습니다. 이것을 달성하기 위해 이전에 개인적인 방법을 내부적으로 만드는 것이 불편하다고 느끼면 직접 단위 테스트의 대상이되어서는 안됩니다.

결국 특정 구현이 아닌 클래스 의 동작 을 테스트하고 있습니다. 전자를 변경하지 않고 후자를 변경할 수 있으며 테스트는 여전히 통과해야합니다.


구현보다는 행동 테스트에 대한 요점을 좋아합니다. 단위 테스트를 구현 (비공개 메소드)에 연결하면 테스트가 취약 해지며 구현이 변경되면 변경해야합니다.
밥 혼

9

개인용 메소드에는 두 가지 유형이 있습니다. 정적 프라이빗 메소드 및 비 정적 프라이빗 메소드 (인스턴스 메소드). 다음 두 기사는 개인 메소드를 예제와 함께 테스트하는 방법을 설명합니다.

  1. 정적 개인 메소드 테스트
  2. 비 정적 개인 메소드의 단위 테스트

몇 가지 예를 제공뿐만 아니라 링크 제공
vinculis

못 생겼어 지능이 없습니다. MS의 나쁜 해결책. 나는 충격을 받고있다!
alerya

비공개 정적 메소드를 테스트하는 가장 쉬운 방법
부여

8

MS Test는 VSCodeGenAccessors라는 파일을 생성하여 프로젝트에서 개인 멤버 및 메소드를 사용할 수있는 멋진 기능을 내장

[System.Diagnostics.DebuggerStepThrough()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
    internal class BaseAccessor
    {

        protected Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject m_privateObject;

        protected BaseAccessor(object target, Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
        {
            m_privateObject = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateObject(target, type);
        }

        protected BaseAccessor(Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType type)
            :
                this(null, type)
        {
        }

        internal virtual object Target
        {
            get
            {
                return m_privateObject.Target;
            }
        }

        public override string ToString()
        {
            return this.Target.ToString();
        }

        public override bool Equals(object obj)
        {
            if (typeof(BaseAccessor).IsInstanceOfType(obj))
            {
                obj = ((BaseAccessor)(obj)).Target;
            }
            return this.Target.Equals(obj);
        }

        public override int GetHashCode()
        {
            return this.Target.GetHashCode();
        }
    }

BaseAccessor에서 파생 된 클래스 사용

와 같은

[System.Diagnostics.DebuggerStepThrough()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TestTools.UnitTestGeneration", "1.0.0.0")]
internal class SomeClassAccessor : BaseAccessor
{

    protected static Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType m_privateType = new Microsoft.VisualStudio.TestTools.UnitTesting.PrivateType(typeof(global::Namespace.SomeClass));

    internal SomeClassAccessor(global::Namespace.Someclass target)
        : base(target, m_privateType)
    {
    }

    internal static string STATIC_STRING
    {
        get
        {
            string ret = ((string)(m_privateType.GetStaticField("STATIC_STRING")));
            return ret;
        }
        set
        {
            m_privateType.SetStaticField("STATIC_STRING", value);
        }
    }

    internal int memberVar    {
        get
        {
            int ret = ((int)(m_privateObject.GetField("memberVar")));
            return ret;
        }
        set
        {
            m_privateObject.SetField("memberVar", value);
        }
    }

    internal int PrivateMethodName(int paramName)
    {
        object[] args = new object[] {
            paramName};
        int ret = (int)(m_privateObject.Invoke("PrivateMethodName", new System.Type[] {
                typeof(int)}, args)));
        return ret;
    }

8
gend 파일은 VS2005에만 존재합니다. 2008 년에는 배후에서 생성됩니다. 그것들은 축약입니다. 그리고 연관된 Shadow 작업은 빌드 서버에서 비정상적입니다.
Ruben Bartelink

또한 접근자는 VS2012-2013에서 더 이상 사용되지 않습니다.
Zephan Schroeder

5

CodeProject에는 개인 메서드 테스트의 장단점에 대해 간략하게 설명하는 기사가 있습니다. 그런 다음 Marcus가 위에서 제공 한 코드와 비슷한 개인 메서드에 액세스하기위한 리플렉션 코드를 제공합니다. 샘플에서 찾은 유일한 문제는 코드가 오버로드 된 메서드를 고려하지 않는다는 것입니다.

여기에서 기사를 찾을 수 있습니다.

http://www.codeproject.com/KB/cs/testnonpublicmembers.aspx


4

을 선언 internal한 다음을 사용 InternalsVisibleToAttribute하여 단위 테스트 어셈블리가 볼 수 있도록합니다.


11
이유 때문에 비공개 메소드를 작성했기 때문에 InternalsVisibleTo를 사용하는 것을 좋아하지 않습니다.
swilliams

4

나는 컴파일러 지시문을 사용하지 않는 경향이 있습니다. 실제로 필요한 경우 완화하는 한 가지 방법은 부분 클래스에 배치하고 프로덕션 버전을 만들 때 빌드에서 해당 .cs 파일을 무시하도록하는 것입니다.


(컴파일러 최적화 등을 테스트하기 위해) 프로덕션 버전에는 테스트 접근자를 포함하지만 릴리스 버전에서는 제외합니다. 그러나 나는 머리카락을 나누고 있습니다. 어쨌든이 물건을 한곳에 두는 것이 좋습니다 생각하기 때문에 이것을 어쨌든 찬성했습니다. 아이디어 주셔서 감사합니다.
CAD bloke

4

먼저 코드의 개인 메소드를 테스트해서는 안됩니다. '공개 인터페이스'또는 API, 클래스의 공용 요소를 테스트해야합니다. API는 외부 발신자에게 공개하는 모든 공개 메소드입니다.

그 이유는 일단 개인 메소드와 클래스의 내부 테스트를 시작하면 클래스의 구현 (사물)을 테스트에 결합하기 때문입니다. 이는 구현 세부 사항을 변경하기로 결정할 때 테스트도 변경해야 함을 의미합니다.

이러한 이유로 InternalsVisibleToAtrribute를 사용하지 마십시오.

이안 쿠퍼 (Ian Cooper)가이 주제를 다루는 좋은 강연이 있습니다 : 이안 쿠퍼 (Ian Cooper) : TDD.


3

때로는 개인 선언을 테스트하는 것이 좋습니다. 기본적으로 컴파일러에는 하나의 공용 메소드 만 있습니다 : Compile (string outputFileName, params string [] sourceSFileNames). 각 "숨겨진"선언을 테스트하지 않고는 이러한 방법을 테스트하기 어려울 것입니다.

그렇기 때문에 테스트를 쉽게하기 위해 Visual T #을 만들었습니다. 무료 .NET 프로그래밍 언어입니다 (C # v2.0 호환).

'.-'연산자를 추가했습니다. 그냥 '.'처럼 동작합니다. 테스트 된 프로젝트에서 아무것도 변경하지 않고 테스트에서 숨겨진 선언에 액세스 할 수 있다는 점을 제외하고

웹 사이트를 살펴보십시오 : 무료로 다운로드 하십시오 .


3

아무도 아직 이것을 말하지 않은 것에 놀랐지 만, 내가 고용 한 해결책은 클래스 내부에서 정적 메소드를 작성하여 자체 테스트하는 것입니다. 이를 통해 테스트 할 공개 및 개인 정보에 액세스 할 수 있습니다.

또한 스크립팅 언어 (Python, Ruby 및 PHP와 같은 OO 기능 사용)에서 실행시 파일 자체를 테스트 할 수 있습니다. 변경 사항이 아무런 영향을 미치지 않도록하는 빠른 방법입니다. 이를 통해 모든 클래스를 테스트 할 수있는 확장 가능한 솔루션을 만들 수 있습니다. 모두 실행하면됩니다. (또한 항상 테스트를 실행하는 빈 메인으로 다른 언어 로이 작업을 수행 할 수 있습니다).


1
이것은 실용적이지만 매우 우아하지는 않습니다. 이로 인해 코드 기반이 약간 혼란 스러울 수 있으며 테스트를 실제 코드와 분리 할 수 ​​없습니다. 외부에서 테스트 할 수 있으면 정적 메서드를 직접 작성하는 대신 테스트를 자동화하는 스크립트 기능이 열립니다.
대런 리드

이것은 외부 테스트를 방해하지는 않지만 원하는 정적 메소드를 호출하면됩니다. 코드베이스도 지저분하지 않습니다 ... 방법의 이름을 적절하게 지정하십시오. "runTests"를 사용하지만 비슷한 기능이 있습니다.
Rub

당신의 권리는 외부 테스트를 방해하지 않지만 훨씬 더 많은 코드를 생성합니다. 즉 코드베이스를 어지럽게 만듭니다. 각 클래스에는 하나 이상의 생성자에서 변수의 초기화를 테스트하는 많은 개인용 메소드가있을 수 있습니다. 테스트하려면 테스트 할 메서드가있는 것보다 많은 정적 메서드를 작성해야하며 올바른 값을 초기화하려면 테스트 메서드가 커야 할 수 있습니다. 이로 인해 코드 유지 관리가 더 어려워집니다. 다른 사람들이 말했듯이 클래스의 동작을 테스트하는 것이 더 나은 방법이며 나머지는 디버깅하기에 충분히 작아야합니다.
대런 리드

다른 사람과 동일한 수의 줄을 사용하여 테스트합니다 (실제로 읽을수록 줄어 듭니다). 모든 개인 메소드를 테스트 할 필요는 없습니다. 테스트가 필요한 것만 :) 당신은 또한 별도의 방법으로 각각을 테스트 할 필요가 없습니다. 한 번의 통화로 수행합니다. 모든 클래스에는 동일한 개별 단위 테스트 방법이 있으므로 모든 개인 및 보호 단위 테스트를 한 줄씩 실행하기 때문에 실제로 코드의 유지 보수가 어려워집니다. 그런 다음 전체 테스트 하네스가 모든 클래스에서 동일한 메소드를 호출하고 유지 관리는 모두 클래스 내 테스트-모두에 상주합니다.
루브

3

개인 메서드를 테스트하려는 모든 클래스에서 사용할 수있는 명확한 코드 예제를 만들고 싶습니다.

테스트 케이스 클래스에는 이러한 메소드를 포함시킨 다음 표시된대로 사용하십시오.

  /**
   *
   * @var Class_name_of_class_you_want_to_test_private_methods_in
   * note: the actual class and the private variable to store the 
   * class instance in, should at least be different case so that
   * they do not get confused in the code.  Here the class name is
   * is upper case while the private instance variable is all lower
   * case
   */
  private $class_name_of_class_you_want_to_test_private_methods_in;

  /**
   * This uses reflection to be able to get private methods to test
   * @param $methodName
   * @return ReflectionMethod
   */
  protected static function getMethod($methodName) {
    $class = new ReflectionClass('Class_name_of_class_you_want_to_test_private_methods_in');
    $method = $class->getMethod($methodName);
    $method->setAccessible(true);
    return $method;
  }

  /**
   * Uses reflection class to call private methods and get return values.
   * @param $methodName
   * @param array $params
   * @return mixed
   *
   * usage:     $this->_callMethod('_someFunctionName', array(param1,param2,param3));
   *  {params are in
   *   order in which they appear in the function declaration}
   */
  protected function _callMethod($methodName, $params=array()) {
    $method = self::getMethod($methodName);
    return $method->invokeArgs($this->class_name_of_class_you_want_to_test_private_methods_in, $params);
  }

$ this-> _ callMethod ( '_ someFunctionName', 배열 (param1, param2, param3));

원래 개인 함수에 표시되는 순서대로 매개 변수를 발행하십시오.


3

모든 혼란과 혼란없이 개인 메소드를 실행하려는 사람에게 적합합니다. 이것은 좋은 오래된 반사를 사용하는 모든 단위 테스트 프레임 워크에서 작동합니다.

public class ReflectionTools
{
    // If the class is non-static
    public static Object InvokePrivate(Object objectUnderTest, string method, params object[] args)
    {
        Type t = objectUnderTest.GetType();
        return t.InvokeMember(method,
            BindingFlags.InvokeMethod |
            BindingFlags.NonPublic |
            BindingFlags.Instance |
            BindingFlags.Static,
            null,
            objectUnderTest,
            args);
    }
    // if the class is static
    public static Object InvokePrivate(Type typeOfObjectUnderTest, string method, params object[] args)
    {
        MemberInfo[] members = typeOfObjectUnderTest.GetMembers(BindingFlags.NonPublic | BindingFlags.Static);
        foreach(var member in members)
        {
            if (member.Name == method)
            {
                return typeOfObjectUnderTest.InvokeMember(method, BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, typeOfObjectUnderTest, args);
            }
        }
        return null;
    }
}

그런 다음 실제 테스트에서 다음과 같이 할 수 있습니다.

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    typeof(StaticClassOfMethod), 
    "PrivateMethod"), 
  "Expected Result");

Assert.AreEqual( 
  ReflectionTools.InvokePrivate(
    new ClassOfMethod(), 
    "PrivateMethod"), 
  "Expected Result");

2

MbUnit에는 Reflector라는 멋진 래퍼가 있습니다.

Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);

속성에서 값을 설정하고 가져올 수도 있습니다

dogReflector.GetProperty("Age");

"개인 테스트"와 관련하여 나는 완벽한 세상에 동의합니다. 개인 단위 테스트는 아무 의미가 없습니다. 그러나 실제로는 리팩토링 코드 대신 개인 테스트를 작성하려고 할 수 있습니다.


4
정보 제공을 위해 Gallio / MbUnit v3.2에서 Reflector보다 강력한 기능으로 대체되었습니다 Mirror. ( gallio.org/wiki/doku.php?id=mbunit:mirror )
얀 트레 빈

2

다음은 개인 메소드의 단위 테스트에 대한 좋은 기사 입니다. 그러나 테스트를 위해 특별히 설계된 응용 프로그램 (테스트 전용 테스트를 만드는 것과 같음)을 만들거나 테스트에 반영을 사용하는 것이 더 나은지 잘 모르겠습니다. 우리 중 대부분은 두 번째 방법을 선택할 것입니다.


2

내 생각에 당신은 당신의 클래스의 공개 API를 단위 테스트해야합니다.

단위 테스트를 위해 메소드를 공개하면 구현 세부 사항을 노출시키는 캡슐화가 중단됩니다.

훌륭한 퍼블릭 API는 클라이언트 코드의 즉각적인 목표를 해결하고 그 목표를 완전히 해결합니다.


정답 IMO가되어야합니다. 많은 개인 메소드가있는 경우 아마도 숨겨진 클래스가 있기 때문에 자체 공용 인터페이스로 분리해야하기 때문일 수 있습니다.
sunefred

2

PrivateObject 클래스를 사용 합니다. 그러나 앞에서 언급했듯이 개인 메소드 테스트를 피하는 것이 좋습니다.

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);

2
CC -Dprivate=public

"CC"는 내가 사용하는 시스템의 명령 행 컴파일러입니다. -Dfoo=bar에 해당 #define foo bar합니다. 따라서이 편집 옵션은 모든 개인 정보를 공개적으로 변경합니다.


2
이게 뭐야? 이것이 Visual Studio에도 적용됩니까?
YeahStu

"CC"는 내가 사용하는 시스템의 명령 행 컴파일러입니다. "-Dfoo = bar"는 "#define foo bar"와 동일합니다. 따라서이 편집 옵션은 모든 개인 정보를 공개적으로 변경합니다. ㅋ!
Mark Harrison

Visual Studio에서 빌드 환경에서 정의를 설정하십시오.
Mark Harrison

1

다음은 먼저 메소드 서명입니다.

private string[] SplitInternal()
{
    return Regex.Matches(Format, @"([^/\[\]]|\[[^]]*\])+")
                        .Cast<Match>()
                        .Select(m => m.Value)
                        .Where(s => !string.IsNullOrEmpty(s))
                        .ToArray();
}

테스트는 다음과 같습니다.

/// <summary>
///A test for SplitInternal
///</summary>
[TestMethod()]
[DeploymentItem("Git XmlLib vs2008.dll")]
public void SplitInternalTest()
{
    string path = "pair[path/to/@Key={0}]/Items/Item[Name={1}]/Date";
    object[] values = new object[] { 2, "Martin" };
    XPathString xp = new XPathString(path, values);

    PrivateObject param0 = new PrivateObject(xp);
    XPathString_Accessor target = new XPathString_Accessor(param0);
    string[] expected = new string[] {
        "pair[path/to/@Key={0}]",
        "Items",
        "Item[Name={1}]",
        "Date"
    };
    string[] actual;
    actual = target.SplitInternal();
    CollectionAssert.AreEqual(expected, actual);
}

1

이를 수행하는 방법은 메소드를 보유 protected하고 테스트 할 클래스를 상속하는 테스트 픽스처를 작성하는 것입니다. 이런 식으로, 당신은 방법을 돌리거나 돌리지 public않지만 테스트를 활성화합니다.


나는 소비자가 기본 클래스에서 상속하고 보호 된 기능을 사용할 수 있기 때문에 이것에 동의하지 않습니다. 이것은 기능을 비공개 또는 내부로 만들어서 처음부터 예방하고 싶었던 내용입니다.
Nick N.

1

1) 레거시 코드가있는 경우 개인 메서드를 테스트하는 유일한 방법은 리플렉션입니다.

2) 새 코드 인 경우 다음 옵션이 있습니다.

  • 반사를 사용하여 (복잡하게)
  • 동일한 클래스에서 단위 테스트 작성 (테스트 코드도 포함하여 프로덕션 코드를 추악하게 만듭니다)
  • 일종의 유틸리티 클래스에서 리팩토링하고 메소드를 공개하십시오.
  • @VisibleForTesting 주석을 사용하고 개인을 제거하십시오

가장 간단하고 복잡한 주석 방법을 선호합니다. 유일한 문제는 큰 관심사가 아니라고 생각하는 가시성을 높였다는 것입니다. 인터페이스에 항상 코딩해야하므로 MyService 인터페이스와 MyServiceImpl 구현이 있으면 MyServiceTest (테스트 인터페이스 메소드) 및 MyServiceImplTest (테스트 전용 메소드)에 해당하는 테스트 클래스를 가질 수 있습니다. 모든 클라이언트는 개인 메소드의 가시성이 증가하더라도 실제로는 중요하지 않은 방식으로 인터페이스를 사용해야합니다.


1

디버그 모드에서 빌드하는 동안 공개 또는 내부 (InternalsVisibleToAttribute 사용)로 선언 할 수도 있습니다.

    /// <summary>
    /// This Method is private.
    /// </summary>
#if DEBUG
    public
#else
    private
#endif
    static string MyPrivateMethod()
    {
        return "false";
    }

코드가 부풀어 오르지 만 private릴리스 빌드에 있습니다.


0

Visual Studio 2008에서 개인용 메서드에 대한 테스트 방법을 생성 할 수 있습니다. 개인용 메서드에 대한 단위 테스트를 만들면 테스트 참조 폴더가 테스트 프로젝트에 추가되고 접근자가 해당 폴더에 추가됩니다. 접근자는 단위 테스트 방법의 논리에서도 참조됩니다. 이 접근자를 사용하면 단위 테스트에서 테스트중인 코드에서 개인 메서드를 호출 할 수 있습니다. 자세한 내용은

http://msdn.microsoft.com/en-us/library/bb385974.aspx


0

또한 InternalsVisibleToAtrribute에는 강력한 이름을 요구하는 어셈블리가 있으므로 이전에 해당 요구 사항이 없었던 솔루션에서 작업하는 경우 자체 문제를 발생시킵니다. 접근자를 사용하여 개인 메서드를 테스트합니다. 이에 대한 예는 이 질문 을 참조하십시오 .


2
아니요, 어셈블리 이름을 강력하게 지정할 필요InternalsVisibleToAttribute없습니다 . 나는 현재 그렇지 않은 프로젝트에서 현재 사용하고 있습니다.
코디 그레이

1
이를 명확히하려면 : "현재 어셈블리와 친구 어셈블리 모두 서명하지 않았거나 둘 다 강력한 이름으로 서명해야합니다." -MSDN에서
Hari
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.