programing

Reflection in Unit 테스트를 사용하는 것은 잘못된 관행입니까?

newsource 2022. 9. 28. 00:10

Reflection in Unit 테스트를 사용하는 것은 잘못된 관행입니까?

지난 몇 년 동안 저는 항상 Java에서 Reflection이 유닛 테스트 중에 널리 사용된다고 생각했습니다.확인해야 하는 변수/방법 중 일부는 비공개이므로 값을 읽을 필요가 있습니다.저는 항상 Reflection API를 이 용도로 사용하고 있다고 생각했습니다.

지난 주에 나는 몇 가지 패키지를 테스트해야 했고 그래서 몇 가지 JUnit 테스트를 작성해야 했다.항상 그렇듯이 Reflection을 사용하여 개인 필드 및 메서드에 액세스합니다.하지만 코드를 체크한 상사는 별로 만족하지 않았고, Reflection API는 그런 "해킹"을 위한 것이 아니라고 말했습니다.대신 그는 생산 코드의 가시성을 수정할 것을 제안했다.

Reflection을 사용하는 것은 정말 나쁜 관행입니까?정말 믿을 수가 없어

편집: 모든 테스트는 test라고 불리는 별도의 패키지에 포함되어 있어야 한다는 것을 설명해야 합니다(따라서 보호된 가시성을 사용하는 것도 가능하지 않았습니다).

IMHO Reflection은 레거시 코드 또는 변경할 수 없는 API를 테스트하는 특별한 경우를 위해 예약된 마지막 수단이어야 합니다.자체 코드를 테스트하는 경우 Reflection을 사용해야 한다는 것은 설계를 테스트할 수 없음을 의미하므로 Reflection을 선택하는 대신 설계를 수정해야 합니다.

유닛 테스트에서 개인 멤버에 액세스해야 하는 경우 일반적으로 해당 클래스에 부적절한 인터페이스가 있거나 너무 많은 작업을 수행하려고 합니다.따라서 인터페이스를 수정하거나 일부 코드를 다른 클래스로 추출하여 문제가 있는 메서드/필드 액세스 장치를 공개해야 합니다.

Reflection을 일반적으로 사용하면 코드를 이해하고 유지하기 어려울 뿐만 아니라 더 취약해집니다.일반적인 경우 컴파일러에 의해 검출될 수 있는 일련의 오류가 있지만 Reflection에서는 실행 시 예외로만 나타납니다.

업데이트: @tackline이 지적한 바와 같이 이 문제는 테스트 프레임워크의 내부가 아닌 자신의 테스트 코드 내에서 Reflection만 사용하는 것입니다.JUnit(및 다른 모든 유사한 프레임워크)는 리플렉션(reflection)을 사용하여 테스트 방법을 식별하고 호출합니다.이것은 리플렉션의 정당하고 현지화된 사용입니다.Reflection을 사용하지 않고 동일한 기능과 편의성을 제공하는 것은 어렵거나 불가능합니다.OTOH는 프레임워크 구현 내에 완전히 캡슐화되어 있기 때문에 자체 테스트 코드가 복잡해지거나 손상되는 일은 없습니다.

테스트만을 위해 프로덕션 API의 가시성을 수정하는 것은 매우 좋지 않습니다.가시성은 타당한 이유로 현재 값으로 설정될 가능성이 높으며 변경되지 않습니다.

유닛 테스트에 반사를 사용하는 것은 대부분 괜찮습니다.물론, 반성이 덜 필요하도록 시험성을 위해 클래스를 설계해야 합니다.

예를 들어 봄에는ReflectionTestUtils하지만 그 목적은 의존성을 스프링이 주입하기로 되어 있는 곳에 두는 것입니다.

이 토픽은 「실행과 행하지 않는다」보다 심도 있는 것으로, 테스트 대상 오브젝트의 내부 상태를 테스트할 필요가 있는지 어떤지, 테스트 대상 클래스의 설계에 의문을 제기할 수 있는지 등에 대해 설명합니다.

TDD - 테스트 중심 설계 - 이는 잘못된 관행입니다.이 TDD에 태그를 붙이지 않았거나 구체적으로 묻지 않은 것은 알고 있습니다만, TDD는 좋은 프랙티스이며, 이것은 TDD의 성질에 어긋납니다.

TDD에서는 클래스 인터페이스인 퍼블릭인터페이스를 정의하기 위해 테스트를 사용합니다.따라서 퍼블릭인터페이스와 직접 상호작용하는 테스트를 작성합니다.적절한 액세스 레벨은 설계의 중요한 부분이며, 좋은 코드의 중요한 부분입니다.사적인 것을 테스트해야 할 필요가 있는 경우는, 제 경험상, 보통 디자인상의 냄새입니다.

나쁜 관행이라고 생각하겠지만, 생산 코드의 가시성을 바꾸는 것은 좋은 해결책이 아닙니다. 원인을 살펴봐야 합니다.테스트 불가능한 API를 가지고 있기 때문에(즉, API가 테스트를 위해 충분한 정보를 제공하지 않기 때문에) 프라이빗 스테이트를 테스트하는 것을 검토하고 있거나, 테스트가 구현과 너무 결합되어 있기 때문에 리팩터링 시에만 사용할 수 있습니다.

당신의 사례를 더 잘 알지 못하면 더 이상 말할 수 없지만, 성찰하는 것은 정말 좋지 않은 관행으로 여겨집니다.개인적으로 저는 리플렉션에 의존하기보다는 테스트 대상 클래스의 정적 내부 클래스로 테스트를 하고 싶습니다(API의 테스트 불가능한 부분이 제 통제 하에 있지 않다고 한다면). 그러나 테스트 코드가 리플렉션 사용보다 프로덕션 코드와 동일한 패키지에 포함되어 있는 것이 더 큰 문제가 될 수 있습니다.

편집: 편집에 대한 응답으로, 적어도 Reflection을 사용하는 것만큼 좋지 않은 방법일 수 있습니다.일반적으로 같은 패키지를 사용하지만 테스트는 별도의 디렉토리 구조에 보관합니다.유닛 테스트가 테스트 대상 클래스와 동일한 패키지에 속하지 않는 경우, 무엇이 속하는지 알 수 없습니다.

어쨌든, 다음과 같은 서브 클래스를 테스트하는 것으로, 보호되고 있는 것(불행하게도, 이것에 매우 이상적인 패키지 전용은 아닙니다)을 사용하는 것으로, 이 문제를 회피할 수 있습니다.

 public class ClassUnderTest {
      protect void methodExposedForTesting() {}
 }

그리고 당신의 유닛 테스트 안에서

class ClassUnderTestTestable extends ClassUnderTest {
     @Override public void methodExposedForTesting() { super.methodExposedForTesting() }
}

보호된 컨스트럭터가 있는 경우:

ClassUnderTest test = new ClassUnderTest(){};

통상적인 상황에서는 위의 사항을 반드시 권장하는 것은 아니지만, 이미 '베스트 프랙티스'가 아닌 '베스트 프랙티스'에 따라 작업하도록 요구되는 제약 사항입니다.

Bozho에 대해서는 찬성합니다.

테스트만을 위해 프로덕션 API의 가시성을 수정하는 것은 매우 좋지 않습니다.

그러나 가능한 한 간단한 작업을 시도한다면 최소한 코드가 아직 새롭거나 변경될 때 Reflection API 보일러 플레이트를 작성하는 것이 더 나을 수 있습니다.즉, 메서드 이름 또는 파라미터를 변경할 때마다 테스트에서 반영되는 콜을 수동으로 변경해야 하는 부담은 IMHO에 너무 큰 부담과 잘못된 포커스입니다.

단지 테스트를 위해 접근성을 완화하는 함정에 빠져 다른 프로덕션 코드에서 was-private 메서드에 무심코 접근한 후 dp4j.jar를 생각했다.그것은 Reflection API 코드(Lombok 스타일)를 삽입하여 사용자가 직접 Reflection API를 작성하지 않도록 합니다.dp4j는 직접 접근을 대체하는 것입니다.컴파일 시 동등한 반사 API를 사용하여 단위 테스트를 수행합니다.다음으로 JUnit을 사용한dp4j의 예를 나타냅니다.

당신의 코드는 두 가지 방법으로 테스트되어야 한다고 생각합니다.유닛 테스트를 통해 공개 방법을 테스트해야 하며, 이는 당사의 블랙박스 테스트 역할을 합니다.코드가 관리 가능한 기능(좋은 디자인)으로 분할되어 있기 때문에, 각각의 피스를 리플렉션으로 테스트해, 프로세스로부터 독립적으로 동작하는 것을 확인하고 싶다고 생각합니다.개인적이기 때문에 리플렉션으로 테스트하는 방법밖에 없습니다.

적어도 단위 테스트 과정에서의 제 생각은 이렇습니다.

다른 사람의 말에 덧붙이려면 다음 사항을 고려하십시오.

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

여기서는 리플렉션을 사용하여 개인 메서드 SomeApplicationClass.computeSquare()를 실행하여 Integer를 전달하고 String 결과를 반환합니다.그 결과 JUnit 테스트는 정상적으로 컴파일되지만 다음 중 하나가 발생할 경우 실행 중 실패합니다.

  • 메서드 이름 "computeSquare"의 이름이 변경되었습니다.
  • 메서드는 다른 매개변수 유형을 사용합니다(예: Integer를 Long으로 변경).
  • 파라미터의 수가 변화한다(예: 다른 파라미터로 전달)
  • 메서드의 반환 유형이 변경됩니다(String에서 Integer로 변경되었을 수 있습니다).

대신 computeSquare가 퍼블릭 API를 통해 해야 할 일을 수행했음을 증명할 수 있는 쉬운 방법이 없다면, 여러분의 클래스는 많은 것을 수행하려고 할 것입니다.이 메서드를 새 클래스로 가져와 다음 테스트를 수행합니다.

public void testSquare()
{
   String result = new NumberUtils().computeSquare(new Integer(3));
   assertEquals("9", result);
}

(특히 최신 IDE에서 사용할 수 있는 리팩터링 툴을 사용하는 경우) 메서드의 이름을 변경해도 테스트에 영향을 주지 않습니다(IDE도 JUnit 테스트를 리팩터링하기 때문에). 한편 파라미터 타입을 변경해도 메서드의 파라미터 수 또는 반환 타입은 Junit 테스트에서 컴파일 에러 플래그가 표시됩니다.JUnit 테스트에서 컴파일되지만 실행 시 실패합니다.

마지막 요점은 특히 레거시 코드로 작업할 때 새로운 기능을 추가해야 하는 경우가 있으며 테스트 가능한 적절한 쓰기 클래스에서 이를 수행하는 것이 쉽지 않을 수 있다는 것입니다.이 경우 JUnit 테스트코드에서 직접 실행할 수 있는 보호된 가시성 메서드에 대한 새로운 코드 변경을 분리할 것을 권장합니다.이를 통해 테스트 코드의 기본 구성을 시작할 수 있습니다.최종적으로 클래스를 리팩터링하고 추가된 기능을 추출해야 하지만, 그 사이에 새로운 코드에 대한 가시성을 보호하는 것이 주요 리팩터링 없이 테스트 가능성의 측면에서 최선의 방법이 될 수 있습니다.

언급URL : https://stackoverflow.com/questions/2811141/is-it-bad-practice-to-use-reflection-in-unit-testing