인프런 워밍업 스터디 클럽 2기 백엔드(클린코드&테스트코드) 발자국 - 4주차
인프런 워밍업 클럽 2기, 백엔드(클린코드&테스트코드) 과정에 참여하고 있습니다.
마지막 주차인 이번 4주차에는 Layered Architecture 의 각 레이어별 역할과 실무에 가까운 테스트 방법들을 배워보는 시간을 가졌습니다.
[학습 요약]
Layered Architecture
Persistence Layer
Data Access의 역할을 한다.
~~Repository
비즈니스 가공 로직이 포함되어서는 안된다.
Data에 대한 CRUD에만 집중한 레이어
Business Layer
비즈니스 로직을 구현하는 역할
Persistence Layer와의 상호작용(Data를 읽고 쓰는 행위)을 통해 비즈니스 로직을 전개시킨다.
트랜잭션을 보장해야 한다.
작업단위에 대한 원자성
Presentation Layer
외부 세계의 요청을 가장 먼저 받는 계층
파라미터에 대한 최소한의 검증을 수행한다.
MockMvc
Mock: 가짜, 대역의 의미를 갖고 있다.
Mock(가짜) 객체를 사용해 스프링 MVC 동작을 재현할 수 있는 프레임워크
Test Double
테스트를 수행하기 위한 대역 역할을 하는 객체들을 칭하는 표현
Dummy
아무것도 하지 않는 깡통 객체
Fake
단순한 형태로 동일한 기능은 수행하나, 프로덕션에서 쓰기에는 부족한 객체 (ex. FakeRepository)
Stub
테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체.
그 외에는 응답하지 않는다.
Spy
Stub 이면서 호출된 내용을 기록하여 보여줄 수 있는 객체.
일부는 실제 객체처럼 동작시키고 일부만 Stubbing 할 수 있다.
Mock
행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체.
Stub과 Mock의 차이
Stub은 상태 검증(State Verififation) 을 위해 사용
Mock은 행위 검증(Behavior Vefirification) 을 위해 사용
순수 Mockito로 검증해보기
@ExtendWith(MockitoExtension.class)
class MailServiceTest {
@Spy
private MailSendClient mailSendClient;
@Mock
private MailSendHistoryRepository mailSendHistoryRepository;
@InjectMocks
private MailService mailService;
@DisplayName("메일 전송 테스트")
@Test
void sendMail() {
// given
// 1)
doReturn(true)
.when(mailSendClient)
.sendEmail(anyString(), anyString(), anyString(), anyString());
// 2)
doNothing()
.when(mailSendClient)
.a();
// when
boolean result = mailService.sendMail("", "", "", "");
// then
assertThat(result).isTrue();
verify(mailSendHistoryRepository, times(1)).save(any(MailSendHistory.class)); // 3)
}
}
@Spy
객체는Mockito
의 메서드가 아니라Stubber
메서드를 이용해서 Stubbing 을 수행해야 한다.1) MailSendClient 의 .sendEmail() 메서드만 Stubbing 했고, 나머지는 실제 구현 코드를 사용한다.
2) 이렇게 하면 .a() 도 Stubbing 했으므로, 동작을 안하게 됨.
3)
verify()
를 이용해서 Mock 객체의 여러가지 행위를 검증할 수 있다.
BDDMockito
Mockito 를 한번 더 감싸서, 동일한 기능을 제공하되 BDD 스타일로 작성된 메서드를 사용할 수 있도록 도와주는 라이브러리이다.
// Before
// given
Mockito.when(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString()))
.thenReturn(true);
// After
// given
BDDMockito.given(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString()))
.willReturn(true);
Mockito를 사용하게 되면, given 절에 when()
이라는 이름의 메서드를 사용해야 된다. 이는 부자연스럽게 느껴질 수 있으므로, BDDMockito 를 사용하면 동일하게 when()
동작을 수행하지만 이름은 given()
인 메서드를 사용할 수 있다.
더 나은 테스트를 작성하기 위한 구체적 조언
한 문단에 한 주제
테스트 코드는 문서의 역할을 한다.
그러니, 글쓰기를 한다는 마음 가짐으로 테스트를 작성하자.
완벽하게 제어하기
현재시간, 랜덤값 등을 외부에서 주입받도록 DI 구조로 리팩토링 하여, given 데이터를 완벽하게 제어할 수 있도록 한다.
이메일 발송과 같은 외부 세계와 소통하는 기능은 Mocking 을 통해 제어할 수 있도록 한다.
테스트 환경의 독립성을 보장하자
한 테스트 메서드에서 두가지 이상의 기능을 테스트하지 말자
논리적인 사고가 한번 더 필요해지므로, 가독성에 안좋은 영향을 준다.
when/then 절의 assert 구문을 호출하기 전에 given 에서 예외가 발생할 수도 있다.
given절에 사용할 데이터를 만들때는 가급적이면 순수한 생성자 또는 Builder 를 통해 생성하는 것이 좋다.
생성 과정에 검증이 포함되어 있는 Factory 메서드 패턴을 사용하면, 생성 과정에서 예외가 발생할 수도 있음.
테스트 간 독립성을 보장하자
테스트간에는 순서라는 개념이 없어야 한다.
각각 독립적으로, 언제 어떤 순서로 어떻게 수행되든 항상 같은 결과를 내야만 한다.
한 눈에 들어오는 Test Fixture 구성하기
Fixture: 고정물, 고정되어 있는 물체
테스트를 위해 원하는 상태로 고정시킨 일련의 객체
Fixture를 생성하는 코드를 메서드로 분리시킨 다면,
이 테스트에서 필요한 파라메터만 넘길 수 있도록 메서드 내부에서 기본값을 하드코딩으로 설정해주는 방법이 가독성 향상에 좋다.
@BeforeEach, @BeforeAll
중복 코드를 줄이기 위해 사용하는 기능이지만, 각 테스트간 결합이 생기게 만든다는 맹점이 존재함.
각 테스트 입장에서 봤을 때, 아래 두가지 항목을 만족하면 BeforeEach 절에 사용해도 괜찮다.
아예 몰라도 테스트 내용을 이해하는 데에 문제가 없는가?
수정해도 모든 테스트에 영향을 주지 않는가?
data.sql
테스트 클래스 코드가 아닌, 쿼리로 데이터를 미리 셋업하도록 처리하는 방식은 데이터를 셋업하는 코드의 파편화로 인해, 유지보수 포인트가 증가하고 추후 중복코드를 만들어낼 수도 있는 등 프로젝트의 복잡도만 높이는 행위이므로 지양하는 것이 좋다.
Test Fixture 클렌징
.deleteAll()
vs.deleteAllInBatch()
`.deleteAll()` : 지울 테이블을 먼저 select 하고, 데이터 건수만큼 delete 쿼리의 where 절에 key값을 포함한 쿼리가 요청됨.
`.deleteAllInBatch()` : 냅다 delete All 쿼리를 요청해버림.
그럼
.deleteAll()
은 왜 사용??.deleteAll()
은 모든 연관관계를 먼저 찾은 다음 삭제를 해주기 때문에, 외래키 같은 제약 조건을 고려해서 삭제해준다.어지간하면
.deleteAllInBatch()
를 사용하자.
[후기]
이로써 총 4주간의 워밍업 클럽 일정이 마무리 되었습니다. 지금까지는 인프런 강의를 구매만 해놓고 수강을 미뤄놓기만 해왔는데, 우연한 기회에 알게된 워밍업 클럽 덕분에 밀도있게 학습을 할 수 있는 경험을 겪어본 것이 가장 좋았던 점이었습니다.
실무에서도 테스트를 적극적으로 작성하지 않는 상황이 정말 많아, 테스트를 어떻게 하면 더 잘 작성할 수 있는지에 대한 의문점이 항상 있었습니다. 그런 저에게 실용적인 테스트를 작성할 수 있게 실무에 가까운 예제를 알려준 이번 강의가 정말 많은 도움이 되었습니다.
좋은 강의를 준비해주신 박우빈 강사님께 감사드리고, 워밍업 클럽이라는 시스템을 마련하고 운영해주신 인프런 운영진분께도 감사드립니다.
댓글을 작성해보세요.