개발을 하다보면 테스트 코드의 중요성을 강조하는 의견이 많다.
하지만 나의 경우 테스트 코드를 작성하는 것이 실제 서비스 코드를 작성하는 것보다 어려웠다. 이해하지 않고 코드를 배끼다 보면, 나의 시스템과 맞지 않는 코드를 사용하게 되고 테스트 코드를 돌려도 오류 -> 오류 -> 오류... 가 발생한다.
테스트 코드에 관해 학습을 하며, 초반 진입장벽이었던 context에 관해 정리하려고 한다.
목차
- Spring context
 - Spring Test Context
 - @MockBean
 - @Mock
 - @SpringBootTest
 - @ExtendWith
 
Spring context는 심오하게 들어가면 엄청 길어지니, 간단하게 정리하면 어플리케이션의 빈을 생성/관리하는 엔진으로 인터페이스이다. 스프링 프레임워크를 이용하여 어플리케이션을 개발했다면 Bean을 생성했을 것이다. 이 Bean을 생성/관리하는 것이 Application Context이다.
org.springframework.context.ApplicationContext.java 코드를 통해 구조를 확인할 수 있다.
이전에 언급한 Spring Context는 어플리케이션 (=백엔드 서버)를 구성할 때 완전하게 필요한 Application Context에 관한 이아기였다. 하지만, 테스트 시(Test Context)에도 이전에 언급한 Application Context를 구성한다. 

이때 Application Context는 테스트 특화된 Application Context가 아니라 그냥 Spring에서 사용되는 Application Context 인터페이스다. 하지만 다른 점은, Test시에 추가적인 기능을 포함한다는 것이다.
org.springframework.test.context.TestContext.java
org.springframework.test.context.TestContextManager.java
코드를 통해 확인할 수 있다.
위에서 언급한 Test Context의 추가적인 기능 중 하나가 MockBean이다.
단위 테스트를 할 때 하나의 단위 기능이 다른 객체를 참조하여 동작할 수 있다. 이때 다른 기능이 개발되지 않았거나/다른 사람이 개발 중이거나 한다면 -> 가짜 객체를 만들어서 동작을 지정해줌으로써 단위 테스트를 수행할 수 있다. 이때 Mocking이라는 개념을 사용하게 된다.

Mockito 라이브러리를 이용하여 다른 객체에 대한 대리객체(빈 껍데기, Mock)를 내세워 테스트에 문제가 없도록 한다.
이때, 대리 객체를 Bean으로 등록하는 것이 @MockBean이다.
@MockBean으로 Mocking된 객체는 BeanFactory에 저장된다.
BeanFactory란 1번에서 언급된 Application Context와 관련있는 개념인데, 단순히 말하면 빈을 생성/등록하는 데에 필요한 인터페이스다. 어차피 Application Context가 BeanFactory를 상속해서 그 기능을 다 갖고 있다.

여기서 ListableBeanFactory가 BeanFactory의 자식 인터페이스이다.
3번에서 @MockBean에 대해 설명했다.
@Mock은 @MockBean과 다르게 모킹이 된 객체지만 빈으로 관리되지 않는다. 
추가적으로,
InternalFollowService internalFollowService = Mockito.mock(InternalFollowService.class);
InternalScrapService internalScrapService = Mockito.mock(InternalScrapService.class);
이건 내가 작성한 코드인데, @Mock 어노테이션 사용하지 않아도 위와 같은 형태로 구현할 수도 있다.
테스트를 수행할 때 많이 사용하는 어노테이션이다. 하지만 사용하지 않는 것이 좋다는 의견이 많다. -> 매우 무거움. 실제로 수행할 때 느리다는 게 느껴질 정도로 느리다.
Application Context를 구성하고 실제 서버를 띄우는 것과 거의 비슷하게 돌아가기 때문에 매우 무겁다.
공식 문서에서는 "bootstrap with Spring Boot's support" 라고 언급하고 있다. 스프링 부트에 필요한 모든 것을 알아서 처리해준다는 것 같다.
보통 단위 테스트에서는 잘 이용하지 않는다. 통합 테스트에서 많이 이용한다. 복잡한 단위 테스트에선 이용할 수도 있다. (이전에 단위 테스트 코드를 작성할 때 서비스가 매우 복잡해서 해당 어노테이션을 통해 테스트한 경험이 있다. 근데 다시 복기해보면 별로인 듯. 사실 단위 테스트에서 해당 어노테이션을 쓰게 된다는 건 설계를 잘못 했다는 방증같기도 하다.)
해당 어노테이션을 통해 확장할 기능을 명시할 수 있다.
@ExtendWith(MockitoExtension.class)
@ExtendWith(SpringExtension.class)
보통 둘 중 하나를 명시하는 편이다. 해당 어노테이션이 무슨 역할을 하는지 확실히 알고 사용해야 한다.
위에서 @Mock, @MockBean을 언급했다. @MockBean은 Application Context가 관리한다. 그러기 위해서는 Spring Test FrameWork 기능을 사용해야 한다. 이를 위해 사용하는 것이
@ExtendWith(SpringExtension.class)
이다.
@ExtendWith(MockitoExtension.class)
는 Spring Test Framework 기능이 불필요하고 @Mock, @InjectMocks과 같은 Mockito 기능을 사용할 때 확장하는 어노테이션이다.
테스트 코드를 작성하면서 느낀 건 내가 개발하는 스프링 프레임워크 기반의 어플리케이션 서버가 JAVA 언어로 작성되었다는 걸 상기해야 된다는 것이었다.
사실 단위 테스트의 경우, (만약 서비스를 테스트할 때)
Application context를 구성할 필요가 없는 경우가 많다.
서비스를 테스트한다면 보통 동작에 관해 테스트 하는 것이고, 모든 빈을 등록한 Application context를 생성할 필요가 없다는 것이다.
하지만 어떠한 관행처럼 Spring이니까 빈이랑 뭐 관련된 설정이 있겠지... (알아보지 않고) 그냥 단순하게 @ExtendWith(SpringExtension.class)를 테스트 클래스 위에 적어주고 서비스를 @Autowired하여 사용하곤 했다.
-> 이것이 테스트 부채로 자리잡을 수 있음. (내 생각)
Spring이라는 개념에 대해 매몰되어서 내가 능동적으로 코드를 작성하지 않고 스프링 코드 컨벤션에 해당하는 인터넷의 규범을 찾기 위해, Spring 키워드를 붙여 검색하는 것을 지양해야 할 것 같다.
👆🏻 방금 언급한 것이 며칠동안 얻은 수확이다.
참고 문서
https://stackoverflow.com/questions/60308578/what-is-the-difference-between-extendwithspringextension-class-and-extendwit
https://dzone.com/articles/difference-between-beanfactory-and-applicationcont
https://stackoverflow.com/questions/58901288/springrunner-vs-springboottest
https://spring.io/blog/2016/04/15/testing-improvements-in-spring-boot-1-4
https://stackoverflow.com/questions/9689131/what-does-application-context-in-spring-do