skarltjr / Memory_Write_Record

나의 모든 학습 기록
0 stars 0 forks source link

단위테스트를 위한 ReflectionTestUtils의 동작방식을 알아보자 #96

Open skarltjr opened 2 years ago

skarltjr commented 2 years ago

개요 :

엔티티의 id는 @GeneratedValue(strategy = GenerationType.IDENTITY)를 설정해둬서
객체가 저장되는 시점에 생성되며 추가로 객체에 setter를 막아 불변을 도모한 상태 
그런데 서비스 단위테스트를 작성하던도중 해당 Id를 설정해줘야하는 경우가 생겼다.

방법:

ReflectionTestUtils를 사용해봤다.

동작방식 :

추측 : 자바의 리플렉션 활용하여 동작 그러나 정확히 어떻게???

-------

자바 리플렉션이 도대체 어떻게 해당 클래스의 매서드와 필드를 알아내는지 찾아봤다.
Class가 제공하는 매서드를 찾아봤고 ReflectionTestUtils이 어떤식으로 특정 클래스의 정보를 뽑아와서 수정하는지 대략적인 느낌을 알 수 있었다.

1. 
Class targetClass = Class.forName("myClass");

2. 
// 해당 클래스의 필드를 뽑아올 수 있다.
Field[] fields = targetClass.getFields();

3. 
// 해당 클래스의 매서드를 가져올 수 있다
Method[] methods = targetClass.getFields();

4. 
// 해당 클래스의 구조체도 가져올 수 있다
Constructor[] cons = targetClass.getConstructors();

이게 가능한 이유는 자바는 정적언어로 컴파일 시점에 타입이 결정되며, jvm은 소스코드를 바이트코드로 변환하고 클래스로더
를 통해 jvm메모리에 이런 정보들을 저장하고있기 때문이다. 그래서 위처럼 가져올 수 있다고 생각한다
skarltjr commented 2 years ago

진짜로 뽑아오는지 확인해보기

  1. @Test
    @DisplayName("ReflectionTestUtils가 어떻게 리플랙션을 활용하여 특정 클래스의 정보를 뽑아오는가 확인")
    void testReflection() throws ClassNotFoundException {
        Class<?> targetClass = Class.forName("com.msa.domain.Product");
        Field[] declaredFields = targetClass.getDeclaredFields();
        Field[] fields = targetClass.getFields();
        Method[] methods = targetClass.getMethods();
    
        System.out.println("fields입니다");
        for (Field field : fields) {
            System.out.println(field.getName());
        }
    
        System.out.println(" ----- ");
        System.out.println("methods입니다");
        for (Method method : methods) {
            System.out.println(method.getName());
        }
    }
  2. 여기서 fields와 declaredFields를 볼 수 있는데
    처음에 targetClass.getFields()로 뽑아와서 확인했더니 아무것도 나오지않았고 분명 접근제어자와 관계가 있지않을까생각했다
    그래서 다른 매서드인 targetClass.getDeclaredFields()를 활용해서 뽑아왔더니 private 필드도 다 가져올 수 있었다.
  3. 
    @Entity
    @Getter
    @Table(name = "products")
    @EntityListeners(AuditingEntityListener.class)
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "product_id")
    private Long id;
    
    @Embedded
    @Column(nullable = false)
    private ProductInfo productInfo;
    
    @Column(name = "created_at")
    @CreatedDate
    private LocalDateTime createdAt;
    
    @Column(name = "updated_at")
    @LastModifiedDate
    private LocalDateTime updatedAt;
    
    @Version
    @Column(name = "product_version")
    private int version=0;
<img width="1149" alt="스크린샷 2022-04-11 오후 10 20 51" src="https://user-images.githubusercontent.com/62214428/162748439-a87087c6-c1eb-488e-b748-e11b8d3cb8e3.png">

4.

setAccessible을 통해 privateField에 접근하여 값을 변경할 수 있다고한다. 다만 아직까지 모르겠는게 결국 ReflectionTestUtils처럼 동작하기위해선 생성된 객체. 즉 특정 인스턴스를 찾아와서 값을 바꿔줘야한다고 생각하는데 어떻게 찾는지 나중에 더 알아봐야겠다