[3주차 발자국] 스프링과 테스트 코드
인프런 ‘Readable Code: 읽기 좋은 코드를 작성하는 사고법’을 수강한 후, 작성한 내용입니다.
📌 3주차 강의
Spring & JPA 기반 테스트
레이어드 아키텍처와 테스트
레이어를 구분하는 이유 ⇒ 관심사의 분리!
스프링과 JPA라는 기술 자체가 중요한 것이 아니라,
무엇을 어떻게 테스트 할 것인지가 중요하다.
통합 테스트
여러 모듈이 협력하는 기능을 통합적으로 검증
단위 테스트만으로는 기능 전체의 신뢰성을 보장하기 어렵다.
풍부한 단위 테스트 & 큰 기능 단위를 검증하는 통합 테스트
Spring / JPA 훑어보기 & 기본 엔티티 설계
Library vs Framework
라이브러리는 내 코드가 주체가 된다.
프레임워크에서는 코드를 작성해서 끼워넣는다.
Spring
IoC : 객체의 생명주기에 대한 관리를 제 3자가 한다.
DI : 컨테이너가 주입해준 객체를 사용한다.
AOP : 코드 상으로 흩어져있는 부가적인 앞뒤로 해주는 작업을 한 군데로 모은다.
JPA
ORM
객체 지향 패러다임과 관계형 DB 패러다임의 불일치를 해결
개발자는 단순 작업을 줄이고, 비즈니스 로직에 집중할 수 있다.
인터페이스이고, 여러 구현체 중 Hibernate를 많이 사용한다.
스프링 진영에서는 JPA를 한번 더 추상화한 Spring Data JPA 제공
Persistence Layer 테스트
쿼리가 명확한데 굳이 테스트를 작성해야 할까?
where 조건이 많아서 쿼리가 길어진다거나, 구현 기술이나 방법이 변경되는 상황에서도 보장할 수 있다.
작성한 코드가 제대로 된 쿼리가 날라가는지에 대한 보장
미래에 어떤 형태로 변형될지 모르기에 이에 대한 보장
Persistence Layer
Data Access 역할
비즈니스 가공 로직이 포함되어서는 안된다.
Repository 테스트는 단위 테스트 성격에 가깝다.
데이터베이스에 접근하는 로직만 가지고 있기 때문
DataJpaTest vs SpringBootTest
DataJpaTest는 SpringBootTest보다 가볍다.
JPA 관련 빈들만 주입한다.
// 리스트 테스트 시 사용하면 좋다!
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를 정적 필드로 가지고 있기 때문에, 한 번에 여러 테스트에 대한 입력이 적용되지 않아서 테스트하는 데에 큰 불편함이 존재했다.
즉, 테스트 코드를 작성하면서 프로덕션 코드의 리팩토링까지 생각이 이어질 수 있는 것이다. 테스트 코드는 단순히 기능을 테스트하기 위함 뿐만 아니라, 프로덕션 코드와 상호보완적으로 리팩토링의 포인트가 될 수 있다는 점을 느꼈다!
댓글을 작성해보세요.