당니의 개발자 스토리
주문 기능 테스트 본문
주문 기능 테스트
자 이번 시간에는 지금까지 만든 주문 기능을 한번 테스트 코드로 작성해 보겠습니다.

우선 여기 보면

만든 것 중에 중요한 비즈니스가 order 메서드를 통해서 주문을 하는 게 있고,

그 다음에 주문이 잘 취소되는지 확인하는 cancelOrder 메서드가 있고,

그리고 또 하나가 핵심 validation 로직이 있었죠. 주문할 때 만약 재고 수량을 초과하면 오류가 나야 되겠죠. 그게 지금 이 로직에서 잘 동작하는지 확인하는 테스트를 작성을 해보겠습니다.

먼저, OrderServiceTest를 만들어야겠죠. cmd + shift + T 하면 만들어집니다.

여기까지는 자동으로 나와야합니다. 그 전에 다 했던 것들이죠. 그래서 여기까지 세팅을 건 다음에 저희가 이제 상품을 주문하는 테스트를 만들어야 되죠. tdd + tab 해서 빠르게 만들어줄게요.

상품 주문이 잘 되는지 만들어야 되고, 그 다음에 또 만들어야 될 테스트가 주문 취소가 잘 되는지랑 재고 수량 초과하는 테스트를 해야죠. 모두 잘 되는지 순차적으로 테스트를 작성하고 돌려보겠습니다.
참고로 저는 Spring Boot와 Integration 해서 여러분께 JPA가 동작하는 것을 자세하게 설명 드리려고 이렇게 한 건데, 사실 이게 좋은 테스트라고 볼 수는 없어요. 왜냐하면 좋은 테스트는 DB나 이런 Dependency 없이 Spring도 안 엮고 정말 딱 순수하게 그 메서드를 단위 테스트하는 게 좋거든요. 여기서는 이제 그것보다는 JPA나 이런 걸 잘 엮여서 동작했을 때 전체적으로 잘 되는지 보는 목적이 있기 때문에 이렇게 통합으로 해서 테스트를 작성했습니다.
자 먼저 상품 주문이 잘 되는지 이거 테스트 만들어 보죠.

상품 주문이 되는지 보려면 일단 멤버가 있어야 되겠네요. 그리고 아이템도 만들어야 겠죠. 멤버가 아이템을 주문하는 거니까요. Item은 abstract 클래스이기 때문에 구현체로 생성해야합니다.

이렇게 member를 세팅하고, 지금은 테스트기 때문에 단순하게 persist 하면 좋거든요.

그래서 여기서는 그냥 @Autowired로 해서 EntityManager 바로 받아올게요. 테스트 데이터를 딱 그냥 넣는게 목적이어서 em.persist로 멤버를 딱 집어넣어 버리겠습니다.
그 다음 책을 세팅해보겠습니다.

먼저, 보기 쉽게 Book으로 고치고 이렇게 book과 member를 모두 세팅했습니다.
그 다음에 어떻게 합니까? OrderService 에 있는 order 메서드를 테스트 해보면 되겠죠.

@Autowired 해서 OrderService를 받아오고,

2권만 주문할 건데, 2를 cmd +option + v 해서 변수로 따로 뺴겠습니다.

이렇게 해놓았을 때 이제 검증하는 로직을 만들어야 되겠죠. then 절 에서 먼저 한번 찾아볼게요. orderRepository에서 바로 찾으면 되겠죠.

orderRepository도 만들어 놓겠습니다. 그런 다음 orderRepository에서 잘 주문이 됐는지 검증해야 되니까요. 데이터베이스에 있는 거를 조회해 와야되겠죠.

findOne을 쓸 건데, orderId를 가져와야 되니까 OrderService의 order가 식별자 값을 반환하도록 설계를 해놨으니까 이렇게 하면 됩니다.
자 이제 검증을 해보겠습니다.

org.junit의 Assert.assertEqual()을 쓸 건데요.

Assert를 option + enter로 static import 해서 빼주시면 됩니다.

자 이제 "상품 주문시 상태는 ORDER" 여야 되죠. 이게 맞는지 검증해 보겠습니다. 맨 처음에는 message를 쓴 거예요.

그리고 그 다음 두 번쨰 파라미터가 expected 거든요. 그러니까 기대하는 값이에요. 그래서 OrderStatus.ORDER를 써줍니다.

세번 째 파라미터가 actual 이에요. 실제 값이죠. 실제 값은 getOrder.getStatus() 해서 맞춰보면 되겠죠?

이제 잘 되는지 테스트 돌려볼게요. 이 정도 테스트는 바로 돌려보면 됩니다. 그 전에 항상 ./h2.sh 실행시키는 거 잊지 마시구요.

돌려보면 녹색 불이 잘 떴습니다. 저희가 의도한 대로 Order의 상태를 가지고 있다는 거죠.
자 두번째 뭘 한번 해볼까요?

"주문한 상품 종류 수가 정확해야 한다." 라고 하면 하나를 주문했으니까 actual은 1이고, getOrder의 orderItems 사이즈가 1이면 됩니다.

그 다음에 또 "주문 가격은 가격 * 수량이다." 이거는 계산 로직을 검증해 보는 거죠.

그 다음 주문하면 재고가 줄어야 돼요. 원래 우리가 세팅한 재고가 10권이었는데, 주문하고 나면 8권으로 줄어야 되는거죠.
변수를 써서 다 맞추면 더 좋구요.


자 여기서는 이정도만 해보겠습니다.

돌려보면 잘 떴죠. 만약에

8권을 10권으로 바꾸고 돌리면,

이렇게 message가 뜨면서 에러가 날 겁니다.
그 다음에 해볼 거는

재고 수량을 초과해서 주문하면 어떻게 되는지를 해보겠습니다.

이런 예외 테스트가 정말 중요하겠죠. 이 테스트는 이전에 우리가 만들었던

NotEnoughStockException 기억나시죠. 이걸 쓰기로 했단 말이에요.

그니까 재고 수량이 초과가 되면 이 Exception이 터져야 돼요. 지금 테스트를 재고 수량을 초과해서 주문을 딱 해보는 거예요.
이 NotEnoughStockException가 있는 곳이

Item에서 주문을 하면 removeStock이 호출된단 말이에요. 그래서 createOrderItem 하는 시점에 이때 Exception이

재고 수량을 초과해서 주문을 하면 이 라인에서 빵 터져서 올라와야 돼요.
이제 로직을 한번 작성해 보겠습니다.

얘도 멤버를 만들고 뭐하고 데이터를 귀찮게 초기화를 해야 된단 말이에요.

그냥 이거를 기본 세팅을 만들게요. cmd + option + M 해서,

기본 세팅을 만들구요.

그 다음에 책도 기본으로 데이터 세트를 하나 만들겠습니다.

그럼 이걸 이제 갖다 쓸 수 있겠죠? 둘 다 private 메서드니까 맨 밑에 놓을게요.
그러면 상푸주문_재고수량초과()에서 값들을 세팅하면 되겠죠.

그런데 아무래도 변수를 집어넣어서 값을 세팅하는 게 낫겠죠. cmd + option + P 누르면 변수를 파라미터로 꺼낼 수 있습니다.

이런 식으로 해서

파라미터로 꺼내면 됩니다. 이렇게만 해줘도

인텔리제이에서 친절하게 바꿔줍니다.

그리고나서, 지금 재고 수량이 10개인데 orderCount를 11개 하면 Exception이 빵 터지겠죠.

OrderService에서 order를 하는데 orderCount를 딱 집어 넣으면,

여기서 exception이 딱 터져야 돼요.

여기서 예외가 터지기 때문이죠. 그런데 혹시라도 밑에 then 라인을 통과하면 안 되니까

재고 수량 부족 예외가 터져야 한다고 해두시면 테스트를 본 사람도 '재고 수량 예외가 터져가지고 이 라인까지 오면 안 되는구나' 라고 인식할 수가 있죠.
이제 테스트가 정확하게 돌아가는지 돌려보겠습니다. 두 개 다 전부 잘 돌아야 돼요. 왜냐하면 @Transactional로 한 테스트가 끝나고 바로 롤백, 또 한 테스트 끝나면 바로 롤백 이렇게 돼야 하거든요.

자 지금 잘 돌고 있습니다. 저희가 의도한 대로 주문 상품 재고 수량 초과 예외가 딱 터진 거예요.

근데 만약 10권으로 하면 어떻게 될까요? 지금 재고가 10권인데 주문을 10권 해버리면 로직이 정상적으로 돌고,

이 fail 로직으로 내려오겠죠. 그래서 이 fail이 꼭 있어야 되는 겁니다.

자 돌려보시면 테스트가 실패했다고 뜨죠?
그러면 자 이제 다시 11권으로 바꿨을 때 왜 예외가 터지는지 돌아가보면,

OrderService의 order로 가면,

createOrderItem에서 count 값에 orderCount가 들어가면서

removeStock이 호출됩니다. 그런데 여기서

restStock이 0보다 작기 때문에 Exception이 터지는 겁니다. 이 removeStock 하는 비즈니스 로직이 바로 이 Item 엔티티에 들어있죠. 그래서 여기서 Exception이 터져서 쭉 올라오는 거죠.

사실 이제 이렇게 막 통합적으로 테스트 하는 것도 의미가 있는데, 정말 이런 주문 상품 재고 수량 초과에 대해서 조금 더 나은 테스트는 사실

이 removeStock 자체에 대한 단위 테스트가 있는 게 정말 중요해요. 이번 시간에 안 만들긴 할 텐데, 여기서 이 Item 엔티티에 대한

이 비즈니스 메서드 얘 하나만 가지고 테스트하는 단위 테스트가 별도로 존재하는 게 필요하겠죠.

자 그 다음에 이제 주문 취소를 한번 테스트 해보겠습니다.

참고로 테스트에서 Command + Shift + T 누르면 이 테스트랑 테스트 아닌 것을 왔다 갔다 할 수 있습니다. 근데 이게 아마 유사한 이름으로 뜰 거에요.
자 그 다음에 이제 주문 취소를 한번 작성해 보겠습니다.

우선 이렇게 세팅을 했습니다.

그 다음에 하면 여기서는 주문 취소를 테스트 하는 것이기 때문에 given 절에 들어가는 게 맞습니다.
자 그 다음에 실제 내가 테스트 하고 싶은 게 when에 들어가는 거거든요.

이렇게 하면 이제 취소가 돼야 되겠죠.

이제 then 절에서 뭘 검증해야 되냐? 재고가 정상적으로 잘 복구가 됐는지 이런 것들을 다 검증해야 되겠죠.

자 그래서 이렇게 완성하면 됩니다. 이렇게 해서 돌려보면,

녹색불이 떴습니다. 그럼 이제 전체적으로 다시 한번 실행해 볼게요.

저희가 의도한 대로 주문 서비스에 대한 테스트가 전부 성공했습니다. 실제 실무에서는 이것보다 훨씬 더 꼼꼼하게 만들어요. 근데 여러분들께 보여드린 거는 JPA랑 Spring Boot랑 통합적으로 테스트를 이런 식으로 작성할 수 있구나 라는 걸 보여드린 거라고 보면 될 것 같습니다.
사실 정말 좋은 테스트는 단위 테스트를 하는 게 정말 좋아요. 무슨 얘기냐면,

이거 자체에 대해서

뭔가 이런 걸 다 모킹을 하고 테스트 하는게 더 나은 테스트일 확률이 높아요. 막 DB도 안붙고 성능도 더 빠르고. 근데 또 재밌는건 사실 이렇게 도메인 모델 패턴을 쓰면 어떤 장점이 있냐면,

막 Repository 이런 거 관계없이 그냥 Order 엔티티에 대해서 그냥 바로 테스트를 작성을 해버리는 거에요. 왜냐하면 엔티티에 핵심 비즈니스 로직들이 막 만들어지거든요.

Order의 cancel() 이거 자체가 잘 동작하는지, DB 이런 거 상관없이 정말로 딱 단위 테스트로, 딱 그 메서드만 테스트 하도록 작성하는 게 되게 중요합니다. 그걸 의미있게 잘 작성을 하고,

그 다음에 지금 보여드린 이런 테스트들은 그냥 통합적으로 잘 도는지 테스트 해볼 때 좀 의미가 있겠죠. 그러니까 여러 엔티티와 여러 기능들을 섞어서 테스트하는 것일 때 의미가 있겠죠.
자 그러면 이번 시간은 테스트는 여기서 마치겠습니다.
'스프링 > 실전! 스프링 부트와 JPA 활용1' 카테고리의 다른 글
| 홈 화면과 레이아웃 (0) | 2024.05.12 |
|---|---|
| 주문 검색 기능 개발 (0) | 2024.05.11 |
| 주문 서비스 개발 (0) | 2024.05.08 |
| 주문 리포지토리 개발 (0) | 2024.05.04 |
| 주문, 주문상품 엔티티 개발 (0) | 2024.05.03 |