[강의요약] 스프링 부트 개념과 활용 - 활용 기본 (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 = 모든 로그
-
컬러 출력
- 파일에 쓰기 : 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한 테스트 가능)
- Mock : 톰캣을 구동하지 않고 마치 디스패처 서블릿에 요청을 던진 것 처럼 동작을 해줌
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을 모두 돌리는 통합 테스트
- @JsonTest : 응답이 어떻게 나가는 지만 테스트
- @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. 의존성 추가 시 추가되는 기능
- 캐시들을 꺼줌
- restart : 코드를 고치고 빌드를 하면 서버를 재시작해주는데, 톰캣을 껏다키는 거보다 훨씬 빠름
BaseClassLoader : 잘 안 바뀌는 클래스 담당 로더
restart ClassLoader : 우리가 자주 수정하는 클래스 담당 로더 - live reload : restart 되면 웹브라우저도 새로고침 해줌. 플러그인이 설치되어야 함
- ./spring-boot-devtools.properties : 우선 순위가 제일 높은 프로퍼티
사실 딱히 유용한 기능은 아니다..!
댓글남기기