테스트 주도 개발(Test-Driven Development, TDD) - 2
테스트 주도 개발(Test-Driven Development, TDD)
에 대한 학습
📜 참고문서 : JUnit 5 User Guide
📕 환경
TDD를 공부하기에 앞서 나는 아래와 같은 환경으로 시스템을 구축했다.
통합개발환경(IDE) | IntelliJ Ultimate |
---|---|
프로그래밍 언어(Language) | Java 11 |
프레임워크(Framework) | Spring Boot 2.4.1 |
테스트 프레임워크(Test Framework) | JUnit 5 |
빌드 도구(Build Tool) | Gradle |
우선 컨트롤러(Controller)를 만들기 전 테스트 코드를 작성할 것이다.
테스트 코드를 관리하기 위해 전체적인 구조를 변경하고 테스트 코드 작성에 들어간다.
// file: 'SpringbootApplicationTests.class'
@SpringBootTest
// @ExtendWith(SpringExtension.class)
public abstract class SpringbootApplicationTests {
}
최상위 테스트 코드 클래스인 SpringbootApplicationTests이다.
이 녀석을 모든 패키지의 최상단에 위치하도록 위치를 변경하고, 추상 클래스로 변경한다.
💡 추상클래스로 변경하는 이유?
Spring의 각 계층을 모두 쪼개어 테스트 코드를 작성하기 위함이다.
최상위 클래스를 추상 클래스로 선언하고, 클래스 계층을 피라미드 형식으로 상속받아 구현 할 것이다.
그래서 각 테스트를 진행할 때 지나치게 무겁지 않은 테스트 코드를 작성할 수 있는 구조를 만든다.
@SpringBootTest
애플리케이션 콘텍스트에서 모든 설정과 Bean을 가져와 로딩한다.
테스트 프레임워크가 개발환경과 가장 유사하게 동작하게 해 줄 수 있다.
다만 애플리케이션의 모든 설정을 가져오는 것이기 때문에 테스트 코드가 무거워진다는 단점이 있다.
@ExtendWith(SpringExtension.class)
JUnit5로 업데이트되면서 변한 부분인데,
JUnit4의 @RunWith
와 비슷한 역할을 한다.
JUnit4는 @RunWith를 필수적으로 선언해야 했는데
JUnit5는 이를 생략 할 수 있다.
왜냐하면 @SpringBootTest
안에 이미 선언되어 있기 때문이다.
설정 파일의 흐름을 간단하게 풀어보자면
애플리케이션 -> 테스트 코드 -> JUnit5(Test Framework)로 가져오는 셈
// file: 'AbstractMockMvcTests.class'
@AutoConfigureMockMvc
public abstract class AbstractMockMvcTests extends SpringbootApplicationTests {
@Autowired
protected MockMvc mvc;
}
MVC 테스트를 위한 AbstractMockMvcTests
클래스이다.
역시 추상 클래스로 선언해주고 SpringbootApplicationTests
를 상속받도록 한다.
@AutoConfigureMockMvc
MockMvc
를 빌더 없이 주입받을 수 있게 설정해주는 애노테이션이다.
@WebMvcTest
와는 다른 방법으로, @AutoConfigureMockMvc
는 MVC테스트 외 모든 설정을 같이 올린다.
이 모든 설정이라 함은 AOP
도 되고 JPA Repository
도 사용 가능하다는 뜻이다.
그래서 실제적으로 동작하는 MVC테스트를 진행하려면 위 애노테이션을 사용한다.
@Autowired
protected MockMvc mvc;
AbstractMockMvcTests
를 상속받는 클래스에서 MockMvc
객체를 확인할 수 있게
접근제한자를 protected
로 공개해준다.
// file: 'FirstControllerTest.class'
public class FirstControllerTest extends AbstractMockMvcTests {
@Test
public void init() throws Exception {
mvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(content().string("Hello! World!"));
}
}
컨트롤러 테스트 코드를 작성한다.
AbstractMockMvcTests
를 상속받아 FirstControllerTest
를 작성한다.
앞으로 작성될 모든 컨트롤러 테스트 코드는 AbstractMockMvcTests
를 상속받아 작성하면 된다.
@Test
이 애노테이션이 선언된 메서드를 테스트하겠다는 뜻이다.
해당 애노테이션을 쓰기 위해서는 지켜야 할 약속이 두 가지 있다.
@Test
가 선언된 메서드는 접근 제한자가 public
이어야 하고 반환 타입이 void
여야 한다.
그리고 @Test
를 단위 테스트(Unit Test)
에서 말하는 단위(Unit)
로 본다.
그러니까 FirstControllerTest
에는 @Test
가 한개 뿐 이므로,
테스트 코드를 실행하면 한번의 테스트가 실행 될 것이다.
성공하면 1/1 성공!
실패하면 1/1 실패! 같은 식으로 말이다.
// file: 'FirstControllerTest.class'
@Test
public void init() throws Exception {
mvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(content().string("Hello! World!"));
}
perform()
을 수행한다.
perform()
은 Get 방식으로 URL /
에 요청(Request)을 보낸다.
andExpect()
는 검증을 위한 코드이다.
status().isOk()
는 통신상태가 200이면 true를 반환한다.
content().string("Hello! World!")
는 컨트롤러가 반환하는 내용이 Hello! World!
이면 true를 반환한다.
그리고 테스트를 실행하면 실패 할 것이다.
아직 이 테스트를 통과할 구현 코드가 없기때문이다.
이제 이 테스트 코드를 따라 실행될 FirstController
를 작성한다.
// file: 'FirstController.class'
@RestController
public class FirstController {
@GetMapping("/")
public String init() {
return "Hello! World!";
}
}
@RestController
@Controller + @ResponseBody
이다.
컨트롤러가 View Page가 아닌 데이터 리터럴을 반환하게 해준다.
@GetMapping("/")
URL /
에 Get방식의 요청(Request)을 받아 처리하겠다는 의미이다.
public String init() {
return "Hello! World!";
}
URL /
에 Get방식의 요청(Request)을 받을 경우 init()
을 실행할 것이고,
init()
은 Hello! World!
라는 String 리터럴을 반환해 줄 것이다.
최종적으로 모든 코드를 작성했으면 FirstControllerTest
를 실행해본다.
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
13:18:52.515 [Test worker] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
13:18:52.526 [Test worker] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
13:18:52.555 [Test worker] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.study.springboot.controller.FirstControllerTest] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
13:18:52.570 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [com.study.springboot.controller.FirstControllerTest], using SpringBootContextLoader
13:18:52.575 [Test worker] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.study.springboot.controller.FirstControllerTest]: class path resource [com/study/springboot/controller/FirstControllerTest-context.xml] does not exist
13:18:52.576 [Test worker] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.study.springboot.controller.FirstControllerTest]: class path resource [com/study/springboot/controller/FirstControllerTestContext.groovy] does not exist
13:18:52.576 [Test worker] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.study.springboot.controller.FirstControllerTest]: no resource found for suffixes {-context.xml, Context.groovy}.
13:18:52.577 [Test worker] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.study.springboot.controller.FirstControllerTest]: FirstControllerTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
13:18:52.633 [Test worker] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [com.study.springboot.controller.FirstControllerTest]
13:18:52.687 [Test worker] DEBUG org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider - Identified candidate component class: file [D:\Project\springboot\build\classes\java\main\com\study\springboot\SpringbootApplication.class]
13:18:52.688 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration com.study.springboot.SpringbootApplication for test class com.study.springboot.controller.FirstControllerTest
13:18:52.761 [Test worker] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [com.study.springboot.controller.FirstControllerTest]: using defaults.
13:18:52.761 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener, org.springframework.security.test.context.support.ReactorContextTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
13:18:52.772 [Test worker] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
13:18:52.772 [Test worker] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
13:18:52.773 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@1cc79614, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@1d454c54, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@33794a82, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@38817d1d, org.springframework.test.context.support.DirtiesContextTestExecutionListener@18d62b2f, org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener@2f46f38d, org.springframework.test.context.event.EventPublishingTestExecutionListener@561281bd, org.springframework.security.test.context.support.ReactorContextTestExecutionListener@65c877f7, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@4c6eb732, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@54acefa9, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@167183e8, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@30a0391e, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener@9cedf1f, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@3aaca828]
13:18:52.776 [Test worker] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@78793906 testClass = FirstControllerTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@565eca81 testClass = FirstControllerTest, locations = '{}', classes = '{class com.study.springboot.SpringbootApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@53352bfc, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@251d899, [ImportsContextCustomizer@5938f557 key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@64fbb213, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@52ff824c, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@56d40319, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@774ee425], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true]], class annotated with @DirtiesContext [false] with mode [null].
13:18:52.808 [Test worker] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.1)
2021-01-14 13:18:53.040 INFO 13132 --- [ Test worker] c.s.s.controller.FirstControllerTest : Starting FirstControllerTest using Java 11.0.8 on Changhoon-Han with PID 13132 (started by Han in D:\Project\springboot)
2021-01-14 13:18:53.045 INFO 13132 --- [ Test worker] c.s.s.controller.FirstControllerTest : No active profile set, falling back to default profiles: default
2021-01-14 13:18:54.243 INFO 13132 --- [ Test worker] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-01-14 13:18:54.648 INFO 13132 --- [ Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2021-01-14 13:18:54.648 INFO 13132 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2021-01-14 13:18:54.649 INFO 13132 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 1 ms
2021-01-14 13:18:54.670 INFO 13132 --- [ Test worker] c.s.s.controller.FirstControllerTest : Started FirstControllerTest in 1.856 seconds (JVM running for 2.936)
2021-01-14 13:18:54.983 INFO 13132 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
BUILD SUCCESSFUL in 4s
4 actionable tasks: 2 executed, 2 up-to-date
오후 1:18:55: 작업 실행이 완료되었습니다 ':test --tests "com.study.springboot.controller.FirstControllerTest"'.
테스트에 관련된 로그가 작성되며 테스트가 성공으로 완료됨을 확인할 수 있다.
FirstController
의 init()
반환 값을 바꾸면 어떻게 될까?
@RestController
public class FirstController {
@GetMapping("/")
public String init() {
return "Hellow! World!";
}
}
반환 값을 Hello! World!
-> Hellow! World!
로 변경하고
FirstControllerTest
를 실행해보았다.
> Task :compileJava
> Task :processResources UP-TO-DATE
> Task :classes
> Task :compileTestJava UP-TO-DATE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test
13:39:15.001 [Test worker] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate]
13:39:15.010 [Test worker] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)]
13:39:15.033 [Test worker] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.study.springboot.controller.FirstControllerTest] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper]
13:39:15.041 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [com.study.springboot.controller.FirstControllerTest], using SpringBootContextLoader
13:39:15.044 [Test worker] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.study.springboot.controller.FirstControllerTest]: class path resource [com/study/springboot/controller/FirstControllerTest-context.xml] does not exist
13:39:15.044 [Test worker] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.study.springboot.controller.FirstControllerTest]: class path resource [com/study/springboot/controller/FirstControllerTestContext.groovy] does not exist
13:39:15.044 [Test worker] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.study.springboot.controller.FirstControllerTest]: no resource found for suffixes {-context.xml, Context.groovy}.
13:39:15.045 [Test worker] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.study.springboot.controller.FirstControllerTest]: FirstControllerTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
13:39:15.081 [Test worker] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [com.study.springboot.controller.FirstControllerTest]
13:39:15.130 [Test worker] DEBUG org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider - Identified candidate component class: file [D:\Project\springboot\build\classes\java\main\com\study\springboot\SpringbootApplication.class]
13:39:15.131 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration com.study.springboot.SpringbootApplication for test class com.study.springboot.controller.FirstControllerTest
13:39:15.197 [Test worker] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [com.study.springboot.controller.FirstControllerTest]: using defaults.
13:39:15.197 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener, org.springframework.security.test.context.support.ReactorContextTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
13:39:15.205 [Test worker] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
13:39:15.206 [Test worker] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
13:39:15.206 [Test worker] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@1cc79614, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@1d454c54, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@33794a82, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@38817d1d, org.springframework.test.context.support.DirtiesContextTestExecutionListener@18d62b2f, org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener@2f46f38d, org.springframework.test.context.event.EventPublishingTestExecutionListener@561281bd, org.springframework.security.test.context.support.ReactorContextTestExecutionListener@65c877f7, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@4c6eb732, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@54acefa9, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@167183e8, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@30a0391e, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener@9cedf1f, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@3aaca828]
13:39:15.209 [Test worker] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@78793906 testClass = FirstControllerTest, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@565eca81 testClass = FirstControllerTest, locations = '{}', classes = '{class com.study.springboot.SpringbootApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@53352bfc, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@4b3fa0b3, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@251d899, [ImportsContextCustomizer@5938f557 key = [org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration, org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration, org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@64fbb213, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@52ff824c, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@56d40319, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@774ee425], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true]], class annotated with @DirtiesContext [false] with mode [null].
13:39:15.234 [Test worker] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.1)
2021-01-14 13:39:15.404 INFO 9684 --- [ Test worker] c.s.s.controller.FirstControllerTest : Starting FirstControllerTest using Java 11.0.8 on Changhoon-Han with PID 9684 (started by Han in D:\Project\springboot)
2021-01-14 13:39:15.406 INFO 9684 --- [ Test worker] c.s.s.controller.FirstControllerTest : No active profile set, falling back to default profiles: default
2021-01-14 13:39:16.247 INFO 9684 --- [ Test worker] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-01-14 13:39:16.512 INFO 9684 --- [ Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
2021-01-14 13:39:16.512 INFO 9684 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Initializing Servlet ''
2021-01-14 13:39:16.513 INFO 9684 --- [ Test worker] o.s.t.web.servlet.TestDispatcherServlet : Completed initialization in 1 ms
2021-01-14 13:39:16.535 INFO 9684 --- [ Test worker] c.s.s.controller.FirstControllerTest : Started FirstControllerTest in 1.295 seconds (JVM running for 2.188)
MockHttpServletRequest:
HTTP Method = GET
Request URI = /
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = com.study.springboot.controller.FirstController
Method = com.study.springboot.controller.FirstController#init()
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"14"]
Content type = text/plain;charset=UTF-8
Body = Hellow! World!
Forwarded URL = null
Redirected URL = null
Cookies = []
Response content expected:<Hello! World!> but was:<Hellow! World!>
필요:Hello! World!
실제 :Hellow! World!
<클릭하여 차이점 확인>
java.lang.AssertionError: Response content expected:<Hello! World!> but was:<Hellow! World!>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
at org.springframework.test.web.servlet.result.ContentResultMatchers.lambda$string$4(ContentResultMatchers.java:136)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:196)
at com.study.springboot.controller.FirstControllerTest.init(FirstControllerTest.java:14)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:248)
at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$5(DefaultLauncher.java:211)
at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:226)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:199)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:132)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at com.sun.proxy.$Proxy2.stop(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.stop(TestWorker.java:133)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:182)
at org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:164)
at org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:414)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56)
at java.base/java.lang.Thread.run(Thread.java:834)
2021-01-14 13:39:16.850 INFO 9684 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
FirstControllerTest > init() FAILED
java.lang.AssertionError at FirstControllerTest.java:14
1 test completed, 1 failed
> Task :test FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':test'.
> There were failing tests. See the report at: file:///D:/Project/springboot/build/reports/tests/test/index.html
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 3s
4 actionable tasks: 2 executed, 2 up-to-date
Response content expected:<Hello! World!> but was:<Hellow! World!>
필요:Hello! World!
실제:Hellow! World!
2021-01-14 13:39:16.850 INFO 9684 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
FirstControllerTest > init() FAILED
java.lang.AssertionError at FirstControllerTest.java:14
로그를 자세히 읽어보면
FirstController
의 init()
이 제대로 수행되지 못했고,
해당 코드가 FirstController
클래스의 14번째 줄에 있다는 것이다.
테스트 실패 원인은 Hello! World!
가 나올 거라 예상하였는데
실제로 나온 값이 Hellow! World!
라는 것이다.