[Spring] 빈 생명주기( Bean Lifecycle, Scope ), AssertThat 기능
서버개발

[Spring] 빈 생명주기( Bean Lifecycle, Scope ), AssertThat 기능

빈 생명주기( Bean Lifecycle )

# 데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 에플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다

- 스프링 빈은 간단하게 다음과 같은 라이프사이클을 가진다

객체 생성 -> 의존관계 주입

- 스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료된다.
따라서 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 한다

 

# 스프링 빈의 이벤트 라이프사이클

스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 → 사용 → 소멸 전 콜백 → 스프링 종료

- 초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
- 소멸전 콜백 : 빈이 소멸되기 직전에 호출 

 

빈 생명주기 콜백 지원 3가지 방법

# 인터페이스

클래스명 implements InitializingBean, DisposableBean

 @Override
    public void afterPropertiesSet() throws Exception {
        connect();
        call("초기화 연결 메세지");
    }

 @Override
    public void destroy() throws Exception {

    }

- 단점 : 
스프링 전용 인터페이스에 의존적으로 설계해야한다
초기화 소멸 메서드의 이름을 변경할 수 없다
내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다

 

인터페이스를 사용하는 초기화, 종료 방법은 스프링 초창기에 나온 방법들이고 지금은 거의 사용하지 않는다.

 

# Bean 설정 정보에 초기화 메서드, 종료 메서드 지정

 @Bean(initMethod = "init", destroyMethod = "close")

- 해당하는 메서드를 클래스 내부에 넣으면 된다

- 장점 : 
메서드 이름 자유롭게 가능하다
스프링 빈이 스프링 코드에 전혀 의존하지 않는다
코드가 아니라 설정 정보를 사용하기 때문에 코드를 고칠 수 없는 외부 라이브러리에도 초기화, 종료 적용 가능하다

- 단점 : 
@Bean으로 등록하면 destroyMethod에 default값으로 infer_method라는게 있는데 (inferred)
외부 라이브러리 대부분 close나 shutdown을 종료 메서드로 사용
inferred은 추론으로 close나 shutdown이라는 함수가 있으면 추론해서 불러준다
추론기능이 사용하기 싫으면 ""로 등록해주어야 한다 ( blank )

 

# @PostConstruct @PreConstruct 어노테이션 지원

@PostConstruct
public void init(){
    connect();
    call("초기화 연결 메세지");
}

@PreDestroy
public void close(){
		
}

- 특징

javax에서 지원하는 어노테이션이기에 다른 패키지에서도 지원한다
매우 편리하며, 스프링에 종속적인 기술이 아니라 자바 표준이라 다른 컨테이너에서도 잘 동작한다
컴포넌트 스캔과도 어울리며, 유일한 단점으로는 외부 라이브러리에서는 적용하지 못한다는 것
외부 라이브러리를 초기화 종료해야하면 @Bean의 initMethod destroyMethod를 사용하면 된다
그래서 2번 기능(Bean 설정 정보를 통한 메서드 지정방식)도 알아야 한다!!

 

Assertion.assertThat 기능 비교 ( org.assertj )

# AssertJ는 많은 assertion(직역: 주장, 행사)을 제공하는 자바 라이브러리이다. 에러 메세지와 테스트 코드의 가독성을 매우 높여주고 각자 좋아하는 IDE에서 쓰기 굉장히 쉽다.

 

# JUnit5의 경우, assertEquals(expected, actual)과 같이 두 개의 인자를 받아서 비교를 하지만,

AssertJ는 메소드 체이닝을 통해 가독성을 높여주는 특징이 있습니다. assertEquals()는 왼쪽이 expected인지 actual인지 혼동될 여지가 있지만, assertThat()은 actual 인자 하나만 요구하고 그 뒤로 메소드 체이닝을 하므로 actual과 expected를 명확하게 구분지어준다는 장점이 있다

 

# Test Fail Message

- JUnit5의 경우, 마지막 인자값에 선택적으로 메시지를 넣어줌으로써 테스트 실패 메시지를 명시할 수 있는데, AssertJ에서는 as()를 호출하여 사용합니다. 단, assertion이 수행되기 전에 사용해야합니다.

assertThat(a).as("check %s's age", a).isEqualTo(b);

 

# Filtering assertions

- AssertJ를 사용하면 간단하게 테스트할 데이터들을 필터링할 수 있습니다.

class MemberTest {
    private Member dexter = new Member("dexter", 12, ADMIN);
    private Member james = new Member("james", 30, ADMIN);
    private Member park = new Member("park", 23, USER);
    private Member lee = new Member("lee", 33, USER);

    private List<Member> members = newArrayList(dexter, james, park, lee);

    @Test
    void sample() throws Exception {
        // 1 type 필드가 ADMIN인 멤버만 필터링
        assertThat(members)
                .filteredOn("type", ADMIN)
                .containsOnly(dexter, james);

        // 2 람다식을 활용하여 필터링이 가능합니다
        assertThat(members)
                .filteredOn(m -> m.getType() == USER)
                .containsOnly(park, lee);

        // 3 메서드를 통해 다양한 조건을 넣을 수 있습니다.
        assertThat(members).
                filteredOn("type", in(ADMIN, USER))
                .containsOnly(dexter, james, park, lee);

        // 4 메서드를 통해 다양한 조건을 넣을 수 있습니다.
        assertThat(members)
                .filteredOn("type", not(ADMIN))
                .containsOnly(park, lee);

        // 5 필터링은 연쇄적으로 진행할 수 있습니다.
        assertThat(members)
                .filteredOn("type", ADMIN)
                .filteredOn(m -> m.getAge() > 20)
                .containsOnly(james);
    }

}

 

# Collection Data Extracting

- List에서 멤버들의 필드를 테스트하려면?

// 기존 동작 방식
@Test
void no_extracting() throws Exception{
    List<String> names = new ArrayList<>();
    for (Member member : members) {
        names.add(member.getName());
    }

    assertThat(names).containsOnly("dexter", "james", "park", "lee");
}


@Test
void extracting() throws Exception{
	// 위와 동일하게 동작
    assertThat(members)
            .extracting("name")
            .containsOnly("dexter", "james", "park", "lee");
            
             // 여러 데이터의 경우 튜플로 동작
    assertThat(members)
            .extracting("name", "age")
            .contains(
                    tuple("dexter", 12),
                    tuple("james", 30),
                    tuple("park", 23),
                    tuple("lee", 33)
            );
}

 

# Soft Assertions

- 보통 테스트를 진행할 때 앞의 assertThat이 실패하면 해당 테스트는 중지된다

- Soft Assertions을 이용하면 우선 모든 assertThat을 실행하고 해당 실패 내역을 확인할 수 있다

@Test
void softAssertion() throws Exception{
    SoftAssertions softAssertions = new SoftAssertions();
    softAssertions.assertThat(dexter.getAge()).as("Dexter Age Test").isEqualTo(11);
    softAssertions.assertThat(james.getAge()).as("James Age Test").isEqualTo(31);
    softAssertions.assertThat(park.getAge()).as("Park Age Test").isEqualTo(24);
    softAssertions.assertThat(lee.getAge()).as("Lee Age Test").isEqualTo(32);

    softAssertions.assertAll();
}

 

# File Assertions

- 파일에 대한 테스트를 제공한다

- 해당 데이터가 파일인지, 존재하는 지, 등에 대한 테스트 진행 가능하고, 파일 내용을 읽어 테스트를 진행할 수 있다

@Test
void file() throws Exception{
    File file = writeFile("Temp", "You Know Nothing Jon Snow");

    // 1 해당 파일이 존재하는지, 파일이 맞는지, 상대 경로인지
    assertThat(file).exists().isFile().isRelative();

    // 2 해당 값의 시작, 끝 값과 포함하는 갓 등을 테스트
    assertThat(contentOf(file))
            .startsWith("You")
            .contains("Know Nothing")
            .endsWith("Jon Snow");
}

 

# Exception Assertions

- 예외를 테스트하는 방법이다.

// 1 assertThatThrownBy에 해당 예외가 발생하는 코드를 작성한다
// 예외 종류, 메시지, 스택 트레이스등에 대한 정보를 테스트할 수 있습니다.
@Test
void exception() throws Exception {
    assertThatThrownBy(() -> throwException())
            .isInstanceOf(Exception.class)
            .hasMessage("예외 던지기!")
            .hasStackTraceContaining("Exception");
}

// 2 catchThrowable에서 예외가 발생하는 코드를 작성하고 
// 해당 throwable을 위와 같은 방법으로 테스트할 수 있습니다.

@Test
void thrownTest() throws Exception{
    Throwable throwable = catchThrowable(() -> throwException());

    assertThat(throwable).isInstanceOf(Exception.class).hasMessage("예외 던지기!");
}

private void throwException() throws Exception {
    throw new Exception("예외 던지기!");
}

 

# Custom Comparision Assertions

- 따로 Comparison을 커스텀하여 테스트를 진행한다

@Test
void equalTest() throws Exception{
    Item item1 = new Item("item1");
    Item item2 = new Item("item1");

    // 테스트 실패
    // assertThat(item1).isEqualTo(item2);
    assertThat(item1).isNotEqualTo(item2);

    assertThat(item1)
            .usingComparator(new Comparator<Item>() {
                @Override
                public int compare(Item o1, Item o2) {
                    return o1.getName().compareTo(o2.getName());
                }
            })
            .isEqualTo(item2);

    assertThat(item1)
            .usingComparator((a, b) -> a.getName().compareTo(b.getName()))
            .isEqualTo(item2);
}

 

# Field Comparisons

- 위와 같이 Comparator를 커스텀할 수도 있지만, 기본적인 필드 비교는 지원해준다

@Test
void comparison() throws Exception{
    Item item1 = new Item("item1", 1000);
    Item item2 = new Item("item1", 1000);
    Item item3 = new Item("item1", 3000);

    // 1 해당 객체의 필드 값이 같은지 비교해준다
    assertThat(item1).isEqualToComparingFieldByField(item2);

    // price field가 달라 오류
    //assertThat(item1).isEqualToComparingFieldByField(item3) 

    // 2 주어진 필드만 같은지 비교
    assertThat(item1).isEqualToComparingOnlyGivenFields(item2, "name");
    assertThat(item1).isEqualToComparingOnlyGivenFields(item3, "name");

    // 3 주어진 필드만 제외하고 같은지 비교
    assertThat(item1).isEqualToIgnoringGivenFields(item2, "price");
    assertThat(item1).isEqualToIgnoringGivenFields(item3, "price");

    // 4 Null필드 값을 제외하고 같은지 비교
    Item item4 = new Item(null, 1000);
    assertThat(item1).isEqualToIgnoringNullFields(item4);
}

 

# isSameAs

- 단위 테스트에서 객체들의 주소값을 비교해주는 메서드

assertThat(a).isSameAs(b);
assertThat(a).isNotSameAs(c);

 

# isEqualTo

- isEqualTo는 대상의 내용자체를 비교한다.

String a = "monster";
String b = "monster";

assertThat(a).isEqualTo(b)

 

# isInstanceOf

- 비교값이 매쳐의 타입과 동일한지 여부를 체크한다. any와는 다르게 매쳐의 값은 비교값과 연관없는 경우에도

사용할 수 있다.

 

 

 

 

 

 

#스프링 프레임워크 #스프링 빈 스코프 #빈 lifecycle #빈 등록 방식 #Assertion