[스프링부트 테스트]
테스트 코드 : 유지보수 과정에서 재사용되어 시스템의 안정성을 보장하는 중요한 장치
대부분의 프로젝트는 JUnit을 기반으로 일관성 있는 단위 테스트 진행
단위 테스트 : 자신이 작성한 클래스에 대한 테스트. 테스트 단계 중에서 가장 기본
스프링부트가 제공하는 테스트 환경과 테스트 방법을 알아보자
[스프링 부트에서 테스트하기]
프로젝트 의존성에 테스트 스타터가 등록되어 있으므로 여러 라이브러리들도 자동적으로 추가된다.
웹 애플리케이션에서의 테스트는 일반적인 자바 애플리케이션과 달리 테스트 과정이 복잡하고 자동을 처리해야 할 일도 많다.
@RunWith(SpringRunner.class)
Junit에서 기본적으로 제공하는 러너가 아닌 스프링 러너를 사용하기 위해서 추가
@SpringBootTest
테스트 케이스가 실행될 때 테스트에 필요한 모든 설정과 빈들을 자동을 초기화하는 역할을 수행
- properties : 테스트가 실행되기 전에 테스트에 사용할 프로퍼티들을 'key=value'형태로 추가하거나 properties파일에 설정 프로퍼티를 재정의.
- classes : 테스트할 클래스들을 등록한다. 등록되지 않은 클래스는 객체 생성되지 않기 때문에 테스트 과정에서 불필요한 메모리 낭비를 피할 수 있다. 만일 classes 속성을 생략하면 애플리케이션에 정의된 모든 빈을 생성한다.
- webEnvironment : 애플리케이션이 실행될때 웹과 관련된 환경을 설정할 수 있다.
package com.studyboot;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(
properties = { "author.name=hahaha", //프로퍼티 재정의
"author.age=33",
"author.nation=Korea"
})
public class PropertiesTest {
@Autowired
Environment environment;
@Test
public void testMethod() {
System.out.println("이름:"+ environment.getProperty("author.name"));
System.out.println("나이:"+ environment.getProperty("author.age"));
System.out.println("국적:"+ environment.getProperty("author.nation"));
}
}
[MockMVC 이용해서 컨트롤러 테스트하기]
목(mock) : 테스트를 위해 만든 모형
모킹(mocking) : 테스트를 위해 실제 객체와 비슷한 모의 객체를 만드는 것
목업(Mock-up) : 모킹한 객체를 메모리에서 얻어내는 과정
서블릿 컨테이너를 모킹하기 위해서는 두가지 방법 존재
서블릿 컨테이너 모킹 : 실제 서블릿 컨테이너가 아닌 테스트용 모형 컨테이너를 사용하기 때문에 간단하게 컨트롤러 테스트 가능
1. @WebMvcTest : @Controller, @RestController가 설정된 클래스를 찾아 메모리에 생성. @Service, @Repository가 붙은 객체들은 테스트 대상이 아닌것으로 처리되기 때문에 생성되지 않음.
@RunWith(SpringRunner.class)
@WebMvcTest
public class BoardControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testHello() throws Exception{
mockMvc.perform(get("/hello").param("name","hoho"))
.andExpect(status().isOk())
.andExpect(content().string("Hello : hoho"))
.andDo(print());
// perform() 메소드를 이용하여 요청 전송 가능. 결과로 ResultAction 객체를 반환.
// ResultAction.andExpect() 는 응답결과를 검증할 수있는 메소드
// param()은 key,value 형태로 파라미터를 여러개 보낼 수 있다.
// 실제로 생성된 요청과 응답 메세지를 모두 확인하고 싶을 경우 andDo(ResultHandler handler) 메소드 사용.
// MockMvcResultHandlers.print() 메소드는 ResultHandler를 구현한 객체를 리턴한다.
}
}
2. @AutoConfigureMockMvc
- @SpringBootTest(webEnvironment=WebEnvironment.MOCK) : 테스트 실행 시에 서블릿 컨테이너 구동안하고 모킹 진행
- @AutoConfigureMockMvc 작성
- 컨트롤러 뿐만 아니라 테스트 대상이 아닌 @Service, @Repository가 붙은 객체들도 모두 메모리에 올린다
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class BoardControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testHello() throws Exception{
mockMvc.perform(get("/hello").param("name","hoho"))
.andExpect(status().isOk())
.andExpect(content().string("Hello : hoho"))
.andDo(print());
}
}
3. 참조 : @WebMvcTest 는 @SpringBootTest와 같이 사용될 수 없다. (각자 서로의 MockMvc를 모킹하기 때문에 충돌)
perform() 메소드를 이용하여 요청 전송 가능. 결과로 ResultAction 객체를 반환.
ResultAction.andExpect() 는 응답결과를 검증할 수있는 메소드 param()은 key,value 형태로 파라미터를 여러개 보낼 수 있다.
MockMvcRequestBuilders - get, post, put, delete
응답상태코드검증 - MockMvcResultMatchers.status() 메소드로 응답 상태 코드를 검증 가능
isOK : 응답상태코드가 정상적인 처리 200 인가
isNotFound : 응답상태코드가 404 Not Found 인가
IsMethodNotAllowed : 응답상태코드가 메소드 불일치에 해당하는 405인가
isInternalServerError : 응답상태코드가 예외 발생에 해당하는 500인가
is(int status) : 몇번 응답상태코드가 설정되있는지 확인한다 is(200). is(404). is(405). is(500)
뷰/리다이렉트 검증 - 컨트롤러가 리턴하는 뷰를 검증시 view메소드 사용
andExpect(view().name("index")) : 컨트롤러가 리턴한 뷰의 이름이 무엇인지 검증
andExpect(redirectedUrl("index")) : 요청처리결과가 리다이렉트 응답이라면 어느 url로 된것인지 확인 가능
모델정보 검증 - 컨트롤러에서 저장한 모델의 정보들을 검증시 MockMvcResultMatchers.model() 메소드 사용
attributeExists(String name) : name에 해당하는 데이터가 model에 포함되어 있는지 검증
attribute(String name, Obejct value) : name에 해당하는 데이터가 value 객체인지 걱증
요청/응답 전체 메시지 확인하기 실제로 생성된 요청과 응답 메세지를 모두 확인하고 싶을 경우
perform()가 리턴하는 ResultActions의 andDo() 메소드 활용
내장톰캣으로 테스트하기
정상적으로 서블릿 컨테이너를 구동하고 테스트 결과를 확인하고 싶을 때 webEnvironment 프로퍼티를 설정한다.
//랜덤한 포트
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
//프로퍼티에서 설정한 포트
@SpringBootTest(webEnvironment=WebEnvironment.DEFINED_PORT)
MOCK : 모킹된 서블릿 컨테이너를 제공하기 때문에 내장 톰캣이 구동되지 않는다. @AutoConfigureMockMvc 어노테이션을 이용하여 MockMvc 객체를 주입받아 테스트에 사용할 수 있다.
RANDOM_PORT : 랜덤한 포트로 내장 톰캣을 구동하여 서블릿 컨테이너를 초기화한다. 정상적인 서블릿 테스트가 가능하다.
DEFINED_PORT : RANDOM_PORT 와 동일하지만 applcation.properties에 설정된 서버 포트를 사용한다.
NONE : 서블릿 기반의 환경 자체를 구성하지 않는다.
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class BoardControllerTest {
@Autowired
private TestRestTemplate restTemplate;
// WebEnvironment.RANDOM_PORT로 사용할 경우 서블릿 컨테이너를 모킹하지 않기 때문에 MockMvc 객체를 목업할 수 없다.
// MockMvc 대신 컨트롤러를 실행해줄 TestRestTemplate 객체를 주입
@Test //내장톰캣으로 테스트하기
public void testGetBoard() {
BoardVO board = restTemplate.getForObject("/getBoard", BoardVO.class);
assertEquals("테스터", board.getWriter());
}
}
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Autowired private TestRestTemplate restTemplate; 로 사용할 경우 서블릿 컨테이너를 모킹하지 않기 때문에 MockMvc 객체를 목업할 수 없다. MockMvc 대신 컨트롤러를 실행해줄 TestRestTemplate 객체를 주입한다.
[서비스 계층을 연동하는 컨트롤러 테스트하기]
컨트롤러는 실제 사용자가 원하는 비즈니스 로직을 처리하기 위해 비즈니스 컴포넌트를 호출
비즈니스 컴포넌트 개발시 클라이언트에 제공할 인터페이스를 만들고 인터페이스를 구현할 구현 클래스 작성 인터페이스 없이 구현 클래스만 만들어 사용하는 경우 유지보수 과정에서 비즈니스 클래스를 다른 클래스로 변경하지 않겠다는 것을 의미.
정상적인 경우 Service 인터페이스 생성 > ServiceImpl 클래스 구현
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class TestBusinessComponent {
@Autowired
private MockMvc mockMvc;
@MockBean
private BoardService boardService; //비즈니스 컴포넌트를 모킹해서 테스트하는 방법
// @MockBean은 특정 타입의 객체를 모킹할 수 있다. 비즈니스 객체(BoardServiceImpl)을 생성하지 않고도 테스트 케이스 작성 가능
@Test
public void testHello() throws Exception {
when(boardService.hello("hoho")).thenReturn("Hello : hoho");
mockMvc.perform(get("/hello").param("name","hoho"))
.andExpect(status().isOk())
.andExpect(content().string("Hello : hoho"))
.andDo(print());
}
}
[스프링 부트 로깅]
로그는 디버깅할때도 필요하지만 실행중인 애플리케이션의 성능을 분석하거나 다양한 용도로 사용 가능
SLF4J(Simple Logging Facade for Java) : 복잡한 로깅 프레임워크들을 쉽게 사용할 수 있도록 도와주는 퍼사드.
퍼사드(facade) : GoF의 디자인 패턴중 하나. 복잡한 서브 시스템을 쉽게 사용할 수 있도록 간단하고 통일된 인터페이스 제공한다.
스프링부트는 별도의 설정이 없으면 기본적으로 로그레벨을 INFO로 처리한다.
로그 레벨은 설정 가능 : logging.level.com.studyboot=warn
만약 로그파일을 파일로 출력하고 싶으면 : logging.file=src/main/resources/board_log.log
[스프링 부트 로깅 수정하기]
스프링부트에서 제공하는 기본 로그 설정을 사용하지 않고 직접 관리하고 싶다면 logback.xml을 직접 추가.
로그백 설정은 크게 어펜더와 로거 설정으로 구성된다.
appender 어디에 출력, logger 어떤 패턴으로 출력 세세한 내용은 직전 포스팅 참조.
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
<!-- 로그 경로 변수 선언 -->
<property name="LOG_DIR" value="./logs" />
<!-- Console appender -->
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ---[%thread] %logger{50} - %msg%n</pattern>
</encoder>
<!-- INFO 레벨 필터 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>ACCEPT</onMismatch><!-- 콘솔에 info로그만 출력 원할시 DENY -->
</filter>
</appender>
<!-- RollingFile Appender -->
<appender name="fileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 파일 경로 -->
<file>src/main/resources/logs/board_log.log</file>
<!-- Rolling 정책 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>src/main/resources/logs/myboard.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 출력 패턴 -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level ---[%thread] %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 애플리케이션에 사용할 로거를 등록 -->
<logger name="com.studyboot" level="warn" additivity="false">
<level value="TRACE" />
<appender-ref ref="fileAppender" /> <!-- 로그 파일 작성 -->
<appender-ref ref="consoleAppender" /><!-- 콘솔 로그 작성 -->
</logger>
<root level="WARN">
<appender-ref ref="consoleAppender" />
</root>
</configuration>
[독립적으로 실행 가능한 JAR]
스프링부트는 독립적으로 실행 가능한 애플리케이션을 빠르게 개발하는 것을 목표로 한다.
그래서 웹 애플리케이션도 war가 아닌 jar파일로도 패키징 하여 사용할 수 있도록 지원한다.
- 스프링 부트 빌드 이해하기
○ 빌드란 ? 완성된 프로젝트에서 소스를 컴파일하고 컴파일된 소스들을 적절한 폴더에 취합하고 JAR나 WAR로 패키징하여 운영서버에 배포하는 일련의 과정.
○ 메이븐은 이런 빌드 작업을 자동으로 처리하도록 지원.
○ targer 폴더 : 현재 프로젝트를 빌드하면 빌드과정에서 생성한 임시폴더와 파일들이 생기고 패키징 결과 파일도 만들어진다.
- 패키징 파일 구조 이해하기
○ pom.xml 설정에 따라 <artifactId> + </version> + jar 형태의 패키징 파일 생성.
○ classes 폴더에는 src/main/java에서 컴파일한 클래스 파일들과 src/main/resources에 작성한 여러 설정 파일들이 모두 포함.
○ lib 폴더에는 maven dependencies에 등록된 모든 라이브러리들이 포함(톰캣 서버 등).
○ jar파일에는 반드시 애플리케이션의 메타데이터가 저장된 매니페스트 파일이 존재해야 함. (위치는 META-INF)
MANIFEST.MF
Manifest-Version: 1.0
Implementation-Title: Chapter03
Implementation-Version: 0.0.1-SNAPSHOT
Start-Class: com.studyboot.Chapter03Application 이 클래스를 시작으로 애플리케이션 실행
Spring-Boot-Classes: BOOT-INF/classes/ 스프링부트에서 컴파일한 클래스들은 classes 하위에 존재
Spring-Boot-Lib: BOOT-INF/lib/ 애플리케이션 수행에 필요한 모든 jar 파일의 위치
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.2.2.RELEASE
Created-By: Maven Archiver 3.4.0
Main-Class: org.springframework.boot.loader.JarLauncher 애플리케이션 실행을 위한 메인클래스
- Runnable JAR 실행하기
○ 이클립스/STS에서 jar파일 마우스 우클릭 > Show in > Terminal
○ java -jar Chaper03-0.0.1-SNAPSHOT.jar (JAR 어플리케이션 실행 명령어)
○ 기본적으로 JAR파일은 또다른 JAR 파일을 포함할 수 없다. 굳이 사용해야 한다면 압축풀고 다시 통합해서 재압축..
○ 스프링부트는 패키징된 JAR파일안에 있는 또다른 JAR 파일을 읽어서 클래스를 로딩하는 유틸리티 클래스를 제공한다.
- org/springframework.boot/loader/jar 폴더안에 JarFile이라는 로더 클래스가 BOOT-INF/lib에 있는 수많은 JAR 파일을 사용하도록 로딩해준다.
- JarLauncher 는 org/springframework.boot/loader 에 위치해있다. MAINFEST.MF 파일로부터 관련 정보를 읽어 main()가 있는 메인클래스를 찾아 실행하는 역할을 한다.
○ Run AS > Maven clean 진행시 패키징에 결과 파일과 패키징 과정에서 생성된 임시파일, 폴더들을 target 폴더에서 깨끗하게 삭제해준다.
[정리]
- 스프링 기반의 컴포넌트는 서버를 구동하지 않은 상태에서도 모킹을 통해 빠른 테스트 가능하다.
- 테스트 객체가 비즈니스 로직을 처리하는 서비스 컴포넌트거나 컨트롤러같은 웹 컴포넌트여도 가능하다.
- SLF4J로 컴포넌트별로 로그 제어 가능. 스프링 부트 설정이나 logback.xml 활용하자.
- 스프링부트로 만든 프로젝트는 jar파일로 패키징 가능하다(웹 프로젝트여도 jar 파일로 패키징 가능).
출처
참고 서적 : 누구나 끝까지 따라 할 수 있는 스프링 부트 퀵스타트
<https://github.com/HomoEfficio/dev-tips/blob/master/SpringMVCTest%EC%97%90%EC%84%9C%EC%9D%98%20%EC%98%88%EC%99%B8%20%ED%85%8C%EC%8A%A4%ED%8A%B8.md>
MVC 구조에서 service와 serviceImpl 참조: <https://multifrontgarden.tistory.com/97>
'Dev > SpringBoot' 카테고리의 다른 글
[Springboot] 스프링부트 화면 개발 (0) | 2020.01.01 |
---|---|
[SpringBoot] 스프링 데이터 JPA (0) | 2019.12.28 |
[SpringBoot] JPA 퀵 스타트 (0) | 2019.12.23 |
[SpringBoot] 스프링부트 자동설정 (0) | 2019.12.13 |
[SpringBoot] 스프링부트 시작하기 (0) | 2019.12.11 |