[TEST CODE] Spring Boot 실전 테스트코드 작성 세션

SJ.CHO·2024년 10월 14일

테스트코드는 무엇이고, 왜 작성해야할까?

  • 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 환경에서처럼 객체를 주입받아 사용.

기본

  1. Stubbing 을 이용해서 Mock 객체의 동작 정의
when(socialMemberRepository.findByEmail(any()))
			.thenReturn(Optinal.ofNullable(new Member(...)));
  • any() 는 어떤 값이 와도 동작하도록 보장/
  1. spy 를 이용해 Mock 객체가 특정동작을 수행했는지 검증
verify(socialMemberRepository,times(1)).findByEmail(any());
  1. spy + Captor 를 이용해 특정동작 수행시 파라미터 검증
@Captor
ArgumentCaptor<String> emailCaptor;
verify(socialMemberRepository,times(1)).findByEmail(emailCaptor.capture);
String email = emailCaptor.getValue();
assertThat(email).isEqualTo("...");
  • Juint 의 제공 구문보다 AssertJ 의 assert 구문 사용을 추천
  1. Stubbing 을 이용해 특정메소드가 동작하지 않도록 만들시
doNothing().when(socialMemberRepository).findByEmail(any());
  • 주로 반환값이 Void 같은 메소드에 사용. (결과를 알기힘듬)
  1. 지정된 메소드만 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 - 검증을 수행
    의 순으로 코드의 흐름을 작성해보자!

profile
70살까지 개발하고싶은 개발자

0개의 댓글