테스트코드는 무엇이고, 왜 작성해야할까?
- SW를 테스트한다는 것은 해당 SW가 기대하는대로 잘 동작하는지 확인하는과정.
- SW의 결함을 조기에 발견할 수 있고, 완성도 있는 SW 개발이 가능하다.
- 선택이 아닌 필수사항!!
FIRST 원칙
- F (Fast) : 테스트코드는 빠르게 실행되어야한다.
- I (Isolated) : 테스트는 독립적이어야한다. 다른 테스트 혹은 외부시스템과 의존되면 안된다.
- R (Repeatable) : 테스트는 반복실행해도 같은 결과여야한다.
- S (Self-Validating) : 테스트 스스로 검증되어야한다. Log등을 통해 개발자가 검증을하면 안됌.
- T (Timely) : 테스트코드는 즉시 작성되어야 한다.
Spring Boot 에서의 테스트 코드
단위 테스트
- 개별적인 코드 단위에 대한 테스트코드
- Spring 기능이 없는 순수한 JAVA코드 기준
통합 테스트
- 여러 객체(모듈)의 상호작용에 대한 테스트 (Ex : 3Layer 흐름 등)
E2E 테스트
- API 하나의 단위로 처음부터 끝까지 에 대한 동작 테스트
인수 테스트
- 사용자의 시나리오 전체 플로우를 검증하는 테스트
- E2E 보다 조금더 일반적으로 사용.
테스트 코드 작성전 해야할 것.
시나리오를 잘 작성하는것이 가장 중요하다.
- 테스트를 하기전 내가
어떤 것 을 테스트할지를 정리를 해보고 시작하자.
(한글로 작성해보는 등)
Mocking 과 Stubbing (TestDouble)
- 테스트하고자하는 코드가 외부 혹은 의존하는 객체가 많을시 대역을 가지고와서 의존시키는 테스트 기법.
Mock 객체
Stubbing
- Mock 객체가 어떤방식으로 행동할지 결정해주는것.
Spy
- Mock 객체가 몇번호출됐는지 등 흐름을 기록할 수 있다.
Fake
- Stubbing 은 단순 응답만 정해줬다면 Fake는 가짜로직까지 지정.
TestDouble 구현
@ExtendWith(MockitoExtension.class)
class SocialMemberSreviceTest{
@Mock
private SocialMemberRepository socialMemberRepository;
@InjectMocks
private SocialMemberService socialMemberService;
@ExtendWith(MockitoExtension.class)
- Mock 객체를 사용하기위한 어노테이션 으로 JUnit5 에서 사용.
@InjectMocks
- Mock 객체를 실제 Spring 환경에서처럼 객체를 주입받아 사용.
기본
Stubbing 을 이용해서 Mock 객체의 동작 정의
when(socialMemberRepository.findByEmail(any()))
.thenReturn(Optinal.ofNullable(new Member(...)));
any() 는 어떤 값이 와도 동작하도록 보장/
spy 를 이용해 Mock 객체가 특정동작을 수행했는지 검증
verify(socialMemberRepository,times(1)).findByEmail(any());
spy + Captor 를 이용해 특정동작 수행시 파라미터 검증
@Captor
ArgumentCaptor<String> emailCaptor;
verify(socialMemberRepository,times(1)).findByEmail(emailCaptor.capture);
String email = emailCaptor.getValue();
assertThat(email).isEqualTo("...");
Juint 의 제공 구문보다 AssertJ 의 assert 구문 사용을 추천
Stubbing 을 이용해 특정메소드가 동작하지 않도록 만들시
doNothing().when(socialMemberRepository).findByEmail(any());
- 주로 반환값이 Void 같은 메소드에 사용. (결과를 알기힘듬)
- 지정된 메소드만
Stubbing 하고 나머지는 원본코드 그대로 사용하는 방법.
@Spy
private SocialMemberRepository socialMemberRepository
객체별 단위 테스트 작성하기

모든 단위테스트는 최대한 @SpringBootTest 사용을 지양한다 (테스트 환경치곤 너무 무거움)
- 단위 테스트 작성 우선순위
- DomainEntity -> Service -> Client -> POJO -> Repository -> Controller
- 물론 가장 최고의 상황은 모두다 하는것.
- 이외에도
예측하기 어렵고 불안한 코드 가 존재한다면 우선적으로 작성.
1. DomainEntity And POJO
- 일반적으론 의존성이 없기에 별도의 Mocking 작업은 불필요하다.
- 가장
가볍고 간단하게 구현 할 수 있다.
2. Service
- Service 는 높은확률로 다른 객체들을 의존하기에 해당 객체들의 대한 Mocking 작업이 필요하다.
- Serivce를 흐름의 표현으로만 사용했을 경우 굳이 테스트가 필요한가 싶은 상황이 있다. 이런경우 예외상황 정도만 확인 후 통합테스트로 흐름을 검증하자.
3. Client
- Client 는 외부서비스를 호출하는 특징을 가지기에
Mock Server 를 만들어 테스트해야한다.
(외부 서비스에 의존할 경우 FIRST 원칙을 위배한다.)
- Mock Server 의 경우 MockWebServer 를 이용하고, 복잡한 동작의 경우 WireMock 을 사용한다.
- 주로 외부응답의 파싱, 예외발생 시나리오를 테스트한다.
4. Repository
- JPA 를 사용할땐 내가 작성을하지 않기에 테스트를 잘 안한다.
- 쿼리문이 들어갔을땐 통합테스트로 넘기는경우가 많다.
5. Controller
- Controller 의 경우 특별히 코드가 들어가지 않기에 우선순위가 낮다.
- 특히 특별한경우가 아니라면 굳이...
의존성 관점에 방향성

- 결국엔 단위테스트를 위해선 의존을 하고잇는 객체가
무조건 동작을 보장 하는 경우에만 가능하다. 그렇기에 해당 객체가 동작을 보장한다! 라고 믿거나 검증의 단계를 하나씩 늘려가야함.
@DataJpaTset 어노테이션을 통해 DB 환경을 구성이가능하다.
(Spring 환경에서의 테스트가 아니기에)
- Bean 이 필요한경우
@Import(ClassName.class) 를통해 원하는 Bean 추가가능
H2DataBase(EmbeddedH2) 를 통해 내부 DB를 통해 DB테스트가 가능하다.
- 실제운영중인 DataBase로 테스트 시 실제 DB는 사용하지않는게 좋다.
요약
-
테스트 시나리오 작성의 중점을 가지자
-
읽는사람 이 쉽게 시나리오를 파악할수 있게 작성하자
-
잘할 필요없다 일단 테스트를 진행하자!
-
given - 테스트를 위한 준비물, 메서드 호출파라미터, Stubbing
when - 테스트를 위한 메서드 호출
then - 검증을 수행
의 순으로 코드의 흐름을 작성해보자!