유미의 기록들

[최종 프로젝트] Reflection을 이용한 테스트 코드 본문

대외활동 기록/내일배움캠프

[최종 프로젝트] Reflection을 이용한 테스트 코드

지유미 2024. 11. 10. 23:02
728x90
반응형

💡  배경

@Getter
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class SaveLectureRequest {
    @NotBlank(message = "제목을 작성해 주세요")
    private String title;
    @NotBlank(message = "설명을 작성해 주세요")
    private String description;
    @NotBlank(message = "추천인을 작성해 주세요")
    private String recommend;
    @NotNull(message = "카테고리를 작성해 주세요")
    private Category category;
    @NotNull(message = "난이도를 작성해 주세요")
    private Level level;
    @NotNull(message = "가격을 작성해주세요")
    private BigDecimal price;
}

LectureService 테스트 코드를 작성하면서 `SaveLectureRequest`객체를 생성할 때, `@AllArgsConstructor`가 `AccessLevel.PROTECTED`로 설정되어 있어서 외부에서 직접 접근할 수 없었다. 테스트 코드를 위해 접근 제어자를  public으로 변경하거나 정적 팩토리 메소드를 추가한다면 코드의 복잡성을 증가시키고 유지보수를 어렵게 할 수 있어서 Java의 Reflection 기능을 사용하여 실제 비즈니스 로직을 수정하지 않고 테스트를 작성하려고 한다.

 

📝 Reflection

힙 영역에 로드된 Class 타입의 객체를 통해, 런타임에 접근 제어자 상관없이 원하는 클래스의 정보에 접근해서 수정할 수 있도록 지원하는 API

Java에서는 `java.lang.reflect` 패키지를 통해 Reflection을 사용할 수 있으며, 이를 통해 클래스나 객체의 구조에 대한 정보를 동적으로 가져오고 조작할 수 있다

 

조작할 수 있는 기능들

  • 클래스의 이름, 접근제어자, 부모 클래스, 구현 인터페이스  등 확인 가능 
  • 생성자, 메서드, 필드에 접근하고 호출 가능
  • 생성자를 이용하여 객체를 동적으로 생성하거나 메서드 호출 가능
  • 접근 제어자 관계 없이 필드나 메서드에 접근 가능

 

장점

  • 컴파일 시점에 클래스나 메서드에 대해 알지 못해도, 런타임에 해당 객체를 다룰 수 있어 동적 프로그래밍이 가능
  • 많은 프레임워크(Spring, Hibernate 등)가 리플렉션을 사용하여 객체의 의존성을 주입하거나 설정 정보를 동적으로 적용  
  • 접근 제어자가 private인 필드나 메서드에 접근해서 조작 가능

 

단점

  • 일반 메서드 호출보다 느리며, 많이 사용하면 성능에 영향을 줄 수 있음
  • 캡슐화가 깨질 위험성이 존재함
  • 컴파일 시점에 체크되지 않고 런타임에 발생하는 오류가 많아 예외가 발생할 가능성이 높음

 

프레임워크에서의 활용

  • 스프링 프레임워크 : 의존성 주입, AOP, 이벤트 처리 등이 리플렉션을 기반으로 동작한다 (`@Autowired`이 적용된 생성자를 찾아서 자동으로 의존성 주입)
  • 하이버네이트 : 리플렉션을 사용해 객체와 데이터베이스 테이블 간의 매핑을 동적으로 처리한다
  • JUnit : 리플렉션을 활용해서 테스트케이스를 동적으로 로드하고 실행한다 (`@Test`이 달린 메소드를 찾아내 테스트를 수행함)

Reflection은 강력한 기능을 제공하지만, 잘못 사용하면 프로그램의 안정성에 영향을 줄 수 있기 때문에 필요한 상황에서만 제한적으로 사용하고, 보안 측면을 항상 고려해서 사용해야 한다.

 

 

 

💻 코드 적용

생성자에 접근하여 객체 생성

 Constructor<SaveLectureRequest> constructor = SaveLectureRequest.class.getDeclaredConstructor(
        String.class, String.class, String.class, Category.class, Level.class, BigDecimal.class
);
constructor.setAccessible(true);

saveLectureRequest = constructor.newInstance(
        "테스트 강의",
        "설명",
        "추천인",
        Category.WEB_DEV,
        Level.EASY,
        new BigDecimal(100000)
);

 

생성자에 접근하기 위해서는 Class 객체를 통해 해당 생성자의 Constructor 객체를 얻어야 한다

`getDeclaredConstructor()` : 클래스에 선언된 모든 생성자에 접근할 때 사용

`setAccessible(true)` : 접근 제한을 해제

`newInstance()` : 객체를 생성

 

 

엔티티 필드 접근 및 수정

Field lectureField = Lecture.class.getDeclaredField("approval");
lectureField.setAccessible(true);
lectureField.set(lecture, Approval.APPROVED);

필드에 접근하기 위해서 Class 객체를 통해 해당 필드의 Field 객체를 얻어야 한다

`getDeclaredField()`를 통해 필드에 접근하여 Lecture엔티티의 approval필드를 `set()` 메소드를 사용하여 Approval.APPROVED로 상태를 변경함

728x90
반응형
Comments