[강의요약] 스프링 부트 개념과 활용 - 활용 기본 (2)

개인적인 학습을 위한 Inflearn - 스프링부트 개념과 활용(백기선) 강의 요약입니다.

개념과 원리 위주로 요약합니다.

이전 글 에서 이어집니다.

4부. 스프링 부트 활용


1. 프로파일

1-1. @Profile : profile이 설정된 값일 때 사용하겠다 선언

@Profile(”prod”) VS @Profile(”test”)

// BaseConfiguration
@Profile("prod")
@Configuration
public class BaseConfiguration {
    @Bean
    public String hello() {
        return "hello";
    }
}

// TestConfiguration
@Profile("test")
@Configuration
public class TestConfiguration {
    @Bean
    public String hello() {
        return "hello";
    }
}

//application.properties
spring.profiles.active=prod
// or
spring.profiles.active=test

1-2. spring.profiles.active 와 application.properties

profile name 이 붙은 properties가 default application.properties 보다 우선순위가 높음

// application-prod.properties
cherrue.name=cherrue PROD
// application-test.properties
cherrue.name=cherrue TEST

// shell
java -jar target/springinit-0.1.jar --spring.profiles.active=prod
===================
cherrue PROD
===================

1-3. spring.profiles.include=smaller

// application-prod.properties
cherrue.name=cherrue PROD
cherrue.profiles.include=smaller
// application-proddb.properties
cherrue.fullName=cherrueFullName

// shell
java -jar target/springinit-0.1.jar --spring.profiles.active=proddb 


2. 로깅 1부: 스프링 부트 기본 로거 설정

2-1. 퍼사드를 적용하여 로거의 변화에 유연하게 대처하자

springboot log → Commons Loggings (유사관계) SLF4J

SLF4J 와 Commons Loggins은 로거는 실제 로거가 아닌 퍼사드(interface. 로거 변경에도 같은 동작 보장)

SpringBoot 1.X 대에서는 Commons Logging을 제거하고 SLF4J를 쓰려면 퍼사드의 장점을 포기해야 했음

⇒ Spring5.X(SpringBoot2.x)에서는 Spring-JCL이 기본 적용되어 Commons Logging을 SLF4J로 의존성을 정리했음

jul-to-slf4j ⇒ slf4j ⇒ (implementation) logback

2-2. 스프링 부트의 로깅

  • 기본 형태는 Logback이 찍어주는 형태
  • —debug = core 라이브러리, —trace = 모든 로그
  • 컬러 출력

    log

  • 파일에 쓰기 : logging.path=logs
    • 10Mb가 넘으면 아카이브 됨
  • 로그 레벨 조정 : logging.level.패키지명=수준(TRACE,DEBUG,WARN,ERROR)

로그 찍는 방법은 아래 소스코드 참고

@Component
public class SampleRunner implements ApplicationRunner {
    @Autowired
    CherrueProperties cherrueProperties;
    @Autowired
    String hello;
    private Logger logger = LoggerFactory.getLogger(SampleRunner.class);
    
		@Override
    public void run(ApplicationArguments args) throws Exception {
        logger.debug("======================");
        logger.debug(cherrueProperties.getName());
        logger.debug(hello);
        logger.debug("======================");
    }

    private void printProgramArguments(ApplicationArguments args) {
        logger.debug("foo : " + args.containsOption("foo"));
        logger.debug("bar : " + args.containsOption("bar"));
    }
}

// application.properties
cherrue.name = Cherrue
cherrue.age = ${random.int(0,100)}
cherrue.fullName = ${cherrue.name} Lee
cherrue.sessionTimeout=25
spring.profiles.active=prod
spring.output.ansi.enabled=always
logging.path=logs
logging.level.me.cherrue=debug

3. 로깅 2부: 커스터마이징

application.properties에서 제공하는 속성 이상으로 로깅을 제어하고 싶다면 log 설정 파일 작성

  • logback-spring.xml (logback.xml)
    • Logback-spring.xml VS logback.xml ⇒ -spring을 붙이면 환경 변수나 spring 변수 활용 가능
      <?xml version="1.0" encoding="UTF-8" ?>
      <configuration>
          <include resource="org/springframework/boot/logging/logback/base.xml" />
          <logger name="me.cherrue" level="DEBUG"/>
      </configuration>
    
  • 기본값 Logback을 log4j2으로 변경

      <!-- pom.xml -->
      				**<dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
                  <version>2.2.0.RELEASE</version>
                  <exclusions>
                      <exclusion>
                          <groupId>org.springframework.boot</groupId>
                          <artifactId>spring-boot-starter-logging</artifactId>
                      </exclusion>
                  </exclusions>
              </dependency>
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-log4j2</artifactId>
              </dependency>**
    

4. 테스트

4-1. 프로젝트 생성

// SampleController.java
@RestController
public class SampleController {
    @Autowired
    private SampleService sampleService;

    @GetMapping("/hello")
    public String hello() {
        return "hello " + sampleService.getName();
    }
}

// SampleService.java
@Service
public class SampleService {
    public String getName() {
        return "cherrue";
    }
}

// SpringtestdemoApplication.java
@SpringBootApplication
public class SpringtestdemoApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(SpringtestdemoApplication.class);
        app.run(args);
    }
}

4-2. 기본 테스트 소스 작성

// pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

// SampleControllerTest.java
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class SampleControllerTest {
    @Autowired
    MockMvc mockMvc;

    @Test
    public void hello() throws Exception {
        mockMvc.perform(get("/hello")).andExpect(status().isOk())
                .andExpect(content().string("hello cherrue"))
                .andDo(print());
    }
}
  • webEnvironment
    • Mock : 톰캣을 구동하지 않고 마치 디스패처 서블릿에 요청을 던진 것 처럼 동작을 해줌
      대신 MockMvc를 꼭 써야 함
    • RANDOM_PORT(DEFINED_PORT) : 내장 톰캣으로 진짜로 띄워서 거기다가 요청을 던짐 TestRestTemplate 을 사용하거나, WebTestClient 사용(WebFlux라서 async한 테스트 가능)

4-3. @MockBean : 특정 bean을 바꿔서 테스트하고 싶을 때 사용

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SampleControllerTest {

    @Autowired
    TestRestTemplate testRestTemplate;

    @MockBean
    SampleService mockSampleService;

    @Test
    public void helloRestTemplate() throws Exception {
        when(mockSampleService.getName()).thenReturn("mockCherrue");
        
        String result = testRestTemplate.getForObject("/hello", String.class);
        assertThat(result).isEqualTo("hello mockCherrue");
    }
}

4-4. 👍🏻 WebTestClient (vs RestTemplateClient)

// pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
		<version>2.2.0.RELEASE</version>
</dependency>

// SampleControllerTest.java
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SampleControllerTest {
    @MockBean
    SampleService mockSampleService;

    @Autowired
    WebTestClient webTestClient;

    @Test
    public void helloWebTestClient() {
        when(mockSampleService.getName()).thenReturn("mockCherrue");

        webTestClient.get().uri("/hello").exchange()
                .expectStatus().isOk()
                .expectBody(String.class).isEqualTo("hello mockCherrue");
    }
}

4-5. slice : 테스트 할 객체만 불러 와서 가볍게 테스트하고 싶어!

SpringBootTest는 SpringbootApplication을 모두 돌리는 통합 테스트

  1. @JsonTest : 응답이 어떻게 나가는 지만 테스트
  2. @WebMvcTest : controller만 딱 테스트. 서비스도 빈 등록이 안 된다 서비스는 MockBean으로 등록해서 사용하면 된다.
    @WebMvcTest, @WebFluxTest, @DataJpaTest 등
@RunWith(SpringRunner.class)
@WebMvcTest
public class SliceControllerTest {
    @Autowired
    MockMvc mockMvc;

    @MockBean
    SampleService mockSampleService;

    @Test
    public void testControllerOnly() throws Exception {
        when(mockSampleService.getName()).thenReturn("mockCherrue");

        mockMvc.perform(get("/hello"))
                .andExpect(content().string("hello mockCherrue"));
    }
}

5. 테스트 유틸

🌟 OutputCapture : 테스트에서 발생하는 모든 아웃풋을 잡음. JUnit의 Rule을 확장해서 만든 유틸

로그도 테스트 할 수 있다!!

// SampleController.java
@RestController
public class SampleController {

    Logger logger = LoggerFactory.getLogger(SampleController.class);

    @Autowired
    private SampleService sampleService;

    @GetMapping("/hello")
    public String hello() {
        logger.info("holoman");
        System.out.println("skip");
        return "hello " + sampleService.getName();
    }
}

// SampleControllerTest.java
@RunWith(SpringRunner.class)
@WebMvcTest
public class SliceControllerTest {

    @Rule
    public OutputCapture outputCapture = new OutputCapture();

    @Autowired
    MockMvc mockMvc;

    @MockBean
    SampleService mockSampleService;

    @Test
    public void testControllerOnly() throws Exception {
        when(mockSampleService.getName()).thenReturn("mockCherrue");

        mockMvc.perform(get("/hello"))
                .andExpect(content().string("hello mockCherrue"));

        assertThat(outputCapture.toString())
                .contains("holoman")
                .contains("skip");
    }
}

6. Spring-Boot-Devtools

개발에 유용한 기능 모음

6-1. 사용 방법

// pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

6-2. 의존성 추가 시 추가되는 기능

  1. 캐시들을 꺼줌
  2. restart : 코드를 고치고 빌드를 하면 서버를 재시작해주는데, 톰캣을 껏다키는 거보다 훨씬 빠름
    BaseClassLoader : 잘 안 바뀌는 클래스 담당 로더
    restart ClassLoader : 우리가 자주 수정하는 클래스 담당 로더
  3. live reload : restart 되면 웹브라우저도 새로고침 해줌. 플러그인이 설치되어야 함
  4. ./spring-boot-devtools.properties : 우선 순위가 제일 높은 프로퍼티

사실 딱히 유용한 기능은 아니다..!

댓글남기기