테스트 주도 개발(Test-Driven Development, TDD) - 2

테스트 주도 개발(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"'.

 

테스트에 관련된 로그가 작성되며 테스트가 성공으로 완료됨을 확인할 수 있다.

FirstControllerinit() 반환 값을 바꾸면 어떻게 될까?

 

@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

 

로그를 자세히 읽어보면

FirstControllerinit()이 제대로 수행되지 못했고,

해당 코드가 FirstController 클래스의 14번째 줄에 있다는 것이다.

테스트 실패 원인은 Hello! World!가 나올 거라 예상하였는데

실제로 나온 값이 Hellow! World!라는 것이다.

 


© 2022. All rights reserved.