Coding/Server
[내일배움캠프] 테스트코드 - 이론
kangplay
2025. 4. 17. 15:08
1. 테스트 코드는 무엇이고, 왜 작성해야할까?
소프트웨어를 테스트한다는 것은 소프트웨어가 기대한대로 잘 동작하는지 확인하는 과정이다.
아마 소프트웨어를 개발하고 테스트하지 않는 사람은 없을 것이다.
-> 이러한 테스트를 코드로써 하겠다는 것이다.
💪🏻 테스트 코드를 작성함으로써 얻어지는 이점
1. 테스트를 자주할 수 있게 된다.
2. 리팩토링에 심리적인 안정감을 준다.
3. 에러를 조기에 발견할 수 있다.
2. 테스트 코드 작성 전에 해야할 것
테스트 코드가 무조건 많은 것보다, 질 좋은 테스트를 작성하는 것에 중요하다.
이를 위해서는, "시나리오"를 잘 작성해야한다.
카카오 로그인이 잘되는지 테스트
1.~
2.~
3.~
(BDD를 코드가 아닌, 글로 작성!)
3. Mocking과 Stubbing: Test Double
Test Double이란 실제 객체의 역할을 흉내내는 테스트용 객체로, 종류는 다음과 같다.
- Mock 객체: 테스트에서만 사용할 가짜 객체로 설정에 따라서, Dummy, Stub, Spy처럼 동작할 수 있다.
- Stubbing: Test Double 객체의 메소드가 어떤 응답을 할지 결정해버리는 것
- Spy: Test Double 객체의 메소드가 호출되었는지, 몇 번 호출 되었는지에 대한 정보를 기록하는 것
- Fake: Stubbing은 단순 응답만 정해줬다면, Fake는 가짜 로직까지 정해주는 것
- 예) 데이터 저장 로직을 DB가 아닌 다른 저장소를 만들어 저장
Java 에서는 Mockito 등의 라이브러리를 활용해 Test Double을 구현할 수 있다.
=> Mock 객체 생성 후, 해당 Mock 객체를 이용해 Stubbing, Spy를 구현할 수 있다.
@ExtnedWith(MockitoExtension.class) class SocialMemberServiceTest { @Mock private SocialMemberRepository socilMemberRepository; //@InjectMocks를 쓰지 않고, new SocialMemberService(socialMemberRepsitory);와 같이 Mock객체를 이용해 직접 생성해도 된다! @InjectMocks private SocialMemberService socialMemberService; }
4. Mock 객체를 이용해 여러가지 시나리오 테스트해보기
1. Stubbing을 이용해 Mock 객체의 동작 정의하기
when(memberRepository.findByEmail(any()).thenReturn(Optional.ofNullable(new Member(..));
2. Spy를 이용해 Mock 객체가 특정 동작을 수행했는지 검증하기
verify(memberRepository, times(1)).findByEmail(any());
//Return Value만 테스트하던 것에서 벗어날 수 있다!
3. Spy + Captor를 이용해 특정 동작을 수행할 때의 파라미터 검증하기
//2번에서 any()가 어떤 이메일인지까지 검증 가능
@Captor
ArgumentCaptor<String> emailCaptor;
verify(memberRepsitory, times(1)).findByEmail(emailCaptor.capture());
String email = emailCaptor.getValue();
//org.assertj.core.api.Assertions.assertThat(Junit에서 제공하는 assert 구문보다 AssertJ의 assert 구문 사용을 추천
assertThat(email).isEqualTo("slolee@naver.com");
4. Stubbing을 이용해 특정 메소드가 동작하지 않도록 만들기
//반환없이 void인 애
doNothing().when(memberRepsitory).findByEmail(any());
5. 지정된 메소드만 Stubbing 하고 나머지는 원본 코드 그대로 사용하기
@Spy
private MemberRepository memberRepository;
5. Unit Test: 객체별 단위테스트 작성하기
👐 단위테스트 작성 우선순위
- Domain Entity -> Service -> Client -> POJO -> Repository(내가 직접 작성한 쿼리가 아닌 경우, @DataJpaTest를 활용한 SliceTest) -> Controller(상대적으로 많은 로직이 들어가지 않기 때문에 @WebMvcTest를 이용한 통합테스트나 E2E로 대체됨)
- 기본적인 우선 순위는 이렇지만 "예측하기 어렵고 불안한 코드"(반복문이 많거나 복잡한 계산 로직이 있는 경우)가 있으면 반드시 테스트 코드를 우선적으로 작성하는 게 좋다.
객체별 단위 테스트 작성 요령에 대해서 알아보자.
1. Domain Entity 등 POJO
- 일반적으로 다른 객체를 의존하고 있지 않기 때문에 별도의 Mocking은 필요없다.
- 가장 가볍고 간단하게, 즉 Spring Boot 없이 순수한 단위 테스트로 구현할 수 있는 테스트다.

2. Service
- Service는 다른 객체들을 의존하고 있기 때문에 해당 객체들에 대한 Mocking이 필요하다.
- 만약 DB까지 붙여서 테스트하고 싶으면, 통합테스트를 작성하자.


3. Client
- Client는 외부 서비스를 호출하는 특징을 가졌기 때문에 Mock Server를 만들어 테스트해야한다.
- 간단한 Mock Server인 경우 MockWebServer를 이용하고, 조금 복잡한 동작을 해야하는 Mock Server의 경우 WireMock을 이용할 수 있다.
- 이를 통해 외부 서비스로부터의 응답 파싱, 외부 서버 예외 발생 시나리오 등을 테스트할 수 있다.

6. JPA + H2 Database를 곁들인 통합테스트
- 통합 테스트라고 다짜고짜 @SpringBootTest를 사용하는 것은 좋지 않다.
- JPA에서 제공해주는 @DataJPaTest를 이용하자.
- +) Spring Boot Test에서 Context를 재사용해서 제공하는 방법도 있다.
- 만약, MemberService.register()가 실행되면 DB에 회원정보가 잘 저장되는지 확인하고 싶다면?
- Service - Repository - DB
7. 인수테스트 예시

8. 마무리하며
테스트코드에서 가장 중요한 것은 테스트 시나리오를 잘 작성하는 것이다. 테스트 코드를 보고 읽는 사람이 쉽게 시나리오를 파악할 수 있도록 하자.