728x90
단위 테스트
- 단위 테스트는 프로젝트에 필요한 모든 기능에 대한 테스트를 각각 진행하는 것을 의미
- 일반적으로 스프링부트에서는
org.springframework.boot:spring-boot-starter-test
디펜던시만으로 의존성을 모두 가질 수 있다.
F.I.R.S.T 원칙
Fast
: 테스트 코드의 실행은 빠르게 진행되어야함Independent
: 독립적인 테스트가 가능해야함Repeatable
: 테스트는 매번 같은 결과를 만들어야 함Self-Validating
: 테스트는 그 자체로 실행하여 결과를 확인할 수 있어야 함Timely
: 단위 테스트는 비즈니스 코드가 완성되기 전에 구성하고 테스트가 가능해야 한다.- 코드가 완성되기 전부터 테스트가 따라와야 한다는 TDD의 원칙을 담고 있음.
@WebMvcTest(Class명.class)
- ()에 작성된 클래스만 실제로 로드하여 테스트를 진행
- 매개변수를 지정해주지 않으면
@Controller
,@RestController
,@RestControllerAdvice
등 컨트롤러와 연관된 Bean이 모두 로드됨 - 스프링의 모든 Bean을 로드하는
@SpringBootTest
대신 컨트롤러 관련 코드만 테스트할 경우 사용
@WebMvcTest(ArticleRestController.class)
class ArticleRestControllerTest {
}
@Autowired about MockBean
- Controller의 API를 테스트하는 용도인 MockMvc객체를 주입받음
- perform() 메소드를 활용하여 컨트롤러의 동작을 확인할 수 있음
.andExpect()
,.andDo()
,.andReturn()
등의 메소드를 같이 활용함.
@WebMvcTest(ArticleRestController.class)
class ArticleRestControllerTest {
@Autowired
MockMvc mockMvc;
}
@MockBean
- Spring은 객체 관리를 직접하지 않고
Dependency Injection(DI)
를 통해 관리하기 때문에 MockMvc객체 사용하기 쉬움 - 테스트할 클래스에서 주입 받고 있는 객체에 대해 가짜 객체를 생성해주는 어노테이션
- 해당 객체는 실제 행위를 하지 않음
given()
메소드를 활용하여 가짜 객체의 동작에 대해 정의하여 사용할 수 있음.- Controller에서 잡고 있는 Bean 객체에 대해 Mock 형태의 객체를 생성해준다.
@WebMvcTest(ArticleRestController.class)
class ArticleRestControllerTest {
@MockBean
ArticleService articleService;
}
given
- given : Mock 객체가 특정 상황에서 해야하는 행위를 정의하는 메소드
Mockito
라이브러리에서 사용.
given(articleService.getArticleInfo(1L))
.willReturn(articleResponse);
// 행위 정의(아티클서비스.아티클 정보를 불러온다(1L))
// .정보를 불러오면(아티클 리스폰스)를 반환해야한다.
MockMVC
perform()
: RestAPI를 테스트할 수 있는 환경 제공get()
:MockHttpServletRequestBuilder
에 있는get()
메소드.- 실제 어떤 통신을 할 것인지 정의하는 메소드
andExpect()
: 기대하는 값이 나왔는지 체크해볼 수 있는 메소드 (기대 값을 수행)- builder 구조를 가지고 있음.
jsonPath()
: http request를 날리면 json형태의 body값을 받기 때문에 받은 값의 존재 확인- $를 사용하여 json의 key값을 조회 가능
- json path의 depth가 깊어지면 .을 추가하여 추가 탐색이 가능
- (ex : $.hospitalId.hospitalIdName)
- jsonPath git hub repository
- https://github.com/json-path/JsonPath
andDo()
: 요청에 대한 저리verify()
: 해당 객체의 메소드가 실행되었는지 체크
mockMvc.perform(get(url))
.andExpect(status().isOk())
.andExpect(jsonPath("$.title").exists())
.andExpect(jsonPath("$.content").exists())
.andExpect(jsonPath("$.content").value("hi"))
.andDo(print());
verify(articleService).getArticleInfo(articleId);
/*
mockMvc.RestAPI TEST 환경제공(get으로 통신 정의(url))
.기대값 체크(status().응답 상태 코드가 200인가?(정상 처리)()) // 빌더 구조
.기대값 체크(json 확인(title).다음과 같은 키가 존재하는지())
.기대값 체크(json 확인(Content).다음과 같은 키가 존재하는지())
.기대값 체크(json 확인(Content).다음과 같은 키의 값이 다음과 일치하는지("hi"))
.실행 결과 처리(출력());
메소드 확인(아티클 서비스).아티클 정보를 받아오는 메서드(아티클 ID);
*/
objectMapper
ObjectMapper
클래스의writeValueAsString
및writeValueAsBytes
메서드는 Java 개체에서 Json을 생성하고, 생성된 Json을 문자열 또는 바이트 배열로 반환
ObjectMapper objectMapper = new ObjectMapper();
Car car = new Car("yellow", "renault");
String carAsString = objectMapper.writeValueAsString(car);
// {"color":"yellow","type":"renault"}
Gson을 사용한 json 문자열 반환, ObjectMapper를 사용한 json 문자열 반환
Gson gson = new Gson();
String content = gson.toJson(ArticleDto)
// 아래 코드로 json 형태 변경 작업을 대체할 수 있음.
String json = new ObjectMapper().writeValueAsString(ArticleDto);
예시
void add() throws Exception {
ArticleAddRequest dto = new ArticleAddRequest("hi", "welcome");
given(articleService.addArticle(any())).willReturn(new ArticleAddResponse(2L, dto.getTitle(), dto.getContent()));
//objectMapper
String url = "/api/v1/articles/add";
mockMvc.perform(post(url)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(new ArticleAddRequest("hi", "welcome")))
).andExpect(status().isOk())
.andExpect(jsonPath("$.id").exists())
.andExpect(jsonPath("$.title").exists())
.andExpect(jsonPath("$.title").value("hi"))
.andExpect(jsonPath("$.content").exists())
.andDo(print());
}
/*
행위 정의(아티클서비스.아티클 추가(any()))
.정보를 불러오면(새로운 아티클 추가 리스폰스 객체 생성)를 반환해야한다.
mockMvc.RestAPI TEST 환경제공(통신 정의(url))
.어떠한 컨텐트 타입을 지정할 것인가(JSON형태 사용)
.어떠한 body값을 넘겨줄 것인지(오브젝트매퍼.Java 개체에서 Json을 생성하고, 바이트 배열로 반환(아티스트 추가 요청 객체))
.기대값 체크(status().응답 상태 코드가 200인가?(정상 처리)()) // 빌더 구조
.기대값 체크(json 확인(it).다음과 같은 키가 존재하는지())
.기대값 체크(json 확인(title).다음과 같은 키가 존재하는지())
.기대값 체크(json 확인(title).다음과 같은 키의 값이 다음과 일치하는지("hi"))
.기대값 체크(json 확인(Content).다음과 같은 키가 존재하는지())
.실행 결과 처리(출력());
*/
Object Mapper 소개
new 객체 생성하여 given에 대입했을 때 테스트 fail이 발생한 이유
- DTO에
@Data
어노테이션이 부재하였기 때문- •
@Getter
,@Setter
,@RequiredArgsConstructor
,@ToString
,@EqualsAndHashCode
를 한번에 설정해주는 어노테이션이다.
- •
@EqualsAndHashCode
란?equals
,hashCode
를 자동 생성한다.equals
: 두 객체의 내용이 같은지, 동등성(equality) 를 비교하는 연산자hashCode
: 두 객체가 같은 객체인지, 동일성(identity) 를 비교하는 연산자
any()
사용 했을 때 테스트가 통과한 이유
- 모든 매개 변수에 대하여 같은 행동을 하는 Mock 객체를 만들 수 있음.
- Matches anything, including nulls and varargs.
- We are stubbing bool() method to return “true” for any string, integer and object arguments. All the below assertions will pass in this case:
- Mockito Argument Matchers - any(), eq() | DigitalOcean
Request, Response DTO 나눈 이유
- 클라이언트에게 요청하는 정보와, 요청 받아 응답하는 정보가 다를 수 있다.
- ex) 로그인 시 클라이언트에게 ID, Password를 요청 → 응답할 땐 name, user_no와 같은 값을 제공.
- password와 같은 보안적인 사항을 return하여 웹에 띄워서는 안되기 때문.
- 필요한 정보만 주고 받기 위함.
- ex) 로그인 시 클라이언트에게 ID, Password를 요청 → 응답할 땐 name, user_no와 같은 값을 제공.
- 같은 필드인데, 상황에 따라 값이 있고 없는 모호한 경우가 발생한다면 유지 보수가 어렵다.
- API 응답 스펙이 정해지면 그 필드에 값은 항상 같은 원칙으로 반환되도록 명확하게 설계하는 것이 중요
- 클래스를 여러게 만들더라도, 코드가 중복되는 것 처럼 보일지라도, 명확한 것이 훨씬 더 나은 선택
- API 응답 스펙이 정해지면 그 필드에 값은 항상 같은 원칙으로 반환되도록 명확하게 설계하는 것이 중요
활용편에서 dto로 리턴해야 하는 중요성을 배웠었는데요. dto클래스를 꼭 요청에 필요한 필드로만 구성해야 하나요? 어떤곳은 username만, 어떤곳은 age 또는 2개다 이렇게 하나의 엔티티에서 여러개
www.inflearn.com](https://www.inflearn.com/questions/72423)
추가) DTO의 사용 범위에 대해
- Layered Architecture 상의 계층들에서는 DTO를 어떻게 사용해야 하지?
- [DTO의 사용 범위에 대하여
1. DTO란? DTO(Data Transfer Object)란 계층간 데이터 교환을 위해 사용하는 객체(Java Beans)입니다. 간략하게 DTO의 구체적인 용례 및 필요성을 MVC 패턴을 통해 알아볼까요? 🚀 1.1. MVC 패턴 MVC…
tecoble.techcourse.co.kr](https://tecoble.techcourse.co.kr/post/2021-04-25-dto-layer-scope/)
Layered Architecture
- 각 계층은 어플리케이션 내에서의 특정 역할과 관심사(화면 표시, 비즈니스 로직 수행, DB 작업 등)별로 구분된다.
- Layered Architecture 의 강력한 기능인 '관심사의 분리 (Separation of Concern)' 를 의미
- 특정 계층의 구성요소는 해당 계층에 관련된 기능만 수행
- 높은 유지보수성과 쉬운 테스트라는 장점이 존재
계층간 역할
- Presentation Layer
- 사용자가 데이터를 전달하기 위해 화면에 정보를 표시하는 것이 주 관심사
- 비즈니스 로직이 어떻게 수행되는지 알 필요가 없다.
- 대표적인 구성요소는 View와 Controller
- Business Layer
- 비즈니스 로직을 수행하는 것이 주 관심사
- 화면에 데이터를 출력하는 방법이나 혹은 데이터를 어디서, 어떻게 가져오는지에 대한 내용은 모름
- Persistence Layer에서 데이터를 가져와 비즈니스 로직을 수행하고 그 결과를 Presentation Layer 로 전달
- 대표적인 구성요소는 Service와 Domain Model
- Persistence Layer
- 어플리케이션의 영속성을 구현하기 위해, 데이터 출처와 그 데이터를 가져오고 다루는 것이 주 관심사
- 대표적인 구성요소는 Repository, DAO 등
- Database Layer
- MySQL, MariaDB, PostgreSQL, MongoDB 등 데이터베이스가 위치한 계층
Layers of isolation
Presentation Layer 에서 Database Layer로 직접 연결해서 정보를 가져오면 발생하는 문제
- SQL에 대한 변경사항이 Presentation Layer에 직접 영향을 미친다.
- 과도한 의존성이 발생
- 애플리케이션의 변경을 매우 어렵게 만든다.
Layered Architecture 에서 각 레이어는 격리되어 있다. 각 레이어가 다른 레이어와 독립적이므로 특정 레이어는 다른 레이어의 내부 동작을 모르게 된다. 즉 각 계층은 캡슐화되어 있고, 단일 책임을 갖는다. 따라서 특정 레이어는 다른 레이어에 영향을 주지 않고 변경될 수 있다.
싱크홀 안티 패턴 주의
- 특정 레이어가 아무런 로직도 수행하지 않고 들어온 요청을 그대로 다시 하위 레이어로 내보내는 경우를 의미
- 필요한 리소스 낭비를 초래
- 전체 흐름 중에서 약 20%가 싱크홀이라면 그럭저럭 나쁘지 않은 수준
참조
테스트 코드 (Junit, TDD)
@Data, @EqualsAndHashCode
Mockito ArgumentMatchers
https://www.baeldung.com/mockito-argument-matchers
Jackson ObjectMapper 정리
https://www.baeldung.com/jackson-object-mapper-tutorial
계층간 역할(Layered Architecture)
반응형
'회고록 > Archive' 카테고리의 다른 글
[Spring Security & JWT] 2. JWT(Json Web Token) 발급 (0) | 2022.12.06 |
---|---|
[Spring Security & JWT] 1. Security 적용 (0) | 2022.12.06 |
retrospect: 멋쟁이 사자처럼 백앤드 스쿨 2022.11.01 회고 (1) | 2022.12.02 |
retrospect: 멋쟁이 사자처럼 백앤드 스쿨 2022.10.18 회고 (0) | 2022.12.02 |
retrospect: 멋쟁이 사자처럼 백앤드 스쿨 2022.09.30 회고 (0) | 2022.12.02 |