단위 테스트에서 Reflection을 사용하는 것이 나쁜 습관입니까? [복제]
이 질문에 이미 답변이 있습니다.
지난 몇 년 동안 저는 Java에서 Reflection이 Unit 테스트 중에 널리 사용된다고 항상 생각했습니다. 확인해야하는 일부 변수 / 메소드는 비공개이기 때문에 어떻게 든 그 값을 읽어야합니다. 저는 항상 리플렉션 API가이 목적으로 사용된다고 생각했습니다.
지난주에 몇 가지 패키지를 테스트해야했기 때문에 JUnit 테스트를 작성해야했습니다. 언제나처럼 리플렉션을 사용하여 개인 필드와 메서드에 액세스했습니다. 하지만 코드를 확인한 내 상사는 그것에별로 만족하지 않았고 Reflection API가 그러한 "해킹"에 사용하기위한 것이 아니라고 말했습니다. 대신 그는 프로덕션 코드에서 가시성을 수정하도록 제안했습니다.
Reflection을 사용하는 것이 정말 나쁜 습관입니까? 정말 믿을 수가 없어-
편집 : 나는 모든 테스트가 테스트라는 별도의 패키지에 있어야한다고 언급해야했습니다 (따라서 보호 된 가시성을 사용하는 것은 가능한 해결책이 아닙니다)
IMHO Reflection은 레거시 코드 또는 변경할 수없는 API의 특별한 경우를 위해 예약 된 마지막 수단 일뿐입니다. 자체 코드를 테스트하는 경우 Reflection을 사용해야한다는 사실은 디자인을 테스트 할 수 없음을 의미하므로 Reflection에 의존하는 대신 수정해야합니다.
단위 테스트에서 비공개 멤버에 액세스해야하는 경우 일반적으로 해당 클래스에 부적절한 인터페이스가 있거나 너무 많은 작업을 시도한다는 의미입니다. 따라서 인터페이스를 수정하거나 일부 코드를 별도의 클래스로 추출해야합니다. 여기서 문제가있는 메서드 / 필드 접근자를 공개 할 수 있습니다.
일반적으로 Reflection을 사용하면 코드를 이해하고 유지하기가 더 어렵지만 더 취약한 코드가 생성됩니다. 일반적인 경우 컴파일러에 의해 감지되는 전체 오류 집합이 있지만 Reflection을 사용하면 런타임 예외로만 생성됩니다.
업데이트 : @tackline이 언급했듯이 이것은 테스트 프레임 워크의 내부가 아닌 자체 테스트 코드 내에서 Reflection을 사용하는 것과 관련이 있습니다. JUnit (및 아마도 다른 모든 유사한 프레임 워크)은 리플렉션을 사용하여 테스트 메서드를 식별하고 호출합니다. 이것은 리플렉션의 정당화되고 지역화 된 사용입니다. Reflection을 사용하지 않고는 동일한 기능과 편의성을 제공하는 것이 어렵거나 불가능합니다. OTOH는 프레임 워크 구현 내에서 완전히 캡슐화되므로 자체 테스트 코드를 복잡하게하거나 손상시키지 않습니다.
테스트 목적으로 프로덕션 API의 가시성을 수정하는 것은 정말 나쁜 일입니다 . 해당 가시성은 유효한 이유로 현재 값으로 설정 될 수 있으며 변경되지 않습니다.
단위 테스트에 리플렉션을 사용하는 것은 대부분 괜찮습니다. 물론 테스트 가능성을 위해 클래스를 디자인 해야 반영이 덜 필요합니다.
예를 들어 Spring에는 ReflectionTestUtils
. 그러나 그 목적은 스프링이 그것들을 주입하기로되어있는 의존성 모의를 설정하는 것입니다.
주제는 깊은 "수행 및하지"보다, 그리고 관련 무엇을 테스트해야합니다 - 객체의 요구의 내부 상태를 테스트 여부를할지 여부; 테스트중인 클래스의 디자인에 대해 질문 할 여력이 있는지 여부 기타
TDD (Test Driven Design) 의 관점에서 이것은 나쁜 습관입니다. 나는 당신이이 TDD에 태그를 붙이지 않았고 그것에 대해 구체적으로 묻지 않았다는 것을 알고 있지만, TDD는 좋은 습관이며 이것은 그 의미에 어긋납니다.
TDD에서는 테스트를 사용하여 클래스의 인터페이스 ( 공용 인터페이스) 를 정의 하므로 공개 인터페이스와 만 직접 상호 작용하는 테스트를 작성하고 있습니다. 우리는 그 인터페이스에 관심이 있습니다. 적절한 액세스 수준은 좋은 코드의 중요한 부분 인 디자인의 중요한 부분입니다. 개인적으로 무언가를 테스트해야 할 필요가 있다면, 그것은 보통 제 경험상 디자인 냄새입니다.
나는 그것을 나쁜 관행이라고 생각하지만 프로덕션 코드에서 가시성을 변경하는 것은 좋은 해결책이 아니므로 원인을 살펴보아야합니다. 테스트 할 수없는 API (즉, API가 테스트가 처리 할 수있을만큼 충분히 노출되지 않음)가있어 대신 비공개 상태를 테스트하려고하거나 테스트가 구현과 너무 결합되어 리팩토링 할 때 약간만 사용합니다.
귀하의 사례에 대해 더 많이 알지 못하면 더 이상 말할 수는 없지만 실제로 반사 를 사용하는 것은 좋지 않은 관행으로 간주됩니다 . 개인적으로 나는 리플렉션 (API의 테스트 할 수없는 부분이 내 통제하에 있지 않다고 말하면)보다는 테스트중인 클래스의 정적 내부 클래스로 테스트를 만들고 싶지만, 일부 장소에서는 테스트 코드에 더 큰 문제가있을 것입니다. 리플렉션을 사용하는 것보다 프로덕션 코드와 동일한 패키지입니다.
편집 : 당신의 편집에 대한 응답으로, 그것은 적어도 Reflection을 사용하는 것만큼이나 나쁜 습관입니다. 일반적으로 처리되는 방법은 동일한 패키지를 사용하지만 테스트를 별도의 디렉토리 구조에 보관하는 것입니다. 단위 테스트가 테스트중인 클래스와 동일한 패키지에 속하지 않으면 무엇을하는지 모르겠습니다.
어쨌든 다음과 같은 하위 클래스를 테스트하여 protected (안타깝게도 실제로 이상적인 package-private가 아님)를 사용하여이 문제를 해결할 수 있습니다.
public class ClassUnderTest {
protect void methodExposedForTesting() {}
}
그리고 단위 테스트 내부
class ClassUnderTestTestable extends ClassUnderTest {
@Override public void methodExposedForTesting() { super.methodExposedForTesting() }
}
보호 된 생성자가있는 경우 :
ClassUnderTest test = new ClassUnderTest(){};
나는 정상적인 상황에 대해 위의 내용을 반드시 권장하지는 않지만 이미 "모범 사례"가 아닌 작업을 요청받은 제한 사항입니다.
나는 Bozho의 생각을 두 번째로 생각합니다.
테스트 목적으로 프로덕션 API의 가시성을 수정하는 것은 정말 나쁜 일입니다.
But if you try to do the simplest thing that could possibly work then it might be preferred to writing Reflection API boilerplate, at least while your code is still new/changing. I mean, the burden of manually changing the reflected calls from your test every time you change the method name, or params, is IMHO too much burden and the wrong focus.
Having fallen in the trap of relaxing accessibility just for testing and then inadvertently accessing the was-private method from other production code i thought of dp4j.jar: it injects the Reflection API code (Lombok-style) so that you don't change the production code AND don't write the Reflection API yourself; dp4j replaces your direct access in the unit test with the equivalent reflection API at compile-time. Here is an example of dp4j with JUnit.
I think your code should be tested in two ways. You should test your public methods via Unit Test and that will act as our black box testing. Since your code is broken apart into manageable functions(good design), you'll want to unit test the individual pieces with reflection to make sure they work independent of the process, the only way I can think of doing this would be with reflection since they are private.
At least, this is my thinking in the unit test process.
To add to what others have said, consider the following:
//in your JUnit code...
public void testSquare()
{
Class classToTest = SomeApplicationClass.class;
Method privateMethod = classToTest.getMethod("computeSquare", new Class[]{Integer.class});
String result = (String)privateMethod.invoke(new Integer(3));
assertEquals("9", result);
}
Here, we are using reflection to execute the private method SomeApplicationClass.computeSquare(), passing in an Integer and returning a String result. This results in a JUnit test which will compile fine but fail during execution if any of the following occur:
- Method name "computeSquare" is renamed
- The method takes in a different parameter type (e.g. changing Integer to Long)
- The number of parameters change (e.g. passing in another parameter)
- The return type of the method changes (perhaps from String to Integer)
Instead, if there is no easy way to prove that the computeSquare has done what it is supposed to do through the public API, then your class is likely trying to do to much. Pull this method into a new class which gives you the following test:
public void testSquare()
{
String result = new NumberUtils().computeSquare(new Integer(3));
assertEquals("9", result);
}
Now (especially when you use the refactoring tools available in modern IDE's), changing the name of the method has no effect on your test (as your IDE will also have refactored the JUnit test as well), while changing the parameter type, number of parameters or return type of the method will flag a compile error in your Junit test meaning you won't be checking in a JUnit test which compiles but fails at runtime.
My final point is that sometimes, especially when working in legacy code, and you need to add new functionality, it may not be easy to do so in a separate testable well written class. In this instance my recommendation would be to isolate new code changes to protected visibility methods which you can execute directly in your JUnit test code. This allows you to begin building up a base of test code. Eventually you should refactor the class and extract your added functionality, but in the meantime, protected visibility on your new code can sometimes be your best way forward in terms of testability without major refactoring.
참고URL : https://stackoverflow.com/questions/2811141/is-it-bad-practice-to-use-reflection-in-unit-testing
'program story' 카테고리의 다른 글
모든 하위 디렉토리에서 특정 확장자를 가진 모든 파일 복사 (0) | 2020.08.30 |
---|---|
Java에서 작은 따옴표와 큰 따옴표 사이에 차이가 있습니까? (0) | 2020.08.30 |
비동기 람다를 사용한 병렬 foreach (0) | 2020.08.30 |
FileList에서 파일을 제거하는 방법 (0) | 2020.08.30 |
파일이 아닌 문자열로 CSV 형식으로 데이터를 쓰려면 어떻게해야합니까? (0) | 2020.08.30 |