[인프런 워밍업 스터디 클럽 2기 백엔드] 4주차 + 회고
해당 글은 인프런 박우빈 강사님의 「Practical Testing: 실용적인 테스트 가이드」을 바탕으로 작성하였습니다.
강의 요약
Presentation Layer
외부 세계의 요청을 가장 먼저 받는 계층
사용자 입력 값, 프론트엔드에서 주는 값, 요청 등...
주요 로직은 없지만, 최소한의 유효성 검증을 수행한다.
레이어드 아키텍쳐의 단점
도메인에 강하게 의존
도메인에 레포지토리를 위한 어노테이션을 필연적으로 붙여야 하는 문제 발생
⇒ 대안: 헥사고날 아키텍쳐
Mockito로 stubbing 하기
가짜 객체를 만들어 행위를 지정하고 테스트하는 방법 (자세한 내용은 미션 6 참조)
Test Double
dummy: 아무것도 하지 않는 깡통 객체
fake: 단순 형태로, 동일 기능을 수행. 프로덕션에서 쓰기에는 부족한 객체
stub: 테스트에서 요청한 것에 대해 기록하여 보여줄 수 있는 객체. 정의하지 않은 요청에는 무응답
spy: stub이면서, 호출된 내용을 기록하여 보여줄 수 있는 객체. 일부는 실제 객체처럼 동작.
mock: 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체
stub vs mock
stub은 상태를 검증
mock은 행위를 검증
더 나은 테스트 작성하기
한 문단에는 한 가지 주제만!
'테스트 코드 = 문서' 라는 것을 항상 인지해야 한다.
완벽한 제어
시간, 랜덤 등은 외부에서 주입받도록 리팩토링 하기. 강의 예제의 가게의 영업시간을 테스트했던 것을 기억하자.
외부 세계와 소통해야 하는 것은 mocking을 이용한다.
독립성 보장하기
한 테스트 메서드에는 한 가지 기능만 테스트하기
given에서는 가급적 순수 생성자 또는 빌더를 통해 생성하기
검증이 있는 팩토리 메서드는 사용 시 예외 발생 가능성이 있다.
테스트는 순서 보장이 안 되어 있다.
Fixture 구성
@BeforeEach, @BeforeAll
중복 코드를 줄일 수 있지만 테스트 간의 결합이 생겨버린다.
신중한 사용이 필요!
Fixture 클렌징
deleteAll vs deleteAllInBatch
deleteAllInBatch(): 중간 테이블에 외래키 등으로 인한 오류 발생 때문에 데이터 삭제 순서를 고려해야 함. → 코치님의 추천 메서드
deleteAll(): 순서 고려 필요가 없고 연관 테이블을 자동적으로 같이 지워주지만, 삭제 쿼리가 복잡하게 나간다! 전체 테이블을 조회하고 건별로 삭제하는 방식이기에 불필요한 쿼리가 늘어난다.
@ParameterizedTest
케이스 확장할 때 좋은 방식!
재고 타입 테스트할 때를 기억하기!
@CsvSource로 제공된 데이터를 @ParameterizedTest가 붙은 메서드의 파라미터에 각각 제공 가능
@CsvSource 외에도 @MethodSource, @ValueSource 등이 존재
@DynamicTest
일련의 시나리오를 테스트
@TestFactory가 붙은 메서드에서 Iterable한 반환 값을 던지는 형태
공통의 환경에서부터 일련의 사건을 실행시켜 테스트가 가능.
given/when/then 세트가 복수로 생기기 때문에 가독성 저하에 주의하기.
환경 통합하기
gradle 탭의 tesk에서 test를 수행하면 전체 테스트를 수행해보기
전체 테스트 시 Spring Boot가 여러번 로딩하게 되면 비용과 시간 증가...!
서버가 발생하는 횟수를 줄여야 한다!
서버를 띄우는 @SpringBootTest과 profile을 지정하는 @ActiveProfile을 별도의 상위 클래스에 붙여 Test 클래스들에 상속시키기!
mock도 별도의 환경으로 취급하기 때문에 mock 객체는 상위 클래스에서 protected로 생성하거나 mock이 필요한 환경을 별도로 설정하기.
환경 통합을 위해, @DataJpaTest보단 @SpringBootTest를 사용하기
private 메서드를 테스트하기
결론적으로는, 할 필요도 없고 하려고 해서도 안된다!
그래도 고민된다면, 객체를 분리할 시점인지 관해 생하기
분리가 필요하다고 판단된다면 객체를 별도로 분리해 private 메서드를 공개 메서드로 작성하면 된다.
private 메서드는 외부에서 공개하고 싶지 않은 것이고 외부에서는 몰라도 되는 부분이다! public 메서드를 검증하다보면 자연스레 검증되는 부분이다!
+ α
헥사고날 아키텍쳐
도메인 주도의 아키텍쳐
QueryDSL
JPA 사용 시 거의 필수
타입 체크, 동적 쿼리(값에 null이 들어올 시 자동으로 동적으로 조회해줌)가 가능
낙관적 락, 비관적 락
CQRS(Command and Query Responsibility Segregation)
DB에서 조회와 업데이트를 분리
미션
미션 5, 6은 직접 테스트 코드를 작성했던 네 번째 미션 보다는 훨씬 편안한 내용이었다. 강의 내에서 이미 정답을 다 알려준 느낌이고, 내 나름의 언어로 정리해보는 느낌이었다.
쉽다고 만만히 보았기 때문일까... 특강 때 공통 피드백을 받고 바로 아차 싶었다. @BeforeEach 부분은 단순 중복 제거를 위한 것이 아니라는 점을 간과했다. 리팩토링 강의에서부터 가장 강조하신 것 중 하나가 도메인인데 그것을 놓치다니...🤣🤣 정답은 맞췄어도 풀이 과정 때문에 반타작 밖에 못한 기분이었다.
후기
벌써 4주차, 마지막 발자국이자 워밍업 클럽 2기의 마무리이다. 한 달이라는 시간이 쏜살같이 지나가버렸다. 테스트 코드와 리팩토링에 대해 심도 있게 다가간 것은 처음이었던 나에게는 정말 어려웠다. 강의에서 들었던 내용을 과제에 적용하는 과정은 험난했다. 과제를 모두 제출하고 완주를 한 것 만으로도 만족해버리고 말았다.
당장 다음주 시작부터, 새로운 스터디가 시작된다. 같이 워밍업 클럽에 참여하신 분의 자바 스터디인데, 기초부터 다시 다잡는다는 느낌이라 굉장히 기대가 된다. 👏👏
마지막으로, 이런 자리를 마련해주신 인프런과 코치님, 그리고 내 부족한 미션 피드백을 해주신 다른 스터디 참여자 분께 감사의 말씀을 드리고 싶다. 언젠가 3기가 열린다면, 다시 한 번 도전하고 싶다!