Junit5 테스트 동작 방식과 빈 주입 본문

우아한테크코스 4기

Junit5 테스트 동작 방식과 빈 주입

giron 2022. 5. 10. 01:00
728x90
@JdbcTest
@Sql("/schema.sql")
class RoomDaoTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    private RoomDao roomDao = new RoomDaoImpl(jdbcTemplate);
}


위 코드는 현재 NPE가 발생합니다.
반면에 아래 코드처럼 텍스트픽쳐스를 이용한 테스트는 정상 동작합니다.

@Sql("/schema.sql")
@JdbcTest
class PieceDaoTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    private PieceDao pieceDao;

    @BeforeEach
    void setUp(){
        pieceDao = new PieceDaoImpl(jdbcTemplate);
    }
}


JdbcTemplate은 빈으로 등록되어서 싱글톤으로 만들어져 있습니다.
반면에 Junit 테스트는 각 테스트 메서드마다 TestClass의 인스턴스를 만들고 각 인스턴스가 생성된 후 테스트가 실행한다고 합니다. 

그렇기때문에 빈 객체에 대해서 스프링 IoC로 인해 개발자가 직접 인스턴스를 생성하지 않고 스프링이 관리해주는데, Junit 테스트 동작 방식이 각 테스트 메서드마다 필드에 선언된 객체에 대해서 직접 인스턴스를 생성하기 때문에 빈으로 관리되지 않아서, 빈 의존성 주입받는 게 실패해서 발생한 것이라고 추측을 한다.


위 부분에 대해서 궁금해서 토미에게 질문을 드렸다!
 
토미가 아래와 같이 제시를 해주셨다.
 
조금 더 단순화된 코드를 바탕으로 이야기해보려고 해요.
class Sample {
    private JdbcTemplate jdbcTemplate;

    private RoomDao roomDao = new RoomDaoImpl(jdbcTemplate);  // 1번

    public Sample() { }  // 2번

    public setJdbcTemplate(JdbcTemplate jdbcTemplate) {  // 3번
        this.jdbcTemplate = jdbcTemplate;
    }
}

class SampleApplication {
    public static void main(String args) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();

        Sample sample = new Sample();
        sample.setJdbcTemplate(jdbcTemplate);
    }
}
이런 코드가 있다고 할 때, Sample 클래스에 있는 1~3번 코드의 실행 순서가 어떻게 될까요?위 내용을 먼저 생각해보신 후, 아래 코드를 다시 한번 보면 좋겠네요.

1,2,3번 순서대로 실행이 되었다. -> 모든 필드가 초기화 되고 그 후 생성자가 불러오는 것 같다

@JdbcTest
@Sql("/schema.sql")
class RoomDaoTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    private RoomDao roomDao = new RoomDaoImpl(jdbcTemplate);
jdbcTemplate 은 field injection 을 이용해서 의존성을 주입받고 있고, roomDao 는 RoomDaoTest 객체가 만들어지는 시점에 생성될 것 같네요.위 2개 내용을 이해하신 후, @BeforeEach 사용을 하지 않는 방법을 생각해보면 좋을 것 같아요.
jdbcTemplate 을 먼저 주입받고, 그 후에 RoomDao 를 생성하려면 어떻게 하면 좋을지 고민해볼 수 있겠죠.
jdbcTemplate 이 RoomDaoTest 에서 필요한 것인지, RoomDao 를 만들기 위해서만 필요한 것인지도 고민해볼 수 있을 것 같네요. 

참, 아래 내용에 대해서는 스프링의 의존성 주입 문제는 아니라고 생각됩니다.
그렇기때문에 bean 객체에 대해서 스프링 IoC로 인해 개발자가 직접 인스턴스를 생성하지 않고 스프링이 관리 해주는데, Junit 테스트 동작 방식이 각 테스트 메서드마다 필드에 선언된 객체에 대해서 직접 인스턴스를 생성하기 때문에 빈으로 관리 되지 않아서, 빈 의존성 주입 받는게 실패해서 발생한 것이라고 봐도 될까요?
왜냐면, PieceDaoTest 에서도 @Autowired 를 통해 jdbcTemplate 을 주입받고 있는데, 잘 동작하기 때문이죠.

   @Autowired
    private JdbcTemplate jdbcTemplate;

    private final RoomDao roomDao;

    public RoomDaoTest(){
        roomDao = new RoomDaoImpl(jdbcTemplate);
    }
이렇게 했는데도 jdbcTemplate보다 먼저 불러오더라고요. 정상 동작되지 않았고, 필드 주입이 (빈은 먼저 생성되지만) 모든 필드가 생성되고 생성자를 통해 객체까지 생성된 후, 맨 마지막에 주입이 되는 것 같다고 생각했습니다.
그래서
private JdbcTemplate jdbcTemplate;

    private final RoomDao roomDao;

    public RoomDaoTest(DataSource dataSource){
        jdbcTemplate = new JdbcTemplate(dataSource);
        roomDao = new RoomDaoImpl(jdbcTemplate);
    }

위와 같이 생성자에서 받도록 실행을 했는데 생성자를 통해 주입하는 이 방식은 Junit5부터 안된다고 한다..

Jupiter가 ParameterResolver를 못찾는다.

키워드는 Junit5 생성자 주입 에러라고 치면 나올것이다.

그런데 위 코드에서 생성자에 @Autowired만 붙이면 정상 동작이 되었다.

아무래도 Junit에서는 Jupiter가 빈들을 관리한다고 한다. 그래서 @Autowired를 명시적으로 적지 않으면 해당 스프링 빈을 찾지 못하므로 @Autowired를 적어서 스프링에게 해당 빈을 찾으라고 알려줘야 작동하는 것으로 보인다.
 
 
객체가 완전히 생성된 이후에 빈이 주입이 되므로 필드에서 인스턴스 생성으로는 주입이 불가능하다!!
 
토미가 추가 키워드도 남겨주셨다.
빈이 생성된 이후 추가로 호출되는 콜백들이 있는데요. Spring bean lifecycle, Spring bean hook 같은 키워드로 검색해보시면 도움이 될 것 같아요!
 추후에는 위 2개의 키워드도 찾아봐서 정리하려고 한다. 
감사합니다 토미!

정리

필드에서 바로 주입받으려면 생성자가 실행된 이후에 일어나는 거라고 생각해서 필드 주입이 아닌 생성자 주입을 이용해야한다.

이때 Junit에서는 Jupiter가 빈들을 관리한다고 한다. 그래서 @Autowired를 명시적으로 적지 않으면 해당 스프링 빈을 찾지 못하므로 @Autowired를 적어서 스프링에게 해당 빈을 찾으라고 알려줘야 작동한다.

Reference

http://daplus.net/java-%EB%AA%A8%EB%B2%94-%EC%82%AC%EB%A1%80-setup-%EB%98%90%EB%8A%94-%EC%84%A0%EC%96%B8%EC%8B%9C-junit-%ED%81%B4%EB%9E%98%EC%8A%A4-%ED%95%84%EB%93%9C%EB%A5%BC-%EC%B4%88%EA%B8%B0%ED%99%94/

 

[java] 모범 사례 : setUp () 또는 선언시 JUnit 클래스 필드를 초기화 하시겠습니까? - 리뷰나라

이렇게 선언 할 때 클래스 필드를 초기화해야합니까? public class SomeTest extends TestCase { private final List list = new ArrayList(); public void testPopulateList() { // Add stuff to the list // Assert the list contains what I expect }

daplus.net

https://goodgid.github.io/How-JUnit-Works/

 

JUnit의 동작 방식

Index

goodgid.github.io

https://velog.io/@sdp1123/JUnit5-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85-%EC%98%A4%EB%A5%98

 

JUnit5 생성자 주입 에러

JUnit5로 테스트코드를 작성하면서 생성자를 통해서 DI하는 것, lombok을 이용한 DI도 에러가 발생했다.그런데 왜 lombok을 이용한 @Setter(onMethod\_ = {@Autowired}) 수정자 주입방식만 잘 되는걸까.. 도저히

velog.io

 

728x90
Comments