Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[우WOO] 1단계 - 점진적인 리펙터링 미션 제출합니다. #2

Merged
merged 41 commits into from Sep 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
92d80c8
docs: 요구사항 작성
hwookim Sep 16, 2020
3ded9ce
feat: 도메인에 lombok 적용
hwookim Sep 17, 2020
3e880f0
test: ProductService.create() 테스트 추가
hwookim Sep 17, 2020
b6b1a44
test: ProductService.create() 예외 테스트 추가
hwookim Sep 17, 2020
01e1185
test: ProductService.list() 테스트 추가
hwookim Sep 17, 2020
4acbfea
test: MenuGroupService.create() 테스트 추가
hwookim Sep 17, 2020
f86eac7
test: MenuGroupService.list() 테스트 추가
hwookim Sep 17, 2020
cad1f58
test: MenuService.create() 테스트 추가
hwookim Sep 17, 2020
7cb13aa
refactor: 각 테스트 별 공통 부분 setUp 메소드에서 처리
hwookim Sep 17, 2020
923442f
test: MenuService.create() 예외 테스트 추가
hwookim Sep 17, 2020
71a2f92
test: MenuService.create() 테스트 검증 부분 추가
hwookim Sep 17, 2020
a270725
test: MenuService.list() 테스트 추가
hwookim Sep 17, 2020
eb05978
test: TableGroupService.create() 테스트 추가
hwookim Sep 17, 2020
89bc43c
docs: 잘못된 테이블 그룹 추가 제한사항 변경
hwookim Sep 17, 2020
e3b0659
test: TableGroupService.create() 예외 테스트 추가
hwookim Sep 17, 2020
33b8045
test: TableGroupService.ungroup() 테스트 추가
hwookim Sep 17, 2020
f3ea60c
test: TableGroupService.ungroup() 예외 테스트 추가
hwookim Sep 17, 2020
8d2365c
test: TableService.create() 테스트 추가
hwookim Sep 17, 2020
b8c2112
test: TableService.list() 테스트 추가
hwookim Sep 17, 2020
5849049
test: TableService.changeEmpty() 테스트 추가
hwookim Sep 17, 2020
aa01a6c
test: TableService.changeEmpty() 예외 테스트 추가
hwookim Sep 17, 2020
0141fd0
test: TableService.changeNumberOfGuests() 테스트 추가
hwookim Sep 17, 2020
224fc0c
test: TableService.changeNumberOfGuests() 예외 테스트 추가
hwookim Sep 17, 2020
15e46d0
test: TableService.changeNumberOfGuests() 예외 테스트 추가
hwookim Sep 17, 2020
9f4f30e
test: TableService.changeEmpty() 예외 테스트 추가
hwookim Sep 17, 2020
25064a7
refactor: 그룹 해제 예외 테스트 테이블 그룹 생성 후 주문 생성하도록 변경
hwookim Sep 17, 2020
8468335
test: OrderService.create() 테스트 추가
hwookim Sep 17, 2020
136ee9c
test: OrderService.create() 예외 테스트 추가
hwookim Sep 17, 2020
1345737
test: OrderService.list() 테스트 추가
hwookim Sep 17, 2020
2867ec5
test: OrderService.changeOrderStatus() 테스트 추가
hwookim Sep 17, 2020
a3a3e8b
test: OrderService.changeOrderStatus() 예외 테스트 추가
hwookim Sep 17, 2020
7d6fc6d
fix: TableGroupService.create() 잘못된 예외 테스트 수정
hwookim Sep 17, 2020
0413a24
fix: TableService.changeNumberOfGuests() 잘못된 예외테스트 수정
hwookim Sep 17, 2020
1cdec90
refactor: 테스트명 변경
hwookim Sep 17, 2020
f5e48e2
refactor: MenuService 테스트에 영향을 끼치는 객체 생성 각 테스트 내부에서 진행
hwookim Sep 22, 2020
6d75d84
refactor: OrderService 테스트에 영향을 끼치는 객체 생성 각 테스트 내부에서 진행
hwookim Sep 22, 2020
bd0e097
refactor: Product 테스트에 영향을 끼치는 객체 생성 각 테스트 내부에서 진행
hwookim Sep 22, 2020
c66c4c3
refactor: TableGroup 테스트에 영향을 끼치는 객체 생성 각 테스트 내부에서 진행
hwookim Sep 22, 2020
ccc6529
refactor: Table 테스트에 영향을 끼치는 객체 생성 각 테스트 내부에서 진행
hwookim Sep 22, 2020
4117be3
refactor: 중복되는 객체 생성 함수 TestObjectFactory로 병합
hwookim Sep 22, 2020
7740675
refactor: service의 메소드명을 따던 객체 이름 변경
hwookim Sep 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
85 changes: 85 additions & 0 deletions README.md
Expand Up @@ -2,6 +2,91 @@

## 요구 사항
Copy link

@kingbbode kingbbode Sep 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

질문 주신 내용은 이쪽에 작성하겠습니다 :)

일단 mock 을 사용한 테스트에는 상당히 많은 논란과 갈리는 의견이 있는 점도 참고해주시면 좋겠네요. 제가 말씀드리는 의견도 그 의견 중 일부입니다. 그리고 저는 mock 사용을 굉장히 엄격하게 사용하고자 하는 쪽인 점도 미리 말씀드려요.

Mockito 의 기능은 우리가 테스트하는 것을 꽤나 편리하게 해줍니다. 내가 테스트하고자 하는 부분을 제외하고는 다른 부분을 내가 기대한 동작을 하도록 만들 수 있기 때문입니다. 그러나 저는 부분별한 Mockito 사용이 설계에 있어 가장 중요한 부분을 훼손시키고 있다고 생각을 하고 있습니다.

질문을 주셨던 내용 중 그 핵심 내용이 들어있기도 하군요.

given(func(any()))의 방식으로 짜게된다면 테스트 대상 메소드의 중간 로직이 바뀌어 any()에 들어갈 값이 바뀐다면 테스트가 더 이상 해당 메소드를 보장하지 못한다고 생각했었습니다.

우리가 테스트하는 것은 설계이며, 이러한 설계의 기본 원칙 중 Interface 를 이야기해보겠습니다. Interface 는 어플리케이션의 비지니스 흐름을 완성시키는 가장 기본적인 단위이며, 우리가 Interface 를 사용하는 것은 Interface 를 통한 설계로 하여금 그 구현이 바뀌어도 Interface 에서 선언한 메서드들의 목적이 바뀌지 않는 이상 정상 동작한다는 것 입니다. 쉽게 말하면 Interface 는 구현을 알지 못하고, 그렇기 때문에 우리는 자유롭게 리펙토링을 할 수 있고, 구현체를 개선 혹은 변경할 수 있는 유연한 시스템을 만들 수 있게 되는 것 입니다.

우리가 하는 테스트는 어떨까요? 우리가 하는 테스트는 Interface 를 테스트하는 것과 같습니다. 내부의 구현이 어찌되었건, 조건, 행위로 하여금 내가 기대한 결과를 테스트하는 것이지요. 이러한 이유로 테스트가 탄탄한 프로젝트를 리펙토링하는 것은 꽤나 즐거운 일이 될 수 있습니다. 테스트가 구현이 어떻게 바뀌어도 그 기대값을 탄탄히 테스트해줄 수 있기 때문입니다.

그럼 다시 위에 작성하신 내용을 살펴보겠습니다. 테스트 대상 메소드의 중간 로직이 바뀌어 any()에 들어갈 값이 바뀐다면 테스트가 더 이상 해당 메소드를 보장하지 못한다고 생각 은 제 기준에는 아주 올바른 생각입니다. 위 말을 다르게 표현한다면 Mockito 를 사용한 Mock 선언으로 우리는 이미 구현이 어떻게 되고 있는지를 알고 있게 되며, 구현을 알고 있는 테스트는 리펙토링이나 구현의 변경으로부터 자유로울 수 없다는 풀이가 되겠지요.

method gogo() {
   a.a();
   b();
   c();
}

그렇다면 위와 같은 테스트에서 b 영역만 테스트할 때는 어떻게 해야하는 것인가? 결국 이 이야기는 private 메서드가 어떻게 테스트되어야 하는가와 같은 이야기가 됩니다. 이 때 우리가 b 라는 private 영역에 대해서 별도의 테스트가 필요하다고 느꼈다면, 그것은 사실 pirvate 으로 작성되야할 것이 아닌 별도 Interface 혹은 class 로 추출되어 의미있는 응집을 구성했어야 할 가능성이 굉장히 큽니다.

이 이야기는 약간 번외이기는 합니다만, 응집의 중심이 되는 단위를 테스트하는 것을 우리는 흔히 유닛 테스트라고 말하며, 이러한 여러가지 응집의 단위를 혼합하여 하는 테스트를 통합 테스트라고 말합니다. b() 를 테스트하는 것은 유닛테스트, 위의 gogo() 를 테스트하는 것이 통합테스트가 되겠지요. 이러한 응집의 단위는 설계의 구성마다 다르고 대부분 대규모 어플리케이션은 작은 응집들의 깊이 상당히 깊으므로, 사실 어떤 것을 유닛테스트다 통합테스트다 라고 명확히 말할 수 있는 시스템은 많지 않을 것 입니다. 그렇기에 설계자의 관점에서 유닛테스트 를 설계 단위에서 진행하고, 반드시 통합테스트도 이루어져야 완벽히 커버하는 테스트가 될 것 입니다. 일반적 스프링 어플리케이션에서 응집의 단위를 상당히 크게 잡는다면 Controller 테스트를 통합테스트, Service 테스트를 유닛테스트라고 말할 수 있기도 합니다만, 나누는 큰 의미는 없을 것 입니다.

본론으로 돌아와서 구현을 알게 되는 테스트를 원하지 않기 때문에 저는 Mockito 혹은 Mock 사용을 굉장히 엄격하게 사용합니다. 여기에서 엄격하게 사용한다는 것은 사용을 안한다는 의미는 아니겠지요 ㅎㅎ 그럼 저는 어떤 경우에 Mockito 를 활용하고 있는지 말씀드리겠습니다.

Mockito 에 대한 제 첫번째 기준은 제어할 수 없는 영역에서 사용하는 것 입니다.
제가 2019 스프링캠프에서 연사발표했던 장표를 첨부합니다. (https://www.slideshare.net/ssuser59a869/ksug-2019)
45페이지의 내용인데요. 저는 • Random, Shuffle, LocalDate.now() • 외부 세계 • HTTP • 외부 저장소 를 제어할 수 없는 영역이라고 말합니다. 랜덤의 성격을 띄고 있는 함수나, LocalDate.now() 처럼 계속 흘러가는 시간의 순간, 그리고 외부 요청에 대한 영역, 그리고 외부에 존재하여 내가 제어를 할 수 없는 외부 서버, 외부 저장소 등이 입니다. 이러한 영역에서는 테스트가 가능하도록 설계를 변경하거나, Mockito를 사용하고 있습니다.

두번째 기준은 로컬 구성이 가능한 저장소는 사용하지 않는다는 것 입니다. 저장소의 종류를 떠나서도 저장소의 구현체는 굉장히 큽니다. 그리고 대부분의 저장소는 각자 갖는 제약조건이 무수히 많습니다.(따로 설명안해도 되겠지요?) 그렇기때문에 저장소를 Mock 처리하는 것은 굉장히 많은 것을 포기하여, 거의 신뢰할 수 없는 테스트가 됩니다. 저장소를 테스트할 수 있게 h2, embedded redis 등 다양한 로컬 셋업환경이 있습니다. 이러한 환경을 설치하는 것이 비용도 굉장히 작기 때문에 사용하지 않을 이유가 없습니다. 현업에서는 h2 도 신뢰하지 못하여 운영에서 사용하는 mysql 버전과 동일한 버전으로 테스트 환경에서 docker 를 사용하여 테스트를 하는 곳도 있을 정도 입니다 :)

세번째 기준은 레이어링의 경계에서 사용하는 것 입니다.
대부분의 대규모 어플리케이션은 (아마도?) 단방향으로 흐르는 레이어 계층을 설계를 갖습니다. 예를 들면 응용 계층 - 도메인 계층 - 인프라스트럭처 계층 와 같이 말이죠. 이 레이어는 시스템의 설계마다 전부 다릅니다. 제가 현재 담당하는 시스템은 응용 계층 - 어플리케이션 비지니스 계층 - 도메인 계층 - 데이터 모델 계층 - 인프라스트럭처 계층 과 같이 복잡하게 레이어가 펼쳐져 있기도 합니다.
이러한 각 계층을 테스트할 때 저는 Mockito를 사용합니다. 이건 어플리케이션의 규모가 대규모이기 때문이기도 한데요. 상당히 깊은 depth 의 레이어를 갖는 설계에서 응용 계층 의 테스트를 작성하기 위해 하위 계층들의 테스트 셋업이 지나치게 방대해질 수 있습니다. 그렇기 때문에 이러한 레이어링을 갖는 설계에선는 레이어의 경계 영역에서 Mockito 를 사용하고 있습니다.

설명을 장황하게 해보았지만, 이해하기 상당히 어려운 내용일 것 같네요. 정답은 사실 없습니다. :) 본인이 생각하는 좋은 테스트에 대해 앞으로도 고민이 필요할 것 입니다.ㅎㅎ

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정성스러운 답변 감사드립니다! 🥰
확실히 mock을 이용한 테스트는 메소드의 구현 내용을 다 알고 있다는 전제하에에 사용하니 TDD 기반의 테스트 코드에서는 작성할 일이 거의 없겠네요. 구현을 확실히 알고 있는 상태에서만 제한적으로 사용해야 한다는 느낌이 많이 듭니다.

첨부해주신 장표 겸해서 유튜브에 발표하신 게 올라와 있길래 재밌게 봤습니다! Test Double 외에도 많은 도움이 되었습니다.
발표를 듣고 나니 테스트는 구현보다는 인터페이스에 대한 검증이 필요하다는 말이 더 와닿네요.
아직 완벽하게 이해하진 못했지만, 앞으로 테스트를 작성할 때 좋은 지표가 될 수 있을 것 같아요. 다시 한번 좋은 리뷰감사드립니다! 😄


### 상품(Product)
- 연관 관계
- 1:n 메뉴 상품
- 상품 추가
- 이름, 가격 필요
- 0 이상의 가격 필요
- 전체 상품 조회

### 메뉴 그룹(MenuGroup)
- 연관 관계
- 1:n 메뉴
- 메뉴 그룹 추가
- 이름 필요
- 전체 메뉴 그룹 조회

### 메뉴(Menu)
- 연관 관계
- n:1 메뉴 그룹
- 1:n 메뉴 상품
- 메뉴 추가
- 이름, 가격, 메뉴 그룹, 메뉴 상품 필요
- 0원 이상의 가격 필요
- 저장된 메뉴 그룹 필요
- 메뉴의 가격은 모든 메뉴 상품의 가격 총합보다 클 수 없음
- 메뉴 상품 데이터 추가
- 전체 메뉴 조회

### 메뉴 상품(MenuProduct)
- 연관 관계
- n:1 메뉴
- n:1 상품
- 메뉴 상품 추가
- 메뉴, 상품, 수량 필요

### 테이블 그룹(TableGroup)
- 연관 관계
- 1:n 테이블
- 테이블 그룹 추가
- 테이블 2개 이상 필요
- 저장되지 않은 테이블 추가 불가
- 빈 테이블만 추가 가능
- 이미 테이블 그룹이 있는 테이블 추가 불가
- 모든 테이블 주문 가능하게 변경
- 테이블 그룹 해제
- 조리, 식사 상태의 테이블 포함 시 해제 불가
- 모든 테이블 주문 가능하게 변경

### 테이블(OrderTable)
- 연관 관계
- n:1 테이블 그룹
- 1:n 주문
- 테이블 추가
- 손님 수, 주문 등록 불가 여부
- 주문 등록 불가 = 빈 테이블
- 손님 수는 필수 사항이 아님
- 전체 테이블 조회
- 테이블 상태 변경
- 주문 등록 불가 여부 변경
- 테이블 그룹에 등록된 경우 변경 불가
- 조리, 식사 상태 시 변경 불가
- 손님 수 변경
- 0명 이상
- 빈 테이블은 변경 불가

### 주문(Order)
- 연관 관계
- n:1 테이블
- 1:n 주문 항목
- 주문 추가
- 테이블, 주문 항목 필요
- 1개 이상의 주문 항목 필요
- 저장되지 않은 메뉴 추가 불가
- 빈 테이블 추가 불가
- 조리 상태로 변경
- 주문 항목 데이터 추가
- 전체 주문 조회
- 주문 상태 변경
- 계산 완료 시 변경 불가

### 주문 항목(OrderLineItem)
- 연관 관계
- n:1 주문
- 주문 항목 추가
- 주문, 메뉴, 수량 필요

## 용어 사전

| 한글명 | 영문명 | 설명 |
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Expand Up @@ -17,6 +17,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.flywaydb:flyway-core'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/kitchenpos/domain/Menu.java
Expand Up @@ -2,8 +2,15 @@

import java.math.BigDecimal;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Menu {

private Long id;
private String name;
private BigDecimal price;
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/kitchenpos/domain/MenuGroup.java
@@ -1,6 +1,14 @@
package kitchenpos.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MenuGroup {

private Long id;
private String name;

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/kitchenpos/domain/MenuProduct.java
@@ -1,6 +1,14 @@
package kitchenpos.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MenuProduct {

private Long seq;
private Long menuId;
private Long productId;
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/kitchenpos/domain/Order.java
Expand Up @@ -2,8 +2,15 @@

import java.time.LocalDateTime;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Order {

private Long id;
private Long orderTableId;
private String orderStatus;
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/kitchenpos/domain/OrderLineItem.java
@@ -1,6 +1,14 @@
package kitchenpos.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OrderLineItem {

private Long seq;
private Long orderId;
private Long menuId;
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/kitchenpos/domain/OrderTable.java
@@ -1,6 +1,14 @@
package kitchenpos.domain;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OrderTable {

private Long id;
private Long tableGroupId;
private int numberOfGuests;
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/kitchenpos/domain/Product.java
@@ -1,8 +1,15 @@
package kitchenpos.domain;

import java.math.BigDecimal;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Product {

private Long id;
private String name;
private BigDecimal price;
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/kitchenpos/domain/TableGroup.java
Expand Up @@ -2,8 +2,15 @@

import java.time.LocalDateTime;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TableGroup {

private Long id;
private LocalDateTime createdDate;
private List<OrderTable> orderTables;
Expand Down
84 changes: 84 additions & 0 deletions src/test/java/kitchenpos/TestObjectFactory.java
@@ -0,0 +1,84 @@
package kitchenpos;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import kitchenpos.domain.Menu;
import kitchenpos.domain.MenuGroup;
import kitchenpos.domain.MenuProduct;
import kitchenpos.domain.Order;
import kitchenpos.domain.OrderLineItem;
import kitchenpos.domain.OrderStatus;
import kitchenpos.domain.OrderTable;
import kitchenpos.domain.Product;
import kitchenpos.domain.TableGroup;

public class TestObjectFactory {

public static Order createOrder(OrderTable table, List<OrderLineItem> orderLineItems) {
return Order.builder()
.orderTableId(table.getId())
.orderStatus(OrderStatus.COOKING.name())
.orderLineItems(orderLineItems)
.orderedTime(LocalDateTime.now())
.build();
}

public static Order createOrder(OrderTable table) {
return Order.builder()
.orderTableId(table.getId())
.orderStatus(OrderStatus.COOKING.name())
.orderedTime(LocalDateTime.now())
.build();
}

public static OrderLineItem createOrderLineItem(Menu menu) {
return OrderLineItem.builder()
.menuId(menu.getId())
.quantity(2)
.build();
}

public static Menu createMenu(int price, MenuGroup menuGroup, List<MenuProduct> menuProducts) {
return Menu.builder()
.name("강정치킨")
.price(BigDecimal.valueOf(price))
.menuGroupId(menuGroup.getId())
.menuProducts(menuProducts)
.build();
}

public static MenuGroup createMenuGroup() {
return MenuGroup.builder()
.name("강정메뉴")
.build();
}

public static MenuProduct createMenuProduct(Product product) {
return MenuProduct.builder()
.productId(product.getId())
.quantity(1)
.build();
}

public static Product createProduct(int price) {
return Product.builder()
.name("강정치킨")
.price(BigDecimal.valueOf(price))
.build();
}

public static TableGroup createTableGroup(List<OrderTable> tables) {
return TableGroup.builder()
.orderTables(tables)
.createdDate(LocalDateTime.now())
.build();
}

public static OrderTable createTable(boolean empty) {
return OrderTable.builder()
.numberOfGuests(0)
.empty(empty)
.build();
}
}
47 changes: 47 additions & 0 deletions src/test/java/kitchenpos/application/MenuGroupServiceTest.java
@@ -0,0 +1,47 @@
package kitchenpos.application;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;
import kitchenpos.domain.MenuGroup;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;

@SpringBootTest
@Sql("/deleteAll.sql")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 간 서로 영향을 주지 않기 위해 위 작업을 하고 있군요 👍
deleteAll 작업은 테스트 전보다 테스트 후에 진행되는게 올바른 테스트 사이클이 될 것 같습니다 :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sql을 통해 데이터를 삭제하는 이유는 말씀하신 테스트 격리 뿐만 아니라 기본 데이터의 삭제 효과도 겸하기 위해서였습니다.
만일 테스트 후에 데이터 정리 작업을 하게 된다면, 기본 데이터의 관리는 어떻게 해야할까요?
조회 테스트에 기본 데이터를 고려해 테스트하고, @Dirtiescontext를 이용해 기본 데이터를 유지한 채로 테스트를 진행해야 하나요?

테스트 전에 데이터를 정리하는 것과 후에 관리하는 것이 올바른 테스트 사이클에 어떤 영향을 주게 되는지도 궁금합니다.🤔

Copy link

@kingbbode kingbbode Sep 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용하신 어노테이션에 지원하는 기능이 있는 것 같은데요! 확인 해보시면 좋겠네요 :)

테스트 전에 데이터를 정리하는 것과 후에 관리하는 것이 올바른 테스트 사이클에 어떤 영향을 주게 되는지도 궁금합니다.

결론부터 이야기하자면, 내가 싼 X은 내가 치운다 와 같은 개념입니다 ^^;

첫번째로 모두 똑같이 위 SQL 을 사용할 수 있는 환경이라면 우려가 없을 수도 있겠지만, 복잡한 비지니스를 가지면 가질수록 BEFORE 에서 더 다양한 셋업 케이스들이 생길거에요. 우리는 테스트는 서로 상호독립적이라는 전재가 있기 때문에 테스트의 순서는 고려하지 않습니다. 100개의 테스트 클래스가 있는 프로젝트에서 새로운 테스트를 작성할 때 BEFORE 에서 데이터를 클랜징한다면, 이 전 테스트에서 어떤 동작을 했는지 알아야겠지요? 이건 이미 상호독립적인 테스트라는 전재가 깨진 상태가 됩니다. 데이터를 사용한 테스트에서 지워준다면 다른 테스트에서 이전 테스트를 신경쓸 일이 없어지겠지요.

두번째로 항상 저장소를 100% 클랜징해주면 문제가 없다고 생각할 수도 있습니다. 맞는 이야기이지만, 최적화 측면에서 본다면 비효율적인 테스트가 될 확률이 높을거에요. 테스트는 빠른 피드백을 줄 수 있어야 합니다. 테스트가 한사이클 도는데 몇시간이 걸려버린다면 CI를 하는 의미가 없겠지요? (실제로 현업에서도 테스트 시간을 단축시키기 위한 리펙토링을 계속 진행하고 있습니다.)

class MenuGroupServiceTest {

@Autowired
private MenuGroupService menuGroupService;
private MenuGroup menuGroup;

@BeforeEach
void setUp() {
menuGroup = MenuGroup.builder()
.name("반반메뉴")
.build();
}

@DisplayName("메뉴 그룹 추가")
@Test
void create() {
MenuGroup savedMenuGroup = menuGroupService.create(menuGroup);

assertThat(savedMenuGroup.getId()).isNotNull();
}

@DisplayName("메뉴 그룹 전체 조회")
@Test
void list() {
menuGroupService.create(menuGroup);
menuGroupService.create(menuGroup);

List<MenuGroup> list = menuGroupService.list();

assertThat(list).hasSize(2);
}
}