📌 3주차 강의
Spring & JPA 기반 테스트
레이어드 아키텍처와 테스트
- 레이어를 구분하는 이유 ⇒ 관심사의 분리!
- 스프링과 JPA라는 기술 자체가 중요한 것이 아니라,
- 무엇을 어떻게 테스트 할 것인지가 중요하다.
- 통합 테스트
- 여러 모듈이 협력하는 기능을 통합적으로 검증
- 단위 테스트만으로는 기능 전체의 신뢰성을 보장하기 어렵다.
- 풍부한 단위 테스트 & 큰 기능 단위를 검증하는 통합 테스트
Spring / JPA 훑어보기 & 기본 엔티티 설계
- Library vs Framework
- 라이브러리는 내 코드가 주체가 된다.
- 프레임워크에서는 코드를 작성해서 끼워넣는다.
- Spring
- IoC : 객체의 생명주기에 대한 관리를 제 3자가 한다.
- DI : 컨테이너가 주입해준 객체를 사용한다.
- AOP : 코드 상으로 흩어져있는 부가적인 앞뒤로 해주는 작업을 한 군데로 모은다.
- JPA
- ORM
- 객체 지향 패러다임과 관계형 DB 패러다임의 불일치를 해결
- 개발자는 단순 작업을 줄이고, 비즈니스 로직에 집중할 수 있다.
- 인터페이스이고, 여러 구현체 중 Hibernate를 많이 사용한다.
- 스프링 진영에서는 JPA를 한번 더 추상화한 Spring Data JPA 제공
- ORM
Persistence Layer 테스트
- 쿼리가 명확한데 굳이 테스트를 작성해야 할까?
- where 조건이 많아서 쿼리가 길어진다거나, 구현 기술이나 방법이 변경되는 상황에서도 보장할 수 있다.
- 작성한 코드가 제대로 된 쿼리가 날라가는지에 대한 보장
- 미래에 어떤 형태로 변형될지 모르기에 이에 대한 보장
- Persistence Layer
- Data Access 역할
- 비즈니스 가공 로직이 포함되어서는 안된다.
- Repository 테스트는 단위 테스트 성격에 가깝다.
- 데이터베이스에 접근하는 로직만 가지고 있기 때문
- DataJpaTest vs SpringBootTest
- DataJpaTest는 SpringBootTest보다 가볍다.
- JPA 관련 빈들만 주입한다.
- DataJpaTest는 SpringBootTest보다 가볍다.
// 리스트 테스트 시 사용하면 좋다!
assertThat(products).hasSize(2)
.extracting("productNumber", "name", "sellingStatus")
.containsExactlyInAnyOrder(
tuple("001", "아메리카노", SELLING),
tuple("002", "카페라떼", HOLD));
- .extracting()
- 검증하고자 하는 필드 추출
- @ActiveProfiles("test")
- test 프로파일로 적용
Business Layer 테스트
- Business Layer
- 비즈니스 로직을 구현하는 역할
- Persistence Layer와의 상호작용을 통해 비즈니스 로직 전개
- 트랜잭션을 보장
- @DataJpaTest
- 내부에 @Transactional이 존재
- @SpringBootTest + @Transactional
- 이 조합이 가질 수 있는 문제?
- 프로덕션 코드에 있는 트랜잭션 경계가 모호해질 수 있다.
- 실제로 트랜잭션 설정이 되어있지 않았다면?
- 이 조합이 가질 수 있는 문제?
- 간단한 것들도 테스트를 해야할까?
- 미래 시점에 대한 대비를 생각하여 작성하자!
- 수량 차감에 대한 비즈니스 로직
- 수량을 차감할 수 있는 조건이 만족하면,
- 수량을 차감한다. (stock.deductQuantity())
- 수량을 차감할 수 있다면,
- 차감한다.
- 수량에 대한 검증 로직을 두 곳에서 다 해야하나?
- Stock은 비즈니스 로직이 어떻게 되어있는지 모르기 때문에 이에 대한 보장을 해야한다!
재고 감소는 동시성 문제를 고려해야 한다. 동시에 같은 재고 개수를 읽으면 문제가 생기기에 보통 lock을 사용한다.
Presentation Layer 테스트
- Presentation Layer
- 외부 세계의 요청을 가장 먼저 받는 계층
- 파라미터에 대한 최소한의 검증을 수행
- 넘겨온 값들에 대한 검증
- 하위 레이어는 Mocking 처리
- 의존관계를 가짜로 처리하여 테스트 대상에 집중
💡 CQRS란?
Command와 Query를 분리하여 서로 연관이 없게 만든다.
DB에 대한 엔드포인트를 구분하여 Query용 DB와 Write용 DB를 나누어 쓸 수 있다. (Master/Slave)
📌 Day 11 미션 : 단위 테스트 작성하기
Day 11 미션은 Readable Code 강의에서 진행한 프로젝트의 테스트 코드를 작성하는 것이다. 테스트에 대해 참고할 조언은 다음과 같다.
- 테스트가 필요하다고 판단하는 과정부터 시작이다.
- 어떤 클래스, 메서드를 테스트하고 싶은지 명확한 이유와 함께 고민해보자.
- 가장 작은 단위의 메서드부터 테스트를 작성해보자.
https://github.com/hgh1472/readable-code
테스트를 작성해보며 느낀 점은 외부 세계를 분리할수록 테스트를 작성하기 쉽다는 것이다.
한가지 예를 들면, 메서드 중 콘솔로 유저 입력을 받고 입력에 해당하는 도메인으로 바꿔주는 메서드가 존재했다. 이 메서드를 테스트하려면 어떻게 해야할까?
유저 입력을 System.setIn() 메서드를 통해 미리 준비시키고 테스트를 진행해야 했다. 그런데 InputHandler 내부에서 Scanner를 정적 필드로 가지고 있기 때문에, 한 번에 여러 테스트에 대한 입력이 적용되지 않아서 테스트하는 데에 큰 불편함이 존재했다.
즉, 테스트 코드를 작성하면서 프로덕션 코드의 리팩토링까지 생각이 이어질 수 있는 것이다. 테스트 코드는 단순히 기능을 테스트하기 위함 뿐만 아니라, 프로덕션 코드와 상호보완적으로 리팩토링의 포인트가 될 수 있다는 점을 느꼈다!
'인프런 워밍업 스터디' 카테고리의 다른 글
[4주차 발자국] 워밍업 스터디 마무리 (0) | 2025.04.04 |
---|---|
[인프런 워밍업 스터디] 2주차 발자국: Readable Code 적용기 (0) | 2025.03.17 |
[인프런 워밍업 스터디] 추상과 객체 지향의 구체화 (0) | 2025.03.09 |
[미션 Day 4] 코드 리팩토링 및 나만의 언어로 작성한 SOLID 원칙 (0) | 2025.03.07 |
AI 시대의 개발자의 가치 (0) | 2025.03.05 |