공개 및 개인 메소드가있는 클래스 라이브러리를 작성 중입니다. 개인 메소드를 단위 테스트 할 수 있기를 원합니다 (주로 개발하는 동안 나중에 리팩토링에 유용 할 수 있음).
이를 수행하는 올바른 방법은 무엇입니까?
공개 및 개인 메소드가있는 클래스 라이브러리를 작성 중입니다. 개인 메소드를 단위 테스트 할 수 있기를 원합니다 (주로 개발하는 동안 나중에 리팩토링에 유용 할 수 있음).
이를 수행하는 올바른 방법은 무엇입니까?
답변:
.net을 사용하는 경우 InternalsVisibleToAttribute를 사용해야합니다 .
#if DEBUG
주위에 InternalsVisibleTo
속성은 그렇지 릴리스 코드에 적용 만들려면?
#if RELEASE_TEST
주위를 InternalsVisibleTo
마이크가 제안처럼, 그리고 정의하는 당신의 릴리스 빌드 구성의 복사본을 만들 RELEASE_TEST
. 최적화를 통해 릴리스 코드를 테스트 할 수 있지만 실제로 릴리스를 위해 빌드 할 때는 테스트가 생략됩니다.
개인 메소드를 단위 테스트하려면 무언가 잘못되었을 수 있습니다. 단위 테스트는 (일반적으로 말해서) 클래스의 인터페이스를 테스트하기위한 것으로, 공개 (및 보호) 방법을 의미합니다. 물론이 방법에 대한 해결책을 "해킹"할 수 있지만 (방법을 공개하여도) 다음 사항을 고려할 수도 있습니다.
개인용 메소드를 테스트하는 것은 유용하지 않을 수 있습니다. 그러나 때로는 테스트 메소드에서 개인 메소드를 호출하고 싶습니다. 테스트 데이터 생성을위한 코드 중복을 방지하기 위해 대부분의 경우 ...
Microsoft는이를 위해 두 가지 메커니즘을 제공합니다.
접근 자
그러나 원래 클래스의 인터페이스가 변경 될 때 메커니즘이 약간 다루기 어려운 경우가 있습니다. 그래서 대부분의 경우 이것을 사용하지 마십시오.
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[] {/* ... */} );
"외부 인터페이스 테스트에만 관심이 있어야합니다"라는 철학에 동의하지 않습니다. 자동차 수리점은 바퀴가 돌아가는지 확인하기위한 테스트 만해야한다고 말하는 것과 비슷합니다. 그렇습니다. 궁극적으로 나는 외부 행동에 관심이 있지만, 개인적이고 개인적인 내부 테스트가 좀 더 구체적이고 중요합니다. 예, 리팩터링하면 일부 테스트를 변경해야 할 수도 있지만 대규모 리 팩터가 아닌 한 몇 가지만 변경하면되고 다른 (변경되지 않은) 내부 테스트가 여전히 작동한다는 사실은 리팩토링이 성공했습니다.
공개 인터페이스 만 사용하여 모든 내부 사례를 다루려고 시도 할 수 있으며 이론적으로 공개 인터페이스를 사용하여 모든 내부 방법 (또는 적어도 모든 중요한 방법)을 완전히 테스트 할 수는 있지만 달성하기 위해 머리에서야 할 수도 있습니다 이것과 공개 인터페이스를 통해 실행되는 테스트 케이스와 테스트를 위해 설계된 솔루션의 내부 부분 사이의 연결은 식별하기 어렵거나 불가능할 수 있습니다. 내부 기계가 올바르게 작동하는지 확인하는 개별 테스트는 리팩토링으로 인한 사소한 테스트 변경에 가치가 있습니다. 적어도 그것은 저의 경험이었습니다. 모든 리팩토링에 대해 테스트를 크게 변경해야하는 경우에는이 방법이 적합하지 않지만이 경우 설계를 완전히 다시 생각해야합니다.
FooService
수행하는 것을 X
, 당신에 대해 관심을 가져야 모두가 정말로 할 수 없다는 것입니다 X
요청이있을 때. 어떻게 그것을합니까 그건 문제가 안된다. 인터페이스를 통해 식별 할 수없는 클래스에 문제가있는 경우 (아마도) 여전히 유효합니다 FooService
. 이 문제의 경우 인 인터페이스를 통해 볼 수, 공용 멤버에 대한 테스트를 감지합니다. 요점은 바퀴가 제대로 회전하는 한 바퀴로 사용될 수 있다는 것입니다.
PrivMethod
, PubMethod
어떤 호출 PrivMethod
이 노출되어야하는지 테스트 합니까? 당신이 당신을 변경하면 어떻게됩니까 SimpleSmtpService
A를 GmailService
? 갑자기 응용 프로그램이 완벽하게 작동하더라도 응용 프로그램이 더 이상 존재하지 않거나 다르게 작동하고 실패 할 수있는 코드가 개인 테스트에서 갑자기 나타납니다. 두 이메일 발신자 모두에게 적용되는 복잡한 처리가 있다면, EmailProcessor
둘 다 사용하고 별도로 테스트 할 수 있어야합니까?
드문 경우지만 개인 함수를 테스트하고 싶었지만 일반적으로 대신 보호되도록 수정했으며 공용 래퍼 함수로 하위 클래스를 작성했습니다.
클래스:
...
protected void APrivateFunction()
{
...
}
...
테스트를위한 서브 클래스 :
...
[Test]
public void TestAPrivateFunction()
{
APrivateFunction();
//or whatever testing code you want here
}
...
더 근본적인 질문은 왜 개인 메소드를 먼저 테스트하려고하는지에 대한 것입니다. 그것은 클래스 냄새 공용 인터페이스를 통해 개인 메소드를 테스트하려고 시도하는 코드 냄새이지만, 그 메소드는 구현 세부 사항이므로 사유로 인해 비공개입니다. 공개 인터페이스의 작동 방식에 대해서는 다루지 않고 공개 인터페이스의 동작에만 관심을 가져야합니다.
공통 리팩토링을 사용하여 개인 메소드의 동작을 테스트하려면 코드를 다른 클래스로 추출 할 수 있습니다 (패키지 레벨 가시성으로 공개 API의 일부가 아닌지 확인). 그런 다음 해당 동작을 격리하여 테스트 할 수 있습니다.
리팩토링의 결과는 개인 메소드가 이제 원래 클래스의 공동 작업자가 된 별도의 클래스임을 의미합니다. 그것의 동작은 자체 단위 테스트를 통해 잘 이해 될 것입니다.
그런 다음 원래 클래스를 테스트하려고 할 때 동작을 조롱하여 공용 인터페이스의 조합 폭발과 모든 개인 메소드의 동작을 테스트하지 않고 해당 클래스의 공용 인터페이스 동작 테스트에 집중할 수 있습니다. .
나는 이것이 차를 운전하는 것과 유사한 것을 본다. 차를 운전할 때 보닛을 운전하지 않아서 엔진이 작동하고 있음을 알 수 있습니다. 나는 자동차가 제공하는 인터페이스, 즉 rev 카운터와 속도계를 사용하여 엔진이 작동하고 있음을 알 수 있습니다. 나는 가스 페달을 밟을 때 차가 실제로 움직인다는 사실에 의존합니다. 엔진을 테스트하고 싶다면 별도로 검사 할 수 있습니다. :디
물론 레거시 응용 프로그램이있는 경우 개인 메서드를 직접 테스트하는 것이 최후의 수단 일 수 있지만 더 나은 테스트를 위해 레거시 코드를 리팩터링하는 것이 좋습니다. Michael Feathers는이 주제에 관해 훌륭한 책을 썼습니다. http://www.amazon.co.uk/Working-Effectively-Legacy-Robert-Martin/dp/0131177052
개인 유형, 내부 및 개인 구성원은 이유가 있기 때문에 종종 직접 엉망으로 만들고 싶지 않습니다. 그렇게하면 나중에 해당 어셈블리를 만든 사람이 개인 / 내부 구현을 유지할 것이라는 보장이 없기 때문에 나중에 중단 될 가능성이 있습니다.
그러나 때로는 컴파일되거나 타사 어셈블리를 해킹 / 탐색 할 때 개인 클래스 또는 개인 또는 내부 생성자를 사용하여 클래스를 초기화하려고했습니다. 또는 때로는 변경할 수없는 사전 컴파일 된 레거시 라이브러리를 처리 할 때 개인 메서드에 대한 테스트를 작성합니다.
따라서 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();
개인 메소드를 두 가지 방법으로 단위 테스트 할 수 있습니다.
PrivateObject
구문은 다음과 같이 클래스의 인스턴스를 만들 수 있습니다
PrivateObject obj= new PrivateObject(PrivateClass);
//now with this obj you can call the private method of PrivateCalss.
obj.PrivateMethod("Parameters");
반사를 사용할 수 있습니다.
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 });
PrivateClass
먼저 인스턴스를 선언 하고 사용해야합니다. stackoverflow.com/questions/9122708/…
또한 InternalsVisibleToAttribute 메서드를 사용했습니다. 이것을 달성하기 위해 이전에 개인적인 방법을 내부적으로 만드는 것이 불편하다고 느끼면 직접 단위 테스트의 대상이되어서는 안됩니다.
결국 특정 구현이 아닌 클래스 의 동작 을 테스트하고 있습니다. 전자를 변경하지 않고 후자를 변경할 수 있으며 테스트는 여전히 통과해야합니다.
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;
}
CodeProject에는 개인 메서드 테스트의 장단점에 대해 간략하게 설명하는 기사가 있습니다. 그런 다음 Marcus가 위에서 제공 한 코드와 비슷한 개인 메서드에 액세스하기위한 리플렉션 코드를 제공합니다. 샘플에서 찾은 유일한 문제는 코드가 오버로드 된 메서드를 고려하지 않는다는 것입니다.
여기에서 기사를 찾을 수 있습니다.
을 선언 internal
한 다음을 사용 InternalsVisibleToAttribute
하여 단위 테스트 어셈블리가 볼 수 있도록합니다.
먼저 코드의 개인 메소드를 테스트해서는 안됩니다. '공개 인터페이스'또는 API, 클래스의 공용 요소를 테스트해야합니다. API는 외부 발신자에게 공개하는 모든 공개 메소드입니다.
그 이유는 일단 개인 메소드와 클래스의 내부 테스트를 시작하면 클래스의 구현 (사물)을 테스트에 결합하기 때문입니다. 이는 구현 세부 사항을 변경하기로 결정할 때 테스트도 변경해야 함을 의미합니다.
이러한 이유로 InternalsVisibleToAtrribute를 사용하지 마십시오.
이안 쿠퍼 (Ian Cooper)가이 주제를 다루는 좋은 강연이 있습니다 : 이안 쿠퍼 (Ian Cooper) : TDD.
때로는 개인 선언을 테스트하는 것이 좋습니다. 기본적으로 컴파일러에는 하나의 공용 메소드 만 있습니다 : Compile (string outputFileName, params string [] sourceSFileNames). 각 "숨겨진"선언을 테스트하지 않고는 이러한 방법을 테스트하기 어려울 것입니다.
그렇기 때문에 테스트를 쉽게하기 위해 Visual T #을 만들었습니다. 무료 .NET 프로그래밍 언어입니다 (C # v2.0 호환).
'.-'연산자를 추가했습니다. 그냥 '.'처럼 동작합니다. 테스트 된 프로젝트에서 아무것도 변경하지 않고 테스트에서 숨겨진 선언에 액세스 할 수 있다는 점을 제외하고
웹 사이트를 살펴보십시오 : 무료로 다운로드 하십시오 .
아무도 아직 이것을 말하지 않은 것에 놀랐지 만, 내가 고용 한 해결책은 클래스 내부에서 정적 메소드를 작성하여 자체 테스트하는 것입니다. 이를 통해 테스트 할 공개 및 개인 정보에 액세스 할 수 있습니다.
또한 스크립팅 언어 (Python, Ruby 및 PHP와 같은 OO 기능 사용)에서 실행시 파일 자체를 테스트 할 수 있습니다. 변경 사항이 아무런 영향을 미치지 않도록하는 빠른 방법입니다. 이를 통해 모든 클래스를 테스트 할 수있는 확장 가능한 솔루션을 만들 수 있습니다. 모두 실행하면됩니다. (또한 항상 테스트를 실행하는 빈 메인으로 다른 언어 로이 작업을 수행 할 수 있습니다).
개인 메서드를 테스트하려는 모든 클래스에서 사용할 수있는 명확한 코드 예제를 만들고 싶습니다.
테스트 케이스 클래스에는 이러한 메소드를 포함시킨 다음 표시된대로 사용하십시오.
/**
*
* @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));
원래 개인 함수에 표시되는 순서대로 매개 변수를 발행하십시오.
모든 혼란과 혼란없이 개인 메소드를 실행하려는 사람에게 적합합니다. 이것은 좋은 오래된 반사를 사용하는 모든 단위 테스트 프레임 워크에서 작동합니다.
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");
MbUnit에는 Reflector라는 멋진 래퍼가 있습니다.
Reflector dogReflector = new Reflector(new Dog());
dogReflector.Invoke("DreamAbout", DogDream.Food);
속성에서 값을 설정하고 가져올 수도 있습니다
dogReflector.GetProperty("Age");
"개인 테스트"와 관련하여 나는 완벽한 세상에 동의합니다. 개인 단위 테스트는 아무 의미가 없습니다. 그러나 실제로는 리팩토링 코드 대신 개인 테스트를 작성하려고 할 수 있습니다.
Reflector
보다 강력한 기능으로 대체되었습니다 Mirror
. ( gallio.org/wiki/doku.php?id=mbunit:mirror )
PrivateObject 클래스를 사용 합니다. 그러나 앞에서 언급했듯이 개인 메소드 테스트를 피하는 것이 좋습니다.
Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(retVal);
CC -Dprivate=public
"CC"는 내가 사용하는 시스템의 명령 행 컴파일러입니다. -Dfoo=bar
에 해당 #define foo bar
합니다. 따라서이 편집 옵션은 모든 개인 정보를 공개적으로 변경합니다.
다음은 먼저 메소드 서명입니다.
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) 레거시 코드가있는 경우 개인 메서드를 테스트하는 유일한 방법은 리플렉션입니다.
2) 새 코드 인 경우 다음 옵션이 있습니다.
가장 간단하고 복잡한 주석 방법을 선호합니다. 유일한 문제는 큰 관심사가 아니라고 생각하는 가시성을 높였다는 것입니다. 인터페이스에 항상 코딩해야하므로 MyService 인터페이스와 MyServiceImpl 구현이 있으면 MyServiceTest (테스트 인터페이스 메소드) 및 MyServiceImplTest (테스트 전용 메소드)에 해당하는 테스트 클래스를 가질 수 있습니다. 모든 클라이언트는 개인 메소드의 가시성이 증가하더라도 실제로는 중요하지 않은 방식으로 인터페이스를 사용해야합니다.
Visual Studio 2008에서 개인용 메서드에 대한 테스트 방법을 생성 할 수 있습니다. 개인용 메서드에 대한 단위 테스트를 만들면 테스트 참조 폴더가 테스트 프로젝트에 추가되고 접근자가 해당 폴더에 추가됩니다. 접근자는 단위 테스트 방법의 논리에서도 참조됩니다. 이 접근자를 사용하면 단위 테스트에서 테스트중인 코드에서 개인 메서드를 호출 할 수 있습니다. 자세한 내용은
또한 InternalsVisibleToAtrribute에는 강력한 이름을 요구하는 어셈블리가 있으므로 이전에 해당 요구 사항이 없었던 솔루션에서 작업하는 경우 자체 문제를 발생시킵니다. 접근자를 사용하여 개인 메서드를 테스트합니다. 이에 대한 예는 이 질문 을 참조하십시오 .
InternalsVisibleToAttribute
는 없습니다 . 나는 현재 그렇지 않은 프로젝트에서 현재 사용하고 있습니다.
pre-historic
인터넷 연도 측면에서 뭔가 빠졌거나 아마도이 질문에 불과한 것일 수도 있습니다. 그러나 개인 메소드의 단위 테스트는 이제 쉽고 간단합니다 .Visual Studio는 필요할 때 필요한 접근 자 클래스를 생성하고 간단한 기능 테스트를 원할 수있는 스 니펫으로 테스트 로직을 미리 채 웁니다. 예를 들어보십시오. msdn.microsoft.com/ko-kr/library/ms184807%28VS.90%29.aspx