C #에서 개인 메서드의 단위 테스트


292

Visual Studio에서는 자동으로 생성 된 접근 자 클래스를 통해 개인 메서드의 단위 테스트를 수행 할 수 있습니다. 성공적으로 컴파일하는 개인용 메소드의 테스트를 작성했지만 런타임에 실패합니다. 코드와 테스트의 버전은 다음과 같습니다.

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

런타임 오류는 다음과 같습니다.

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

intellisense에 따르면 컴파일러가 TypeA_Accessor 유형이라고 생각합니다. 그러나 런타임시 유형은 TypeA이므로 목록 추가에 실패합니다.

이 오류를 막을 수있는 방법이 있습니까? 또는 다른 사람들이 할 수있는 다른 조언 (아마도 "비공개 방법을 테스트하지 않음"및 "객체 상태를 조작하는 단위 테스트가 없음")이있을 수 있습니다.


개인 클래스 TypeB에 대한 접근자가 필요합니다. 접근 자 TypeA_Accessor는 TypeA의 개인 및 보호 된 메소드에 대한 액세스를 제공합니다. 그러나 TypeB는 방법이 아닙니다. 수업입니다.
Dima

접근자는 개인 / 보호 된 메소드, 멤버, 속성 및 이벤트에 대한 액세스를 제공합니다. 수업 내 개인 / 보호 된 수업에는 액세스 할 수 없습니다. 개인 / 보호 된 클래스 (TypeB)는 클래스 (TypeA)를 소유하는 방법으로 만 사용하도록되어 있습니다. 따라서 기본적으로 TypeA 외부의 private 클래스 (TypeB)를 private 인 "myList"에 추가하려고합니다. 접근자를 사용하므로 myList에 액세스하는 데 아무런 문제가 없습니다. 그러나 접근자를 통해 TypeB를 사용할 수 없습니다. 가능한 해결책은 TypeB를 TypeA 외부로 옮기는 것입니다. 그러나 디자인이 깨질 수 있습니다.
Dima

개인 방법을 테스트하는 것은 다음에 의해 수행되어야 함을 느껴 stackoverflow.com/questions/250692/...
nate_weldon

답변:


275

예, 비공개 메소드를 테스트하지 마십시오 .... 단위 테스트의 아이디어는 공개 'API'로 단위를 테스트하는 것입니다.

많은 개인 행동을 테스트 해야하는 경우 테스트하려는 클래스 내에 새로운 '클래스'가 숨겨져 있으며 추출하여 공용 인터페이스로 테스트합니다.

하나의 조언 / 사고 도구 ..... 어떤 방법도 사적인 것이 될 수 없다는 생각이 있습니다. 모든 메소드가 객체의 공용 인터페이스에 있어야 함을 의미합니다. 비공개로 만들어야한다고 생각되면 다른 객체에있을 가능성이 큽니다.

이 조언은 실제로는 잘 작동하지 않지만 대부분 좋은 조언이며 종종 사람들이 물건을 더 작은 물건으로 분해하도록합니다.


386
동의하지 않습니다. OOD에서 개인 메서드와 속성은 자신을 반복하지 않는 본질적인 방법입니다 ( en.wikipedia.org/wiki/Don%27t_repeat_yourself ). 블랙 박스 프로그래밍과 캡슐화의 기본 개념은 가입자의 기술적 세부 사항을 숨기는 것입니다. 따라서 코드에 사소한 비공개 메소드와 속성이 있어야합니다. 사소하지 않은 경우 테스트해야합니다.
AxD

8
고유하지 않은 일부 OO 언어에는 전용 메소드가 없으며, 전용 특성에는 테스트 할 수있는 공용 인터페이스가있는 오브젝트가 포함될 수 있습니다.
Keith Nicholas

7
이 조언의 요점은 객체가 한 가지 일을하고 DRY 인 경우 개인 메소드를 사용해야 할 이유가 거의 없다는 것입니다. 사적인 메소드는 객체가 실제로 책임을지지 않는 무언가를 수행하지만 사소하지 않은 경우 매우 유용합니다. 일반적으로 SRP를 위반할 가능성이있는 또 다른 객체입니다.
Keith Nicholas

28
잘못된. 코드 중복을 피하기 위해 개인용 메소드를 사용할 수 있습니다. 또는 검증을 위해. 또는 공공 세계가 알아야 할 많은 다른 목적을 위해.
Jorj

37
OO 코드베이스에 끔찍하게 디자인되어 "증가 적으로 개선"하라는 요청을 받았을 때 개인 메소드에 대한 첫 번째 테스트를 할 수 없었습니다. 그러나 아마도 교과서에는 이러한 방법이 없을 수도 있지만 실제로는 제품 요구 사항을 가진 사용자가 있습니다. 프로젝트에서 약간의 테스트를 거치지 않고 방대한 규모의 "Look at ma clean code"리팩토링을 할 수 없습니다. 연습 프로그래머를 순진하게 좋아 보이지만 실제 지저분한 똥을 고려하지 않는 방법으로 강요하는 또 다른 사례처럼 느껴집니다.
dune.rocks

666

PrivateObject 클래스를 사용할 수 있습니다

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

25
Microsoft가 PrivateObject를 추가 했으므로 정답입니다.
Zoey

4
좋은 대답이지만 PrivateMethod는 "private"대신 "보호"되어야합니다.
HerbalMart

23
@ HerbalMart : 아마도 당신을 오해하지만, PrivateObject가 개인이 아닌 보호 된 멤버에만 액세스 할 수 있다고 제안하면 실수입니다.
kmote

17
@JeffPearce 정적 메서드의 경우 "PrivateType pt = new PrivateType (typeof (MyClass));"을 사용하고 개인 개체에서 Invoke를 호출 할 때 pt 개체에서 InvokeStatic을 호출 할 수 있습니다.
Steve Hibbert

12
경우 사람이 MSTest.TestFramework의 V1.2.1의로 궁금 해서요 - PrivateObject 및 PrivateType 클래스는 .NET 코어 2.0을 대상으로 프로젝트에 사용할 수 없습니다 - 이것에 대한 github의 문제있다 : github.com/Microsoft/testfx/issues/366
표고 버섯

98

"표준 또는 모범 사례로 불리는 것은 없으며 아마도 대중적인 의견 일 것입니다."

이 논의에서도 마찬가지입니다.

여기에 이미지 설명을 입력하십시오

그것은 모두 유닛이라고 생각하는 것에 달려 있습니다 .UNIT이 클래스라고 생각하면 공개 메소드 만 공격합니다. UNIT이 개인 메소드를 공격하는 코드 라인이라고 생각하면 죄책감을 느끼지 않습니다.

개인 메소드를 호출하려면 "PrivateObject"클래스를 사용하고 호출 메소드를 호출하십시오. "PrivateObject"사용 방법을 보여주고 비공개 메소드 테스트가 논리적인지 아닌지에 대한 심층적 인 YouTube 비디오 ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ )를 볼 수 있습니다 .


55

여기서 또 다른 생각은 테스트를 "내부"클래스 / 메서드로 확장하여이 테스트에 대한 화이트 박스 감각을 더 많이주는 것입니다. 어셈블리에서 InternalsVisibleToAttribute를 사용하여 개별 단위 테스트 모듈에 노출 할 수 있습니다.

봉인 클래스와 함께 테스트 방법이 unittest 어셈블리에서만 볼 수 있도록 캡슐화에 접근 할 수 있습니다. 봉인 된 클래스의 보호 된 메소드는 사실상 개인용입니다.

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

그리고 단위 테스트 :

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}

잘 모르겠습니다. InternalsVisibleToAttribute는 "내부"로 표시된 메소드와 속성을 액세스 가능하게하지만 내 필드와 메소드는 "비공개"입니다. 내가 사적인 것에서 사적인 것으로 바꿀 것을 제안하고 있습니까? 내가 오해하는 것 같아
junichiro

2
예, 그것이 제가 제안하는 것입니다. 약간 "해키"이지만 최소한 "공개"가 아닙니다.
Jeff

27
이 멋진 대답 단지 는 "개인 방법을 테스트하지 않습니다"하지만 네, 꽤 "해키"말할하지 않기 때문에. 해결책이 있었으면 좋겠다. IMO는 "개인 방법을 시험해서는 안된다"라고 나쁜 방식 때문에 나는 그것을 볼 : 그것은 "개인 방법이 정확 안"에 해당합니다.
MasterMastic

5
나중에 켄, 나는 또한 개인 테스트가 단위 테스트에서 테스트되어서는 안된다고 주장하는 사람들과 혼동했다. 퍼블릭 API가 출력이지만 때로는 잘못된 구현으로도 올바른 출력이 제공됩니다. 또는 구현시 필요하지 않은 리소스를 보유하고 gc 등에 의해 수집되지 못하게하는 객체를 참조하는 등의 부작용이 발생했습니다. 그들이 단위 테스트가 아닌 개인 메소드를 다루는 다른 테스트를 제공하지 않으면 100 % 테스트 코드를 유지할 수 없다고 생각합니다.
Mr.Pony

MasterMastic에 동의합니다. 이것은 정답입니다.
XDS

27

개인 메소드를 테스트하는 한 가지 방법은 리플렉션을 통하는 것입니다. 이것은 NUnit 및 XUnit에도 적용됩니다.

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);

call methods 정적 비 정적 ?
Kiquenet

2
리플렉션에 의존하는 메소드의 단점은 R #을 사용하여 메소드의 이름을 바꿀 때 중단되는 경향이 있다는 것입니다. 소규모 프로젝트에서는 큰 문제는 아니지만 거대한 코드 기반에서는 단위 테스트가 그런 방식으로 깨져서 신속하게 해결 해야하는 것은 다소 잔소리가됩니다. 이런 의미에서 나는 나의 돈을 Jeff의 대답에 간다.
XDS

2
@XDS 너무 나쁜 nameof ()는 클래스 외부에서 전용 메소드의 이름을 얻는 데 작동하지 않습니다.
Gabriel Morin 2019

12

Ermh ... 똑같은 문제로 여기에 왔습니다 : 간단 하지만 중요한 개인 방법을 테스트하십시오 . 이 스레드를 읽은 후에는 "이 간단한 금속 조각에이 간단한 구멍을 뚫고 싶어서 품질이 사양에 맞는지 확인하고 싶다"고 표시됩니다. "좋아요, 쉽지 않습니다. 우선은, 그렇게 할 적절한 도구가없는,하지만 당신은. 당신의 정원에서 중력 파 전망대를 구축에서 내 기사를 읽을 수 http://foobar.brigther-than-einstein.org/ 첫째, 물론, 당신 고급 양자 물리학 과정에 참석해야합니다. 그러면 엄청난 양의 매우 차가운 질소가 필요합니다. 물론 아마존에서 저의 책을 볼 수 있습니다.

다시 말해...

아니요, 먼저해야합니다.

각각의 모든 방법은 전용 수도 내부 보호 공중 가지고 검증한다. 여기에 제시된 것과 같은 방해없이 이러한 테스트를 구현할 수있는 방법이 있어야합니다.

왜? 정확히 일부 기고자들이 언급 한 건축 적 언급 때문 입니다. 소프트웨어 원칙을 간단히 반복하면 약간의 오해가 사라질 수 있습니다.

이 경우 일반적인 의심은 OCP, SRP 및 항상 KIS입니다.

그러나 잠시만 기다리십시오. 모든 것을 공개적으로 제공한다는 아이디어는 정치적이지 않고 일종의 태도입니다. 그러나. 오픈 소스 커뮤니티에서도 코드에 관해서는 이것이 교리가 아닙니다. 대신, "숨김"은 특정 API에 익숙해지기 쉬운 방법입니다. 예를 들어, 새로운 시장 출시 디지털 온도계 빌딩 블록의 핵심 계산을 숨길 수 있습니다. 실제 측정 곡선 뒤의 수학을 호기심 많은 코드 리더에게 숨기지 않고 코드가 일부에 의존하는 것을 방지하기 위해, 아마도 개인적으로 내부적으로 보호 된 코드를 사용하여 자신의 아이디어를 구현하는 것을 거부 할 수 없었던 갑자기 중요한 사용자 일 것입니다.

내가 무슨 소리 야?

개인 double TranslateMeasurementIntoLinear (double actualMeasurement);

물병 자리의 시대 또는 오늘날 불려지는 것을 선포하기는 쉽지만, 센서가 1.0에서 2.0에 도달하면 Translate ...의 구현은 쉽게 이해할 수있는 간단한 선형 방정식에서 변경 될 수 있습니다. 모두를 위해, 분석 등을 사용하는 매우 정교한 계산에 사용할 수 있으므로 다른 사람의 코드를 깨뜨릴 수 있습니다. 왜? KIS조차도 소프트웨어 코딩의 핵심을 이해하지 못했기 때문입니다.

이 동화를 짧게 만들려면 : 우리는 개인적인 방법을 시험 할 수있는 간단한 방법이 필요합니다 .

첫째 : 새해 복 많이 받으세요!

둘째 : 건축가 수업을 연습하십시오.

셋째 : "공개"수정자는 해결책이 아니라 종교입니다.


5

언급되지 않은 또 다른 옵션은 테스트하려는 객체의 자식으로 단위 테스트 클래스를 만드는 것입니다. NUnit 예 :

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

이렇게하면 개인 및 보호 된 (그러나 상속되지 않은 개인용) 메소드를 쉽게 테스트 할 수 있으며 모든 테스트를 실제 코드와 분리하여 테스트 어셈블리를 프로덕션에 배포하지 않아도됩니다. 많은 상속 된 객체에서 개인용 메서드를 보호 된 메서드로 전환하는 것이 허용되며 이는 매우 간단한 변경입니다.

하나...

이것은 숨겨진 방법을 테스트하는 방법의 문제를 해결하기위한 흥미로운 접근법이지만, 이것이 모든 경우에 문제에 대한 올바른 해결책이라고 주장 할 것입니다. 내부적으로 객체를 테스트하는 것이 조금 이상해 보입니다.이 접근법이 당신에게 영향을 줄 몇 가지 시나리오가있을 수 있습니다. (예를 들어, 불변의 객체는 일부 테스트를 정말 어렵게 만들 수 있습니다).

이 접근 방식을 언급하는 동안 이것은 합법적 인 솔루션보다 더 브레인 스토밍 된 제안이라고 제안합니다. 소금 한알과 함께 섭취하십시오.

편집 : 나는 이것을 분명히 나쁜 생각으로 묘사하기 때문에 사람들 이이 답변을 투표하는 것이 정말 유쾌하다는 것을 알았습니다. 사람들이 저에게 동의한다는 의미입니까? 나 진짜 혼란 스럽다.....


창의적인 솔루션이지만 다소 해키입니다.
shinzou

3

레거시 코드로 효과적으로 작업하기 책에서 :

"비공개 방법을 테스트해야 할 경우 공개해야합니다. 공개적으로 만들면 대부분의 경우 클래스가 너무 많은 일을하고이를 수정해야 함을 의미합니다."

저자에 따르면 문제를 해결하는 방법은 새 클래스를 만들고 메소드를로 추가하는 것 public입니다.

저자는 다음을 더 설명합니다.

"좋은 디자인은 테스트 할 수 있고, 테스트 할 수없는 디자인은 나쁘다."

따라서이 제한 내에서 유일한 실제 옵션은 public현재 또는 새 클래스에서 메소드를 만드는 것 입니다.


2

TL; DR : 다른 클래스로 개인 메소드를 추출하고 해당 클래스를 테스트하십시오. SRP 원칙 (단일 책임 원칙)에 대해 자세히 알아보십시오

private다른 클래스 의 메소드로 추출해야합니다 . 이 있어야합니다 public. private메소드 를 테스트하는 대신 public이 다른 클래스의 메소드를 테스트해야합니다 .

다음과 같은 시나리오가 있습니다.

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

우리는 논리를 테스트해야한다 _someLogic. 그러나 Class A필요한 것보다 더 많은 역할을 하는 것 같습니다 (SRP 원칙 위반). 그냥 두 클래스로 리팩토링

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

이런 식 someLogic으로 A2에서 테스트 할 수 있습니다. A1에서 가짜 A2를 만든 다음 생성자에 주입하여 A2가라는 함수에 호출되는지 테스트합니다 someLogic.


0

VS 2005/2008에서는 개인 접근 자를 사용 하여 개인 구성원을 테스트 할 수 있지만이 방법은 이후 버전의 VS에서 사라졌습니다 .


1
2008 년부터 2010 년 초까지의 좋은 답변. 이제 PrivateObject 및 Reflection 대안을 참조하십시오 (위의 여러 답변 참조). VS2010에는 접근 자 버그가 있었으며 MS는 VS2012에서 더 이상 사용하지 않습니다. VS2010 이상 (18 세 이상 빌드 툴링)에 머물지 않으면 개인 접근자를 피하여 시간을 절약하십시오. :-).
Zephan Schroeder
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.