내부 개인 메서드, 필드 또는 중첩 클래스가있는 클래스를 단위 테스트 (xUnit 사용)하려면 어떻게합니까? 또는 내부 연결 ( static
C / C ++에서)을 사용하여 개인용으로 만들거나 개인용 ( 익명 ) 네임 스페이스에 있는 함수 입니까?
테스트를 실행할 수 있도록 메소드 또는 함수에 대한 액세스 수정자를 변경하는 것은 좋지 않습니다.
내부 개인 메서드, 필드 또는 중첩 클래스가있는 클래스를 단위 테스트 (xUnit 사용)하려면 어떻게합니까? 또는 내부 연결 ( static
C / C ++에서)을 사용하여 개인용으로 만들거나 개인용 ( 익명 ) 네임 스페이스에 있는 함수 입니까?
테스트를 실행할 수 있도록 메소드 또는 함수에 대한 액세스 수정자를 변경하는 것은 좋지 않습니다.
답변:
최신 정보:
약 10 년 후 아마도 개인 방법 또는 접근 할 수없는 멤버를 테스트하는 가장 좋은 방법
@Jailbreak
은 매니 폴드 프레임 워크 를 이용하는 것 입니다.@Jailbreak Foo foo = new Foo(); // Direct, *type-safe* access to *all* foo's members foo.privateMethod(x, y, z); foo.privateField = value;
이런 식으로 코드를 안전하게 입력하고 읽을 수 있습니다. 테스트를위한 디자인 타협, 과도한 노출 방법 및 필드가 없습니다.
레거시 Java 애플리케이션이 있고 메소드의 가시성을 변경할 수없는 경우 개인 메소드를 테스트하는 가장 좋은 방법은 reflection 을 사용하는 것 입니다.
내부적으로 우리는 헬퍼를 사용하여 get / set private
및 private static
변수뿐만 아니라 호출 private
및 private static
메소드를 사용합니다. 다음 패턴을 사용하면 개인 메소드 및 필드와 관련된 거의 모든 작업을 수행 할 수 있습니다. 물론 private static final
리플렉션을 통해 변수를 변경할 수는 없습니다 .
Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);
그리고 필드의 경우 :
Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
참고 :
1. 메소드TargetClass.getDeclaredMethod(methodName, argClasses)
를 살펴볼 수 있습니다private
. 같은 것이 적용됩니다getDeclaredField
.
2. 개인setAccessible(true)
과 놀아야합니다.
비공개 메소드를 테스트하는 가장 좋은 방법은 다른 공개 메소드를 사용하는 것입니다. 이를 수행 할 수없는 경우 다음 조건 중 하나에 해당합니다.
개인 메서드를 직접 테스트해야 할 정도로 충분히 복잡한 클래스에 개인 메서드가 있으면 코드 냄새가납니다. 클래스가 너무 복잡합니다.
이러한 문제를 해결하기위한 나의 일반적인 접근 방식은 흥미로운 부분을 포함하는 새로운 클래스를 만드는 것입니다. 종종,이 방법과 상호 작용하는 필드, 아마도 다른 방법 또는 두 개를 새로운 클래스로 추출 할 수 있습니다.
새 클래스는 이러한 메소드를 '공개'로 노출하므로 단위 테스트에 액세스 할 수 있습니다. 새 클래스와 기존 클래스는 이제 원래 클래스보다 간단합니다. 이는 간단합니다 (물건을 단순하게 유지해야하거나 길을 잃을 수 있습니다!).
사람들이 자신의 두뇌를 사용하지 않고 수업을 만들 것을 제안하지는 않습니다. 여기서 중요한 점은 좋은 새로운 수업을 찾는 데 도움이되는 단위 테스트의 힘을 사용하는 것입니다.
과거에 Java에 대해이 작업을 수행하기 위해 리플렉션 을 사용 했으며 내 의견으로는 큰 실수였습니다.
엄밀히 말하면 개인 메소드를 직접 테스트하는 단위 테스트를 작성 해서는 안됩니다 . 당신이 해야 테스트 할 것은 클래스가 다른 객체와 함께 가지고있는 공공 계약이다; 객체의 내부를 직접 테스트해서는 안됩니다. 다른 개발자가 클래스의 내부 계약에 영향을 미치지 않는 클래스의 작은 내부 변경을 원할 경우 리플렉션 기반 테스트를 수정하여 작동하는지 확인해야합니다. 프로젝트 전체에서이 작업을 반복적으로 수행하면 단위 테스트가 코드 상태의 유용한 측정을 중단하고 개발에 방해가되고 개발 팀에 성가심이되기 시작합니다.
대신 내가 권장하는 것은 Cobertura와 같은 코드 범위 도구를 사용하여 작성하는 단위 테스트가 개인 방법으로 코드의 적절한 범위를 제공하는지 확인하는 것입니다. 이렇게하면 개인 방법이 수행하는 작업을 간접적으로 테스트하고 더 높은 민첩성을 유지할 수 있습니다.
이 기사 : JUnit 및 SuiteRunner (빌 벤너)를 사용 하여 개인 메소드 테스트 에는 기본적으로 4 가지 옵션이 있습니다.
- 개인용 메소드를 테스트하지 마십시오.
- 메소드 패키지 액세스를 제공하십시오.
- 중첩 된 테스트 클래스를 사용하십시오.
- 반사를 사용하십시오.
일반적으로 단위 테스트는 클래스 또는 단위의 공용 인터페이스를 연습하기위한 것입니다. 따라서 전용 메소드는 명시 적으로 테스트하지 않을 구현 세부 사항입니다.
개인 메소드를 테스트하려는 위치의 두 가지 예 :
SecurityManager
이것을 막도록 구성되지 않은 경우 대부분의 경우 개인 메소드조차도 볼 수있는 반영입니다 )."계약"만 테스트한다는 생각을 이해합니다. 그러나 실제로 코드를 테스트하지 말라고 주장 할 수는 없습니다. 마일리지는 다를 수 있습니다.
그래서 내 장단점은 보안 및 SDK를 손상시키지 않고 JUnit을 반영하여 복잡하게 만드는 것입니다.
private
유일한 대안은 아닙니다. 액세스 수정자는 없으며 package private
단위 테스트가 동일한 패키지에있는 한 단위 테스트를 수행 할 수 있습니다.
개인용 메소드는 공용 메소드에 의해 호출되므로 공용 메소드에 대한 입력은 해당 공용 메소드에 의해 호출 된 개인용 메소드도 테스트해야합니다. 공용 메소드가 실패하면 개인 메소드에서 실패가 될 수 있습니다.
내가 사용한 또 다른 방법은 개인 또는 보호 된 패키지로 개인 메서드를 변경 한 다음 Google Guava 라이브러리 의 @VisibleForTesting 주석으로 보완하는 것입니다 .
이렇게하면이 방법을 사용하는 사람에게주의를 기울이고 패키지에서도 직접 액세스하지 않도록 지시합니다. 또한 테스트 클래스는 물리적 으로 동일한 패키지에있을 필요는 없지만 테스트 폴더 아래의 동일한 패키지에 있어야 합니다.
예를 들어, 테스트 할 메소드가있는 경우 src/main/java/mypackage/MyClass.java
테스트 호출을에 배치해야합니다 src/test/java/mypackage/MyClassTest.java
. 그렇게하면 테스트 클래스의 테스트 메소드에 액세스 할 수 있습니다.
크고 기발한 클래스와 테스트 레거시 코드에, 종종 내가 쓰고있어 하나의 개인 (또는 공용) 메서드를 테스트 할 수있는 것이 많은 도움이 될 것입니다 지금을 .
Java 용 junitx.util.PrivateAccessor -package를 사용합니다 . 개인 메소드 및 개인 필드에 액세스하는 데 도움이되는 많은 원 라이너.
import junitx.util.PrivateAccessor;
PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);
Class
이러한 방법에서 첫 번째 매개 변수로 a 를 사용하는 경우 static
멤버 에만 액세스해야합니다 .
에서 스프링 프레임 워크 는이 방법을 사용하여 개인 방법을 테스트 할 수 있습니다 :
ReflectionTestUtils.invokeMethod()
예를 들면 다음과 같습니다.
ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");
Java 용 리플렉션 을 사용하여 Cem Catikkas의 솔루션을 사용해 본 결과 , 여기에 설명 된 것보다 더 우아한 솔루션이라고 말할 수 있습니다. 그러나 리플렉션 사용에 대한 대안을 찾고 있고 테스트중인 소스에 액세스 할 수있는 경우 여전히 옵션입니다.
특히와 클래스의 private 메소드, 테스트 가능한 장점이있다 테스트 주도 개발 당신이 어떤 코드를 작성하기 전에 작은 테스트를 설계하고 싶습니다.
비공개 멤버 및 메소드에 액세스하여 테스트를 작성하면 공개 메소드에만 액세스하여 구체적으로 타겟팅하기 어려운 코드 영역을 테스트 할 수 있습니다. 공용 메소드에 여러 단계가 포함 된 경우 여러 개인 메소드로 구성되어 개별적으로 테스트 될 수 있습니다.
장점 :
단점 :
그러나 지속적인 테스트에이 방법이 필요한 경우 개인 방법을 추출해야한다는 신호일 수 있습니다.이 방법은 전통적인 공개 방법으로 테스트 할 수 있습니다.
이것이 어떻게 작동하는지에 대한 복잡한 예입니다.
// Import statements and package declarations
public class ClassToTest
{
private int decrement(int toDecrement) {
toDecrement--;
return toDecrement;
}
// Constructor and the rest of the class
public static class StaticInnerTest extends TestCase
{
public StaticInnerTest(){
super();
}
public void testDecrement(){
int number = 10;
ClassToTest toTest= new ClassToTest();
int decremented = toTest.decrement(number);
assertEquals(9, decremented);
}
public static void main(String[] args) {
junit.textui.TestRunner.run(StaticInnerTest.class);
}
}
}
내부 클래스는로 컴파일됩니다 ClassToTest$StaticInnerTest
.
다른 사람들이 말했듯이 개인적인 방법을 직접 테스트하지 마십시오. 다음은 몇 가지 생각입니다.
단위 테스트에서 코드 적용 범위를 실행하십시오. 방법이 완전히 테스트되지 않은 경우 테스트를 추가하여 적용 범위를 늘리십시오. 100 % 코드 적용 범위를 목표로하지만 달성하지 못할 수도 있음을 인식하십시오.
비공개 메소드는 공개 메소드에 의해 소비됩니다. 그렇지 않으면 죽은 코드입니다. 그렇기 때문에 공개 메소드를 테스트하여 공개 메소드의 예상 결과와 그에 따라 사용되는 개인 메소드를 확인합니다.
공용 메소드에서 단위 테스트를 실행하기 전에 디버깅하여 개인 메소드 테스트를 테스트해야합니다.
또한 테스트 중심 개발을 사용하여 디버깅 할 수 있으며 모든 어설 션이 충족 될 때까지 단위 테스트를 디버깅합니다.
개인적으로 TDD를 사용하여 클래스를 작성하는 것이 좋습니다. 퍼블릭 메소드 스텁을 생성 한 다음 미리 정의 된 모든 어설 션을 사용 하여 단위 테스트를 생성 하므로 메소드의 예상 결과는 코딩 전에 결정됩니다. 이렇게하면 단위 테스트 어설 션이 결과에 적합하게 만드는 잘못된 경로를 밟지 않아도됩니다. 그러면 수업은 견고하며 모든 단위 테스트를 통과하면 요구 사항을 충족합니다.
Spring을 사용하는 경우 ReflectionTestUtils 는 최소한의 노력으로 여기에 도움이되는 편리한 도구를 제공합니다. 예를 들어, 바람직하지 않은 공개 설정자를 추가하지 않고 개인 구성원에 모형을 설정하려면 다음을 수행하십시오.
ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);
당신이 꺼리거나 변경할 수없는 기존 코드를 테스트하려는 경우 리플렉션이 좋은 선택입니다.
클래스의 디자인이 여전히 유연하고 개별적으로 테스트하려는 복잡한 개인 메소드가있는 경우 별도의 클래스로 가져 와서 해당 클래스를 개별적으로 테스트하는 것이 좋습니다. 원래 클래스의 공용 인터페이스를 변경할 필요는 없습니다. 내부적으로 헬퍼 클래스의 인스턴스를 작성하고 헬퍼 메소드를 호출 할 수 있습니다.
헬퍼 메소드에서 발생하는 어려운 오류 조건을 테스트하려면 한 단계 더 나아가십시오. 도우미 클래스에서 인터페이스를 추출하고 공용 getter 및 setter를 원래 클래스에 추가하여 도우미 클래스 (인터페이스를 통해 사용됨)를 주입 한 다음 도우미 클래스의 모의 버전을 원래 클래스에 주입하여 원래 클래스의 방법을 테스트하십시오. 도우미의 예외에 응답합니다. 이 방법은 도우미 클래스를 테스트하지 않고 원래 클래스를 테스트하려는 경우에도 유용합니다.
내부 구현을 변경할 때마다 클라이언트 코드 (이 경우 테스트)가 중단되므로 개인 메서드를 테스트하면 클래스 캡슐화가 중단됩니다.
따라서 개인 메소드를 테스트하지 마십시오.
JUnit.org FAQ 페이지 의 답변 :
그러나 당신이해야한다면 ...
JDK 1.3 이상을 사용하는 경우 PrivilegedAccessor 의 도움으로 리플렉션을 사용하여 액세스 제어 메커니즘을 파괴 할 수 있습니다 . 사용 방법에 대한 자세한 내용은 이 기사를 읽으십시오 .
JDK 1.6 이상을 사용하고 @Test로 테스트에 주석을 달 경우 Dp4j 를 사용 하여 테스트 방법에 리플렉션을 주입 할 수 있습니다 . 사용 방법에 대한 자세한 내용은 이 테스트 스크립트를 참조하십시오 .
코드를 변경할 수없는 레거시 애플리케이션의 프라이빗 메소드를 테스트하려는 경우 Java의 한 가지 옵션은 jMockit 이며, 이는 클래스 전용 인 경우에도 오브젝트에 대한 모의 객체를 작성할 수있게합니다.
Deencapsulation.invoke(instance, "privateMethod", param1, param2);
나는 개인적인 방법을 테스트하지 않는 경향이 있습니다. 광기가 있습니다. 개인적으로 공개적으로 노출 된 인터페이스 만 테스트해야한다고 생각합니다 (보호 및 내부 방법 포함).
JUnit을 사용하는 경우 junit-addons를 살펴보십시오 . Java 보안 모델을 무시하고 개인용 메소드 및 속성에 액세스 할 수 있습니다.
코드를 약간 리팩토링하는 것이 좋습니다. 코드를 테스트하기 위해 리플렉션이나 다른 종류의 물건을 사용하는 것에 대해 생각해야 할 때 코드에 문제가 있습니다.
다른 유형의 문제를 언급했습니다. 개인 필드부터 시작하겠습니다. 개인 필드의 경우 새 생성자를 추가하고 필드를 주입했습니다. 이 대신에 :
public class ClassToTest {
private final String first = "first";
private final List<String> second = new ArrayList<>();
...
}
나는 이것을 사용했을 것이다 :
public class ClassToTest {
private final String first;
private final List<String> second;
public ClassToTest() {
this("first", new ArrayList<>());
}
public ClassToTest(final String first, final List<String> second) {
this.first = first;
this.second = second;
}
...
}
일부 레거시 코드에서도 문제가되지 않습니다. 오래된 코드는 빈 생성자를 사용하므로 요청하면 리팩토링 된 코드가 더 깨끗해지며 테스트에 필요한 값을 반영하지 않고 주입 할 수 있습니다.
이제 개인 메소드에 대해 개인적인 경험으로 테스트를 위해 개인 메소드를 스텁 해야하는 경우 해당 메소드는 해당 클래스에서 아무 관련이 없습니다. 이 경우 일반적인 패턴 은 인터페이스 내 에서 랩핑Callable
한 다음 생성자에서도 해당 인터페이스를 전달하는 것입니다 (여러 생성자 트릭 포함).
public ClassToTest() {
this(...);
}
public ClassToTest(final Callable<T> privateMethodLogic) {
this.privateMethodLogic = privateMethodLogic;
}
내가 쓴 대부분은 의존성 주입 패턴 인 것처럼 보입니다. 내 개인적인 경험에서는 테스트하는 동안 정말 유용하며 이러한 종류의 코드는 더 깨끗하고 유지 관리하기가 더 쉽다고 생각합니다. 중첩 클래스에 대해서도 마찬가지입니다. 중첩 클래스에 무거운 논리가 포함되어 있으면 패키지 개인 클래스로 이동하여 필요한 클래스에 주입하면 더 좋습니다.
레거시 코드를 리팩토링하고 유지 관리하는 데 사용했던 몇 가지 다른 디자인 패턴도 있지만 테스트 할 코드의 경우에 따라 다릅니다. 리플렉션을 사용하는 것은 문제가되지 않지만 모든 배포 전에 테스트를 많이 수행하고 테스트를 수행하는 엔터프라이즈 응용 프로그램을 사용하는 경우 모든 작업이 실제로 느려집니다 (성가 시며 그런 종류는 좋아하지 않습니다).
세터 주입도 있지만 사용하지 않는 것이 좋습니다. 생성자를 고수하고 실제로 필요할 때 모든 것을 초기화하여 필요한 종속성을 주입 할 가능성을 남겨 두는 것이 좋습니다.
ClassToTest(Callable)
주사에 동의하지 않습니다 . 즉하게 ClassToTest
더 복잡. 간단하게 유지하십시오. 또한이를 위해서는 다른 사람 이 사장 ClassToTest
이 ClassToTest
되어야 할 것을 말해야 합니다 . 논리를 주입 할 수있는 곳이 있지만 그렇지 않습니다. 클래스를 유지 관리하기 어렵게 만들었습니다.
X
증가시키지 않기 X
때문에 더 많은 문제를 일으킬 수있는 방식으로 구현 한 경우 추가 테스트가 필요합니다. (이것은 무한 루프가 아니며 각 반복은 이전보다 작지만 여전히 성가시다)
ClassToTest
을 유지하기 어려워 하는지 설명해 주 시겠습니까? 실제로 응용 프로그램을 유지 관리하기가 더 쉬워집니다. 필요한 모든 값 first
과 '두 번째'변수에 대해 새로운 클래스를 만드는 것이 무엇 입니까?
class A{ A(){} f(){} }
보다 간단합니다 class A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }
. 그것이 사실이 아니고 클래스의 논리를 생성자 인수로 제공하는 것이 더 쉽다는 것이 사실이라면 모든 논리를 생성자 인수로 전달합니다. 나는 당신이 어떻게 주장 할 수 있는지 보지 못합니다 class B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }
. 이와 같은 주사가 의미가있는 경우가 있지만 귀하의 사례를 "유지하기 더 쉽다"고 생각하지 않습니다
개인 메소드는 동일한 클래스 내에서만 액세스 할 수 있습니다. 따라서 테스트 클래스에서 대상 클래스의 "비공개"메소드를 테스트 할 방법이 없습니다. 방법은 단위 테스트를 수동으로 수행하거나 분석법을 "비공개"에서 "보호"로 변경할 수 있다는 것입니다.
그런 다음 보호 된 메소드는 클래스가 정의 된 동일한 패키지 내에서만 액세스 할 수 있습니다. 따라서 대상 클래스의 보호 된 메소드를 테스트한다는 것은 대상 클래스와 동일한 패키지에서 테스트 클래스를 정의해야 함을 의미합니다.
위의 모든 사항이 요구 사항에 맞지 않으면 리플렉션 방법 을 사용하여 개인 방법에 액세스하십시오.
개인 필드를 테스트하는 일반적인 기능은 다음과 같습니다.
protected <F> F getPrivateField(String fieldName, Object obj)
throws NoSuchFieldException, IllegalAccessException {
Field field =
obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (F)field.get(obj);
}
예를 들어 아래를 참조하십시오.
다음 import 문을 추가해야합니다.
import org.powermock.reflect.Whitebox;
이제 private 메소드, 호출 할 메소드 이름 및 추가 매개 변수가있는 오브젝트를 아래와 같이 직접 전달할 수 있습니다.
Whitebox.invokeMethod(obj, "privateMethod", "param1");
오늘은 개인 메소드 및 필드 테스트를 돕기 위해 Java 라이브러리를 푸시했습니다. Android를 염두에두고 설계되었지만 모든 Java 프로젝트에 실제로 사용할 수 있습니다.
개인 메소드 또는 필드 또는 생성자가있는 코드가 있으면 BoundBox 를 사용할 수 있습니다 . 당신이 찾고있는 것을 정확하게 수행합니다. 다음은 테스트를 위해 Android 활동의 두 개인 필드에 액세스하는 테스트의 예입니다.
@UiThreadTest
public void testCompute() {
// Given
boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());
// When
boundBoxOfMainActivity.boundBox_getButtonMain().performClick();
// Then
assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}
BoundBox를 사용하면 개인 / 보호 필드, 메서드 및 생성자를 쉽게 테스트 할 수 있습니다. 상속에 의해 숨겨져있는 것들에 접근 할 수도 있습니다. 실제로 BoundBox는 캡슐화를 중단합니다. 리플렉션을 통해 모든 것에 액세스 할 수 있지만 컴파일 타임에 모든 것이 검사됩니다.
일부 레거시 코드를 테스트하는 데 이상적입니다. 조심해서 사용하십시오. ;)
먼저,이 질문을 던질 것입니다 : 왜 당신의 개인 회원들이 고립 된 테스트를 필요로합니까? 공공 장소와는 별도로 테스트가 필요한 복잡한 행동을 제공하는 것이 복잡합니까? '코드 라인'테스트가 아니라 단위 테스트입니다. 작은 물건을 땀 흘리지 마십시오.
이들이 개인 구성원이 복잡 할 정도로 큰 '단위'가 될만큼 충분히 크고 큰 경우-개인 구성원을이 클래스에서 리팩토링하는 것을 고려하십시오.
리팩토링이 부적절하거나 실행 불가능한 경우, 단위 테스트 중에 전략 패턴을 사용하여 이러한 개인 멤버 함수 / 멤버 클래스에 대한 액세스를 대체 할 수 있습니까? 단위 테스트에서 전략은 추가 검증을 제공하지만 릴리스 빌드에서는 간단한 통과입니다.
나는 최근 에이 문제가 있었고 Java 반사 API를 명시 적으로 사용하는 문제를 피하는 Picklock 이라는 작은 도구를 작성했습니다 . 두 가지 예 :
호출 메소드, 예를 들어 private void method(String s)
-Java 리플렉션
Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");
호출 방법 (예 : private void method(String s)
Picklock)
interface Accessible {
void method(String s);
}
...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");
필드 설정 (예 : private BigInteger amount;
Java 리플렉션)
Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));
필드 설정 (예 : private BigInteger amount;
Picklock 별)
interface Accessible {
void setAmount(BigInteger amount);
}
...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));
이를 위해 PowerMockito가 만들어졌습니다. 메이븐 의존성 사용
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-mockito-release-full</artifactId>
<version>1.6.4</version>
</dependency>
그럼 넌 할 수있어
import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);