개인 함수, 개인 메서드, 필드 또는 내부 클래스가있는 클래스를 어떻게 테스트합니까?


2726

내부 개인 메서드, 필드 또는 중첩 클래스가있는 클래스를 단위 테스트 (xUnit 사용)하려면 어떻게합니까? 또는 내부 연결 ( staticC / C ++에서)을 사용하여 개인용으로 만들거나 개인용 ( 익명 ) 네임 스페이스에 있는 함수 입니까?

테스트를 실행할 수 있도록 메소드 또는 함수에 대한 액세스 수정자를 변경하는 것은 좋지 않습니다.


257
프라이빗 메소드를 테스트하는 가장 좋은 방법은 직접 테스트하는 것이 아닙니다.
Surya


383
동의하지 않습니다. 길거나 이해하기 어려운 (공개) 방법을 리팩토링해야합니다. 공개 메소드 대신에 작은 (비공개) 메소드를 테스트하지 않는 것은 어리석은 일입니다.
Michael Piefel

181
가시성이라는 이유로 메소드를 테스트하지 않습니다. 단위 테스트조차도 가장 작은 코드 조각이어야하며 공개 메소드 만 테스트하는 경우 오류가 발생하는 위치 (해당 메소드 또는 다른 것)를 절대 확인할 수 없습니다.
Dainius

126
구현이 아닌 클래스 기능 을 테스트해야합니다 . 개인 방법을 테스트하고 싶습니까? 이를 호출하는 공개 메소드를 테스트하십시오. 클래스가 제공하는 기능을 철저히 테스트하면 내부가 정확하고 신뢰할 수있는 것으로 입증되었습니다. 내부 조건을 테스트 할 필요가 없습니다. 테스트는 테스트 된 클래스와의 분리를 유지해야합니다.
Calimar41

답변:


1640

최신 정보:

약 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 privateprivate static변수뿐만 아니라 호출 privateprivate 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)과 놀아야합니다.


350
API를 모르는 경우 유용하지만, 이런 방식으로 개인 메소드를 테스트해야하는 경우 설계에 문제가 있습니다. 다른 포스터에 따르면 단위 테스트는 클래스의 계약을 테스트해야한다고합니다. 계약이 너무 광범위하고 시스템을 너무 많이 인스턴스화하면 디자인을 다루어야합니다.
andygavin 2016 년

21
매우 유용한. 이것을 사용하면 난독 처리 후 테스트를 실행하면 실패 할 것이라는 점을 명심해야합니다.
Rick Minerich 1

20
예제 코드는 저에게는 효과가 없었지만,이를 통해 티그가 더 명확 해졌습니다. java2s.com/Tutorial/Java/0125__Reflection/…
Rob

35
리플렉션을 직접 사용하는 것보다 Powermock 과 같은 라이브러리를 사용하는 것이 훨씬 낫습니다 .
Michael Piefel

25
이것이 바로 테스트 구동 설계가 도움이되는 이유입니다. 동작의 유효성을 검사하기 위해 노출해야 할 항목을 파악하는 데 도움이됩니다.
Thorbjørn Ravn Andersen

621

비공개 메소드를 테스트하는 가장 좋은 방법은 다른 공개 메소드를 사용하는 것입니다. 이를 수행 할 수없는 경우 다음 조건 중 하나에 해당합니다.

  1. 전용 메소드는 데드 코드입니다
  2. 테스트중인 클래스 근처에 디자인 냄새가 있습니다.
  3. 테스트하려는 방법은 비공개가 아니어야합니다

6
또한, 우리는 어쨌든 100 % 코드 범위에 가까워지지 않으므로 고객이 실제로 직접 사용하는 방법에 대해 품질 테스트를 수행하는 데 시간을 집중하십시오.
grinch

30
@grinch Spot on. 개인 메소드 테스트에서 얻을 수있는 유일한 것은 정보 디버깅 뿐이며 디버거의 목적입니다. 수업 계약에 대한 테스트에 전체 범위가 포함되어 있으면 필요한 모든 정보가 있습니다. 전용 메소드는 구현 세부 사항입니다. 테스트하면 계약이 변경되지 않더라도 구현이 변경 될 때마다 테스트를 변경해야합니다. 소프트웨어의 전체 수명주기에서 이는 소프트웨어가 제공하는 이점보다 훨씬 많은 비용이 듭니다.
Erik Madsen 2016 년

9
@AlexWien 당신이 맞아요. 적용 범위는 올바른 용어가 아닙니다. 내가 말한 것은 "클래스 계약 테스트에서 모든 의미있는 입력과 모든 의미있는 상태를 다루는 경우"였습니다. 이것은 올바르게 지적했듯이 공개 인터페이스를 통해 코드에 대해 또는 간접적으로 코드를 테스트하는 것이 불가능할 수도 있습니다. 테스트중인 일부 코드의 경우 : (1) 계약이 너무 관대하거나 (예 : 메소드에 너무 많은 매개 변수) (2) 계약이 너무 모호합니다 (예 : 메소드 동작) 클래스 상태에 따라 크게 다름). 나는 두 가지 디자인 냄새를 고려할 것입니다.
에릭 매드슨

16
거의 모든 개인 분석법을 직접 테스트해서는 안됩니다 (유지 보수 비용 절감). 예외 : 과학적 방법은 f (a (b (c (x), d (y))), a (e (x, z)), b (f (x, y, z), z))와 같은 형식을 가질 수 있습니다. 여기서 a, b, c, d, e 및 f는 엄청나게 복잡한 표현이지만이 단일 수식 외부에서는 쓸모가 없습니다. 일부 기능 (MD5 생각)은 반전하기 어렵 기 때문에 동작 공간을 완전히 포함하는 매개 변수를 선택하기가 어렵습니다 (NP- 하드). a, b, c, d, e, f를 독립적으로 테스트하는 것이 더 쉽습니다. 공개 또는 패키지 전용으로 만드는 것은 재사용이 가능하다는 것입니다. 해결책 : 비공개로 설정하고 테스트하십시오.
시조

4
@Eponymous 나는 이것에 약간 찢어진입니다. 내 객체 지향적 순수 주의자는 정적 개인 메소드 대신 객체 지향 접근 방식을 사용해야하므로 퍼블릭 인스턴스 메소드를 통해 테스트 할 수 있다고 말합니다. 그러나 다시 과학적 프레임 워크에서 메모리 오버 헤드는 전적으로 정적 접근 방식을 주장하는 것으로 생각됩니다.
Erik Madsen

323

개인 메서드를 직접 테스트해야 할 정도로 충분히 복잡한 클래스에 개인 메서드가 있으면 코드 냄새가납니다. 클래스가 너무 복잡합니다.

이러한 문제를 해결하기위한 나의 일반적인 접근 방식은 흥미로운 부분을 포함하는 새로운 클래스를 만드는 것입니다. 종종,이 방법과 상호 작용하는 필드, 아마도 다른 방법 또는 두 개를 새로운 클래스로 추출 할 수 있습니다.

새 클래스는 이러한 메소드를 '공개'로 노출하므로 단위 테스트에 액세스 할 수 있습니다. 새 클래스와 기존 클래스는 이제 원래 클래스보다 간단합니다. 이는 간단합니다 (물건을 단순하게 유지해야하거나 길을 잃을 수 있습니다!).

사람들이 자신의 두뇌를 사용하지 않고 수업을 만들 것을 제안하지는 않습니다. 여기서 중요한 점은 좋은 새로운 수업을 찾는 데 도움이되는 단위 테스트의 힘을 사용하는 것입니다.


24
질문은 개인 메소드를 테스트하는 방법이었습니다. 당신은 그것을 위해 새로운 클래스를 만들고 (더 많은 복잡성을 추가) 새 클래스를 만들지 말 것을 제안합니다. 어떻게 개인 메소드를 테스트합니까?
Dainius

27
@Dainius : 나는 새로운 클래스를 생성하지 않는 것이 좋습니다 전적으로 당신이 방법을 테스트 할 수 있도록. 테스트를 작성하면 디자인을 개선하는 데 도움이됩니다. 좋은 디자인은 쉽게 테스트 할 수 있습니다.
Jay Bazuzi

13
그러나 좋은 OOD가 해당 클래스 / 객체가 올바르게 작동하는 데 필요한 메소드 만 노출 (공개)한다는 데 동의합니다. 다른 모든 개인 / 보호해야합니다. 따라서 일부 개인 방법에는 논리가 있으며 IMO 테스트는 소프트웨어의 품질 만 향상시킵니다. 물론 일부 코드가 복잡하면 별도의 메소드 / 클래스로 나누어야한다는 데 동의합니다.
Dainius

6
@Danius : 대부분의 경우 새 클래스를 추가하면 복잡성이 줄어 듭니다. 그냥 측정하십시오. 어떤 경우에는 테스트 가능성이 OOD와 싸 웁니다. 현대적인 접근 방식은 테스트 가능성을 선호합니다.
AlexWien

5
입니다 정확히 드라이브에 테스트의 피드백을 사용하고 디자인을 개선하는 방법! 당신의 시험을 듣습니다. 작성하기 어려운 경우 코드에서 중요한 것을 알려줍니다!
pnschofield 2016

289

과거에 Java에 대해이 작업을 수행하기 위해 리플렉션 을 사용 했으며 내 의견으로는 큰 실수였습니다.

엄밀히 말하면 개인 메소드를 직접 테스트하는 단위 테스트를 작성 해서는 안됩니다 . 당신이 해야 테스트 할 것은 클래스가 다른 객체와 함께 가지고있는 공공 계약이다; 객체의 내부를 직접 테스트해서는 안됩니다. 다른 개발자가 클래스의 내부 계약에 영향을 미치지 않는 클래스의 작은 내부 변경을 원할 경우 리플렉션 기반 테스트를 수정하여 작동하는지 확인해야합니다. 프로젝트 전체에서이 작업을 반복적으로 수행하면 단위 테스트가 코드 상태의 유용한 측정을 중단하고 개발에 방해가되고 개발 팀에 성가심이되기 시작합니다.

대신 내가 권장하는 것은 Cobertura와 같은 코드 범위 도구를 사용하여 작성하는 단위 테스트가 개인 방법으로 코드의 적절한 범위를 제공하는지 확인하는 것입니다. 이렇게하면 개인 방법이 수행하는 작업을 간접적으로 테스트하고 더 높은 민첩성을 유지할 수 있습니다.


76
+1합니다. 내 생각에 그것은 질문에 대한 가장 좋은 대답입니다. 개인 메소드를 테스트하여 구현을 테스트합니다. 이는 클래스 계약의 입력 / 출력을 테스트하는 단위 테스트의 목적을 무효화합니다. 테스트는 의존성에 대해 호출하는 메소드를 조롱 할 수있는 구현에 대해서만 충분히 알고 있어야 합니다 . 더 이상 없습니다. 테스트를 변경하지 않고 구현을 변경할 수없는 경우 테스트 전략이 좋지 않을 수 있습니다.
Colin M

10
@Colin M 그것은 정말로 그가 요구하는 것이 아닙니다.;) 그를 결정하게하십시오. 프로젝트를 모른다.
Aaron Marcus

2
사실이 아닙니다. 공용 메소드를 사용하여 개인 메소드의 일부를 테스트하려면 많은 노력이 필요할 수 있습니다. 공개 메소드는 개인 메소드를 호출하는 회선에 도달하기 전에 상당한 설정이 필요할 수 있습니다.
ACV

1
동의한다. 계약이 이행되면 테스트해야합니다. 이것이 어떻게 수행되는지 테스트해서는 안됩니다. 100 % 코드 적용 범위 (비공개 방법)에 도달하지 않고 컨택이 완료되면 해당 코드가 작동하지 않거나 쓸모없는 코드 일 수 있습니다.
wuppi

207

이 기사 : JUnit 및 SuiteRunner (빌 벤너)를 사용 하여 개인 메소드 테스트 에는 기본적으로 4 가지 옵션이 있습니다.

  1. 개인용 메소드를 테스트하지 마십시오.
  2. 메소드 패키지 액세스를 제공하십시오.
  3. 중첩 된 테스트 클래스를 사용하십시오.
  4. 반사를 사용하십시오.

@JimmyT., "제작 코드"의 대상에 따라 다릅니다. 대상 사용자가 sysadmins 인 프로덕션 코드 인 VPN 내에 배치 된 응용 프로그램을 위해 생성 된 코드를 호출합니다.
Pacerier

@Pacerier 무슨 뜻인가요?
지미 T.

1
위에서 언급 한 것처럼 다섯 번째 옵션은 개인 메소드를 호출하는 공개 메소드를 테스트하는 것입니까? 옳은?
user2441441

1
@LorenzoLerate 임베디드 개발을하고 있기 때문에이 문제로 레슬링을 해왔으며 가능한 한 작은 소프트웨어를 원합니다. 크기 제약과 성능이 내가 생각할 수있는 유일한 이유입니다.

리플렉션을 사용한 테스트가 정말 마음에 듭니다. 여기에서 패턴 메소드 method = targetClass.getDeclaredMethod (methodName, argClasses); method.setAccessible (true); return 메소드. invoke (targetObject, argObjects);
Aguid

127

일반적으로 단위 테스트는 클래스 또는 단위의 공용 인터페이스를 연습하기위한 것입니다. 따라서 전용 메소드는 명시 적으로 테스트하지 않을 구현 세부 사항입니다.


16
이것이 가장 좋은 대답입니다. 또는 일반적으로 말하듯이 방법이 아닌 행동을 테스트하십시오. 단위 테스트는 소스 코드 메트릭, 정적 코드 분석 도구 및 코드 검토를 대체하지 않습니다. 개인 메소드가 너무 복잡하여 별도의 테스트가 필요한 경우 더 많은 테스트를 수행하지 않고 리팩토링해야합니다.
Dan Haynes

14
개인 메소드를 작성하지 말고 10 개의 다른 작은 클래스를 만드십시오.
면도칼

1
아니요, 기본적으로 비공개 메소드를 호출하는 공개 메소드를 사용하여 비공개 메소드의 기능을 테스트 할 수 있음을 의미합니다.
Akshat Sharda

66

개인 메소드를 테스트하려는 위치의 두 가지 예 :

  1. 해독 루틴 -테스트 목적으로 만 볼 수 있도록 다른 사람이 볼 수 있도록하고 싶지 않으면 다른 사람이 해독을 위해 사용할 수 있습니다. 그러나 그것들은 코드에 내재되어 있고 복잡하며 항상 작동해야합니다 (명확한 예외는 SecurityManager이것을 막도록 구성되지 않은 경우 대부분의 경우 개인 메소드조차도 볼 수있는 반영입니다 ).
  2. 커뮤니티 소비를 위한 SDK 작성 여기에서 대중은 완전히 다른 의미를 취합니다. 전 세계가 볼 수있는 코드이기 때문에 (내 응용 프로그램의 내부가 아니라). SDK 사용자가 그것을보고 싶지 않으면 코드를 개인 메소드에 넣습니다. SDK 프로그래밍이 작동하는 방식으로 코드 냄새로 보지 않습니다. 그러나 물론 여전히 개인 메서드를 테스트해야하며 SDK의 기능이 실제로 존재하는 곳입니다.

"계약"만 테스트한다는 생각을 이해합니다. 그러나 실제로 코드를 테스트하지 말라고 주장 할 수는 없습니다. 마일리지는 다를 수 있습니다.

그래서 내 장단점은 보안 및 SDK를 손상시키지 않고 JUnit을 반영하여 복잡하게 만드는 것입니다.


5
메소드를 테스트하기 위해 메소드를 공개해서는 안되지만 private유일한 대안은 아닙니다. 액세스 수정자는 없으며 package private단위 테스트가 동일한 패키지에있는 한 단위 테스트를 수행 할 수 있습니다.
ngreen

1
나는 OP가 아니라 특히 포인트 1에 대한 귀하의 답변에 대해 언급하고있었습니다. 메소드를 공개하지 않으려는 경우 메소드를 비공개로 만들 필요가 없습니다.
ngreen

1
@ngreen true thx-나는 "public"이라는 단어로 게으르다. 공개, 보호, 불이행을 포함하고 반영을 언급하도록 답변을 업데이트했습니다. 내가하려고했던 요점은 일부 코드가 비밀이되어야 할 충분한 이유가 있지만 테스트하지 못하게해서는 안된다는 것입니다.
Richard Le Mesurier

2
모두에게 실제 사례를 보여 주셔서 감사합니다. 대부분의 대답은 좋지만 이론적 인 관점에서 볼 수 있습니다. 실제로는 계약에서 구현을 숨겨야하며 여전히 테스트해야합니다. 이 모든 것을 읽으면 아마도 이것이 다른 이름으로 완전히 새로운 수준의 테스트가되어야한다고 생각합니다
.

1
하나 더 예- 크롤러 HTML 파서 . 전체 html 페이지를 도메인 객체로 구문 분석하려면 구조의 작은 부분을 검증하는 매우 논리적 인 논리가 필요합니다. 메소드로 나누고 하나의 공개 메소드에서 호출하는 것이 합리적이지만, 모든 작은 메소드를 공개로 구성하거나 HTML 페이지 당 10 개의 클래스를 작성하는 것은 의미가 없습니다.
황천

59

개인용 메소드는 공용 메소드에 의해 호출되므로 공용 메소드에 대한 입력은 해당 공용 메소드에 의해 호출 된 개인용 메소드도 테스트해야합니다. 공용 메소드가 실패하면 개인 메소드에서 실패가 될 수 있습니다.


진실. 문제는 하나의 공개 메소드가 여러 개인 메소드를 호출하는 것처럼 구현이 더 복잡 할 때입니다. 어느 쪽이 실패 했습니까? 또는 복잡한 개인 방법 인 경우 새로운 클래스로 노출되거나 이전 할 수 없습니다
Z. Khullah

개인용 메소드를 테스트해야하는 이유를 이미 언급했습니다. 당신은 "그럴 수 있습니다"라고 말 했으므로 확실하지 않습니다. 방법을 테스트해야하는지 여부는 액세스 수준과 직교합니다.
웨이 키우

39

내가 사용한 또 다른 방법은 개인 또는 보호 된 패키지로 개인 메서드를 변경 한 다음 Google Guava 라이브러리 의 @VisibleForTesting 주석으로 보완하는 것입니다 .

이렇게하면이 방법을 사용하는 사람에게주의를 기울이고 패키지에서도 직접 액세스하지 않도록 지시합니다. 또한 테스트 클래스는 물리적 으로 동일한 패키지에있을 필요는 없지만 테스트 폴더 아래의 동일한 패키지에 있어야 합니다.

예를 들어, 테스트 할 메소드가있는 경우 src/main/java/mypackage/MyClass.java테스트 호출을에 배치해야합니다 src/test/java/mypackage/MyClassTest.java. 그렇게하면 테스트 클래스의 테스트 메소드에 액세스 할 수 있습니다.


1
나는 이것에 대해 몰랐다. 그것은 interresting이다. 나는 그런 종류의 주석이 필요하다면 디자인 문제가 있다고 생각한다.
Seb

2
나에게 이것은 소방 훈련을 테스트하기 위해 소장에게 일회성 열쇠를주는 대신 말하는 것과 같습니다. 우리 회사, 입력하지 마십시오 ".
Fr Jeremy Krieg

@FrJeremyKrieg-무슨 뜻인가요?
MasterJoe2

1
@ MasterJoe2 : 화재 테스트의 목적은 화재 위험에 대비하여 건물의 안전을 향상시키는 것입니다. 그러나 소장에게 건물에 대한 접근 권한을 부여해야합니다. 전면 도어를 영구적으로 잠금 해제 상태로두면 무단 액세스의 위험이 높아지고 안전 해집니다. VisibleForTesting 패턴도 마찬가지입니다. "화재"의 위험을 줄이기 위해 무단 액세스의 위험이 증가하고 있습니다. 영구적으로 잠금 해제 (비공개) 상태로두기보다는 필요할 때만 (예 : 리플렉션 사용) 테스트를 위해 일회용으로 액세스 권한을 부여하는 것이 좋습니다.
Jeremy Krieg

34

크고 기발한 클래스와 테스트 레거시 코드에, 종종 내가 쓰고있어 하나의 개인 (또는 공용) 메서드를 테스트 할 수있는 것이 많은 도움이 될 것입니다 지금을 .

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);

2
Source Forge 권장 프로젝트 PrivateAccessor가 아닌 전체 JUnit 애드온 ( sourceforge.net/projects/junit-addons ) 패키지 를 다운로드하십시오 .
skia.heliou

2
그리고 Class이러한 방법에서 첫 번째 매개 변수로 a 를 사용하는 경우 static멤버 에만 액세스해야합니다 .
skia.heliou

31

에서 스프링 프레임 워크 는이 방법을 사용하여 개인 방법을 테스트 할 수 있습니다 :

ReflectionTestUtils.invokeMethod()

예를 들면 다음과 같습니다.

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

Spring을 사용하는 경우에만 지금까지 가장 깨끗하고 간결한 솔루션.
Denis Nikolaenko

28

Java 용 리플렉션사용하여 Cem Catikkas의 솔루션을 사용해 본 결과 , 여기에 설명 된 것보다 더 우아한 솔루션이라고 말할 수 있습니다. 그러나 리플렉션 사용에 대한 대안을 찾고 있고 테스트중인 소스에 액세스 할 수있는 경우 여전히 옵션입니다.

특히와 클래스의 private 메소드, 테스트 가능한 장점이있다 테스트 주도 개발 당신이 어떤 코드를 작성하기 전에 작은 테스트를 설계하고 싶습니다.

비공개 멤버 및 메소드에 액세스하여 테스트를 작성하면 공개 메소드에만 액세스하여 구체적으로 타겟팅하기 어려운 코드 영역을 테스트 할 수 있습니다. 공용 메소드에 여러 단계가 포함 된 경우 여러 개인 메소드로 구성되어 개별적으로 테스트 될 수 있습니다.

장점 :

  • 더 세밀하게 테스트 할 수 있습니다

단점 :

  • 테스트 코드는 소스 코드와 동일한 파일에 있어야하므로 유지 관리하기가 더 어려울 수 있습니다
  • .class 출력 파일과 마찬가지로 소스 코드에 선언 된 것과 동일한 패키지 내에 있어야합니다.

그러나 지속적인 테스트에이 방법이 필요한 경우 개인 방법을 추출해야한다는 신호일 수 있습니다.이 방법은 전통적인 공개 방법으로 테스트 할 수 있습니다.

이것이 어떻게 작동하는지에 대한 복잡한 예입니다.

// 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.

참조 : Java 팁 106 : 재미와 이익을위한 정적 내부 클래스


26

다른 사람들이 말했듯이 개인적인 방법을 직접 테스트하지 마십시오. 다음은 몇 가지 생각입니다.

  1. 모든 방법을 작고 집중적으로 유지하십시오 (테스트하기 쉽고 잘못된 것을 쉽게 찾을 수 있음)
  2. 코드 적용 도구를 사용하십시오. 나는 Cobertura를 좋아한다 (오 행복한 날, 새로운 버전이 나온 것 같습니다!)

단위 테스트에서 코드 적용 범위를 실행하십시오. 방법이 완전히 테스트되지 않은 경우 테스트를 추가하여 적용 범위를 늘리십시오. 100 % 코드 적용 범위를 목표로하지만 달성하지 못할 수도 있음을 인식하십시오.


코드 적용 범위를 넓 힙니다. 개인 메소드에 어떤 종류의 논리가 있더라도 여전히 공개 메소드를 통해 해당 논리를 호출합니다. 코드 커버리지 도구는 테스트에 포함되는 부품을 보여 주므로 개인 분석법이 테스트되었는지 확인할 수 있습니다.
coolcfan

나는 유일한 공용 메소드가 main [] 인 클래스를 보았고 GUI를 팝업하고 데이터베이스와 전세계 몇 대의 웹 서버를 연결했습니다. 쉬운 "간접적으로 테스트하지 않는다"말을 ...
Audrius Meskauskas

26

비공개 메소드는 공개 메소드에 의해 소비됩니다. 그렇지 않으면 죽은 코드입니다. 그렇기 때문에 공개 메소드를 테스트하여 공개 메소드의 예상 결과와 그에 따라 사용되는 개인 메소드를 확인합니다.

공용 메소드에서 단위 테스트를 실행하기 전에 디버깅하여 개인 메소드 테스트를 테스트해야합니다.

또한 테스트 중심 개발을 사용하여 디버깅 할 수 있으며 모든 어설 션이 충족 될 때까지 단위 테스트를 디버깅합니다.

개인적으로 TDD를 사용하여 클래스를 작성하는 것이 좋습니다. 퍼블릭 메소드 스텁을 생성 한 다음 미리 정의 된 모든 어설 션을 사용 하여 단위 테스트를 생성 하므로 메소드의 예상 결과는 코딩 전에 결정됩니다. 이렇게하면 단위 테스트 어설 션이 결과에 적합하게 만드는 잘못된 경로를 밟지 않아도됩니다. 그러면 수업은 견고하며 모든 단위 테스트를 통과하면 요구 사항을 충족합니다.


2
이것은 사실이지만 매우 복잡한 테스트로 이어질 수 있습니다. 전체 그룹이 아닌 한 번에 단일 방법을 테스트하는 것이 더 좋습니다 (단위 테스트). 끔찍한 제안은 아니지만 여기에 더 나은 답변이 있다고 생각합니다. 이것이 최후의 수단이어야합니다.
Bill K

24

Spring을 사용하는 경우 ReflectionTestUtils 는 최소한의 노력으로 여기에 도움이되는 편리한 도구를 제공합니다. 예를 들어, 바람직하지 않은 공개 설정자를 추가하지 않고 개인 구성원에 모형을 설정하려면 다음을 수행하십시오.

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

24

당신이 꺼리거나 변경할 수없는 기존 코드를 테스트하려는 경우 리플렉션이 좋은 선택입니다.

클래스의 디자인이 여전히 유연하고 개별적으로 테스트하려는 복잡한 개인 메소드가있는 경우 별도의 클래스로 가져 와서 해당 클래스를 개별적으로 테스트하는 것이 좋습니다. 원래 클래스의 공용 인터페이스를 변경할 필요는 없습니다. 내부적으로 헬퍼 클래스의 인스턴스를 작성하고 헬퍼 메소드를 호출 할 수 있습니다.

헬퍼 메소드에서 발생하는 어려운 오류 조건을 테스트하려면 한 단계 더 나아가십시오. 도우미 클래스에서 인터페이스를 추출하고 공용 getter 및 setter를 원래 클래스에 추가하여 도우미 클래스 (인터페이스를 통해 사용됨)를 주입 한 다음 도우미 클래스의 모의 버전을 원래 클래스에 주입하여 원래 클래스의 방법을 테스트하십시오. 도우미의 예외에 응답합니다. 이 방법은 도우미 클래스를 테스트하지 않고 원래 클래스를 테스트하려는 경우에도 유용합니다.


19

내부 구현을 변경할 때마다 클라이언트 코드 (이 경우 테스트)가 중단되므로 개인 메서드를 테스트하면 클래스 캡슐화가 중단됩니다.

따라서 개인 메소드를 테스트하지 마십시오.


4
단위 테스트와 src 코드는 쌍입니다. src를 변경하면 단위 테스트를 변경해야 할 수도 있습니다. 이것이 junit 테스트의 의미입니다. 그들은 모든 것이 이전처럼 작동한다고 보증해야한다. 코드를 변경하면 문제가 발생해도 괜찮습니다.
AlexWien

1
코드가 변경되면 단위 테스트가 변경되어야한다는 데 동의하지 마십시오. 클래스의 기존 기능을 변경하지 않고 클래스에 기능을 추가하라는 명령을 받으면 코드를 변경 한 후에도 기존 단위 테스트를 통과해야합니다. Peter가 언급했듯이 단위 테스트는 내부에서 수행되는 방식이 아니라 인터페이스를 테스트해야합니다. 테스트 주도 개발에서 테스트는 코드가 작성되기 전에 생성되어 내부적으로 해결되는 방법이 아니라 클래스의 인터페이스에 중점을 둡니다.
Harald Coppoolse

16

JUnit.org FAQ 페이지 의 답변 :

그러나 당신이해야한다면 ...

JDK 1.3 이상을 사용하는 경우 PrivilegedAccessor 의 도움으로 리플렉션을 사용하여 액세스 제어 메커니즘을 파괴 할 수 있습니다 . 사용 방법에 대한 자세한 내용은 이 기사를 읽으십시오 .

JDK 1.6 이상을 사용하고 @Test로 테스트에 주석을 달 경우 Dp4j 를 사용 하여 테스트 방법에 리플렉션을 주입 할 수 있습니다 . 사용 방법에 대한 자세한 내용은 이 테스트 스크립트를 참조하십시오 .

PS 나는의 주요 기여자 해요 Dp4j , 문의 도움이 필요합니다. :)


16

코드를 변경할 수없는 레거시 애플리케이션의 프라이빗 메소드를 테스트하려는 경우 Java의 한 가지 옵션은 jMockit 이며, 이는 클래스 전용 인 경우에도 오브젝트에 대한 모의 객체를 작성할 수있게합니다.


4
jmockit 라이브러리의 일부로 Deencapsulation 클래스에 액세스하여 개인 메소드를 쉽게 테스트 할 수 있습니다. Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Domenic D.

나는 항상이 접근법을 사용합니다. 매우 도움이 됨
MedicineMan

14

나는 개인적인 방법을 테스트하지 않는 경향이 있습니다. 광기가 있습니다. 개인적으로 공개적으로 노출 된 인터페이스 만 테스트해야한다고 생각합니다 (보호 및 내부 방법 포함).


14

JUnit을 사용하는 경우 junit-addons를 살펴보십시오 . Java 보안 모델을 무시하고 개인용 메소드 및 속성에 액세스 할 수 있습니다.


13

코드를 약간 리팩토링하는 것이 좋습니다. 코드를 테스트하기 위해 리플렉션이나 다른 종류의 물건을 사용하는 것에 대해 생각해야 할 때 코드에 문제가 있습니다.

다른 유형의 문제를 언급했습니다. 개인 필드부터 시작하겠습니다. 개인 필드의 경우 새 생성자를 추가하고 필드를 주입했습니다. 이 대신에 :

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;
}

내가 쓴 대부분은 의존성 주입 패턴 인 것처럼 보입니다. 내 개인적인 경험에서는 테스트하는 동안 정말 유용하며 이러한 종류의 코드는 더 깨끗하고 유지 관리하기가 더 쉽다고 생각합니다. 중첩 클래스에 대해서도 마찬가지입니다. 중첩 클래스에 무거운 논리가 포함되어 있으면 패키지 개인 클래스로 이동하여 필요한 클래스에 주입하면 더 좋습니다.

레거시 코드를 리팩토링하고 유지 관리하는 데 사용했던 몇 가지 다른 디자인 패턴도 있지만 테스트 할 코드의 경우에 따라 다릅니다. 리플렉션을 사용하는 것은 문제가되지 않지만 모든 배포 전에 테스트를 많이 수행하고 테스트를 수행하는 엔터프라이즈 응용 프로그램을 사용하는 경우 모든 작업이 실제로 느려집니다 (성가 시며 그런 종류는 좋아하지 않습니다).

세터 주입도 있지만 사용하지 않는 것이 좋습니다. 생성자를 고수하고 실제로 필요할 때 모든 것을 초기화하여 필요한 종속성을 주입 할 가능성을 남겨 두는 것이 좋습니다.


1
ClassToTest(Callable)주사에 동의하지 않습니다 . 즉하게 ClassToTest더 복잡. 간단하게 유지하십시오. 또한이를 위해서는 다른 사람 이 사장 ClassToTestClassToTest되어야 할 것을 말해야 합니다 . 논리를 주입 할 수있는 곳이 있지만 그렇지 않습니다. 클래스를 유지 관리하기 어렵게 만들었습니다.
Loduwijk

또한 테스트 방법이 더 많은 문제를 일으킬 수있는 수준까지 복잡성을 X증가시키지 않기 X때문에 더 많은 문제를 일으킬 수있는 방식으로 구현 한 경우 추가 테스트가 필요합니다. (이것은 무한 루프가 아니며 각 반복은 이전보다 작지만 여전히 성가시다)
Loduwijk

2
왜 수업 ClassToTest을 유지하기 어려워 하는지 설명해 주 시겠습니까? 실제로 응용 프로그램을 유지 관리하기가 더 쉬워집니다. 필요한 모든 값 first과 '두 번째'변수에 대해 새로운 클래스를 만드는 것이 무엇 입니까?
GROX13

1
테스트 방법은 복잡성을 증가시키지 않습니다. 제대로 작성되지 않은 클래스이므로 테스트 할 수 없습니다.
GROX13

클래스가 더 복잡하기 때문에 유지 관리가 어렵습니다. 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 ) } }. 이와 같은 주사가 의미가있는 경우가 있지만 귀하의 사례를 "유지하기 더 쉽다"고 생각하지 않습니다
Loduwijk

12

개인 메소드는 동일한 클래스 내에서만 액세스 할 수 있습니다. 따라서 테스트 클래스에서 대상 클래스의 "비공개"메소드를 테스트 할 방법이 없습니다. 방법은 단위 테스트를 수동으로 수행하거나 분석법을 "비공개"에서 "보호"로 변경할 수 있다는 것입니다.

그런 다음 보호 된 메소드는 클래스가 정의 된 동일한 패키지 내에서만 액세스 할 수 있습니다. 따라서 대상 클래스의 보호 된 메소드를 테스트한다는 것은 대상 클래스와 동일한 패키지에서 테스트 클래스를 정의해야 함을 의미합니다.

위의 모든 사항이 요구 사항에 맞지 않으면 리플렉션 방법 을 사용하여 개인 방법에 액세스하십시오.


"보호 된"과 "친절한"을 혼합하고 있습니다. 보호 된 메소드는 대상 클래스 (예 : 서브 클래스)에 오브젝트를 지정할 수있는 클래스에서만 액세스 할 수 있습니다.
Sayo Oladeji

1
실제로 Java에는 "Friendly"가 없으며 "Package"라는 용어가 있으며 개인 / 공개 / 보호 된 수정 자 부족으로 표시됩니다. 이 답변을 방금 수정했지만 이미 이것을 말한 것이 좋습니다. 삭제하는 것이 좋습니다.
Bill K

나는 "이 대답은 명백한 잘못이다." 마지막 문장에 도달 할 때까지는 나머지 답변과 모순됩니다. 마지막 문장이 첫 번째 문장이어야합니다.
Loduwijk

11

위의 많은 사람들이 제안했듯이 좋은 방법은 공용 인터페이스를 통해 테스트하는 것입니다.

이 작업을 수행하는 경우 코드와 같은 코드 적용 도구 (예 : Emma)를 사용하여 개인 메서드가 실제로 테스트에서 실행되고 있는지 확인하는 것이 좋습니다.


간접적으로 테스트해서는 안됩니다! 커버리지를 통한 터치뿐만 아니라; 예상 결과가 전달되는지 테스트하십시오!
AlexWien

11

개인 필드를 테스트하는 일반적인 기능은 다음과 같습니다.

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);
}

10

예를 들어 아래를 참조하십시오.

다음 import 문을 추가해야합니다.

import org.powermock.reflect.Whitebox;

이제 private 메소드, 호출 할 메소드 이름 및 추가 매개 변수가있는 오브젝트를 아래와 같이 직접 전달할 수 있습니다.

Whitebox.invokeMethod(obj, "privateMethod", "param1");

10

오늘은 개인 메소드 및 필드 테스트를 돕기 위해 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는 캡슐화를 중단합니다. 리플렉션을 통해 모든 것에 액세스 할 수 있지만 컴파일 타임에 모든 것이 검사됩니다.

일부 레거시 코드를 테스트하는 데 이상적입니다. 조심해서 사용하십시오. ;)

https://github.com/stephanenicolas/boundbox


1
BoundBox는 간단하고 우아하며 솔루션입니다.
14:12에

9

먼저,이 질문을 던질 것입니다 : 왜 당신의 개인 회원들이 고립 된 테스트를 필요로합니까? 공공 장소와는 별도로 테스트가 필요한 복잡한 행동을 제공하는 것이 복잡합니까? '코드 라인'테스트가 아니라 단위 테스트입니다. 작은 물건을 땀 흘리지 마십시오.

이들이 개인 구성원이 복잡 할 정도로 큰 '단위'가 될만큼 충분히 크고 큰 경우-개인 구성원을이 클래스에서 리팩토링하는 것을 고려하십시오.

리팩토링이 부적절하거나 실행 불가능한 경우, 단위 테스트 중에 전략 패턴을 사용하여 이러한 개인 멤버 함수 / 멤버 클래스에 대한 액세스를 대체 할 수 있습니까? 단위 테스트에서 전략은 추가 검증을 제공하지만 릴리스 빌드에서는 간단한 통과입니다.


3
종종 공개 메소드의 특정 코드는 내부 개인 메소드로 리팩토링되며 실제로 잘못되었을 수있는 중요한 논리입니다. 당신은 대중 방법에서 독립적으로이를 테스트 할
oxbow_lakes

단위 테스트가없는 가장 짧은 코드조차도 올바르지 않습니다. 두 개의 지구력 학적 각도의 차이를 막으려 고 노력하십시오. 4 줄의 코드이며 대부분 시도 할 때 올바르게하지 않습니다. 이러한 메소드는 신뢰할 수있는 코드의 기반이되기 때문에 단위 테스트가 필요합니다. (이러한 유용한 코드도 공개 될 수 있습니다. 덜 유용한 보호
AlexWien

8

나는 최근 에이 문제가 있었고 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));

7

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