[인프런 워밍업 스터디 클럽 2기_BE] 4주차 발자국

Mock을 마주하는 자세

1. Test Double 개념

마틴 파울러는 테스트에서 사용되는 가짜 객체를 Test Double이라는 용어로 부르며, 이는 영화에서 스턴트 더블을 사용하는 것과 유사하게 테스트를 위해 실제 객체 대신 사용되는 모든 종류의 "대역" 객체를 의미합니다. Test Double은 다양한 상황에서 실제 객체를 대체하여 테스트를 가능하게 합니다. Test Double에는 여러 종류가 있으며, 여기에는 Dummy, Fake, Stub, Spy, Mock이 포함됩니다.

2. Test Double의 종류

  1. Dummy: 아무것도 하지 않는 깡통 객체입니다. 테스트에서 단순히 자리를 채우기 위해 사용됩니다.

  2. Fake: 실제 기능을 수행하지만, 단순한 형태로 동작하는 객체입니다. 프로덕션에서 사용하기에는 부족한 부분이 있습니다. 예를 들어 FakeRepository가 해당됩니다.

  3. Stub: 테스트에서 요청한 것에 대해 미리 준비된 결과를 제공하는 객체입니다. 그 외의 요청에는 응답하지 않습니다.

  4. Spy: Stub의 역할을 하면서 호출된 내용을 기록하여 보여줄 수 있는 객체입니다. 일부는 실제 객체처럼 동작시키고 일부는 Stubbing할 수 있습니다.

  5. Mock: 특정 행동에 대한 기대를 명시하고, 그 기대에 따라 동작하도록 만든 객체입니다.

3. Stub과 Mock의 차이

StubMock은 모두 테스트에서 특정 객체의 역할을 대신할 수 있지만, 검증 방식과 목적이 다릅니다.

차이점 요약

특징 Stub Mock 목적 특정 상황을 설정하기 위해 사용 행위 자체가 올바르게 수행되는지 검증 검증 방식 상태 검증 (State Verification) 행위 검증 (Behavior Verification) 용도 테스트의 결과가 예상한 대로인지 확인 특정 메서드가 예상대로 호출되었는지 확인 예시 메서드가 호출된 후 내부 상태를 확인 메서드가 기대한 횟수만큼 호출되었는지 확인


4. Mockito란?

Mockito테스트 더블을 쉽게 만들어주는 Java 라이브러리입니다. 테스트 더블이란, 테스트에서 실제 객체 대신 사용되는 가짜 객체를 말합니다. 예를 들어, 우리가 UserService를 테스트할 때, UserService가 실제 데이터베이스를 호출하지 않게 하려면 데이터베이스와 상호작용하는 UserRepository를 가짜로 만들어서 테스트하는 방식이 있습니다. 이때 Mockito를 사용하여 UserRepository의 Mock 객체를 생성하는 것입니다.

5.Mockito에서 자주 사용하는 세 가지 애노테이션

1. @Mock

@Mock 애노테이션은 클래스의 가짜 객체(Mock Object)를 생성하는 데 사용됩니다. 이를 통해 실제 객체와 상호작용하는 대신, 특정 상황에서 원하는 결과를 반환하도록 미리 설정할 수 있습니다.

  1. 용도

    주로 의존성을 격리하고 테스트하려는 대상 객체와 관련된 외부 객체의 실제 구현을 대체할 때 사용합니다. 예를 들어, 데이터베이스나 외부 API 호출 같은 의존성을 제거하고 독립적으로 테스트하려는 경우 유용합니다.

  2. 사용 예시

    @Mock
    private OrderRepository orderRepository;
    
  3. 특징

    @Mock 객체는 기본적으로 모든 메서드 호출에 대해 아무 동작도 하지 않으며 null, 0, false 같은 기본값을 반환합니다. 필요한 경우 특정 메서드 호출에 대한 반환값을 설정할 수 있습니다:

    when(orderRepository.findById(1L)).thenReturn(Optional.of(order));
    

2. @InjectMocks

@InjectMocks 애노테이션은 의존성이 주입된 객체를 생성합니다. 이 애노테이션이 붙은 객체는 @Mock 또는 @Spy로 주입 가능한 필드에 대해 자동으로 주입됩니다.

  1. 용도

실제로 테스트하고자 하는 객체를 생성할 때 사용합니다. 테스트 대상 객체의 의존성으로 @Mock 또는 @Spy로 선언된 가짜 객체가 주입됩니다.

  1. 사용 예시

    @InjectMocks
    private OrderService orderService;
    
  2. 특징:

    OrderService 내부에 있는 OrderRepository, MailService 등의 필드에 자동으로 @Mock 객체가 주입됩니다. @InjectMocks는 주로 테스트 대상 클래스에 붙이고, 해당 클래스의 의존성을 주입하기 위한 @Mock과 함께 사용됩니다.


3. @Spy

@Spy 애노테이션은 기존 클래스의 객체를 부분적으로 모킹할 수 있게 해줍니다. 즉, @Spy로 생성된 객체는 실제 객체처럼 동작하지만, 특정 메서드는 모킹할 수 있습니다.

  1. 용도

    테스트 중에 실제 객체의 일부 기능은 사용하면서 특정 메서드만 모킹하고자 할 때 사용합니다.

  2. 사용 예시

    @Spy
    private MailService mailService;
    
    
  3. 특징

    @Spy 객체는 실제 객체처럼 동작하지만, 특정 메서드에 대해서는 원하는 동작을 설정할 수 있습니다. 특정 메서드만 모킹하고 싶을 때 doReturn이나 doThrow 같은 메서드를 사용합니다:

    doReturn(true).when(mailService).sendEmail(anyString(), anyString(), anyString(), anyString());
    
    

6. BDD Mockito

BDD (Behavior-Driven Development) Mockito는 테스트 코드에서 행동에 기반한 검증을 가능하게 하는 Mockito의 기능입니다. BDD는 “애플리케이션이 어떻게 행동해야 하는지에 대한 공통된 이해를 구성하는 방법” 입니다.

BDDMockito는 Behavior-Driven Development (행위 주도 개발) 스타일의 테스트 작성을 지원하는 Mockito의 확장 라이브러리입니다.

BDDMockito의 주요 특징

  1. given-when-then 구조 BDDMockito는 테스트 코드를 given(준비)-when(실행)-then(검증) 구조로 작성하도록 돕습니다. 이 구조는 테스트의 가독성을 높이고 테스트의 의도를 명확히 합니다.

  2. 스텁(Stubbing) 메서드

    • given(): Mockito의 when() 대신 사용됩니다.

    • willReturn(), willThrow() 등: thenReturn(), thenThrow() 대신 사용됩니다.

  3. 검증 메서드

    • then(): verify() 대신 사용됩니다.

    • should(): 특정 동작이 수행되었는지 검증합니다.

  4. 가독성 향상 BDDMockito를 사용하면 테스트 코드가 더 자연스러운 언어로 읽힙니다.

7. Classicist VS. Mockist

Classicist (고전파) Mockist (모키스트) 상태 기반 테스트 (객체의 상태를 확인) 행동 기반 테스트 (메서드 호출을 검증) 협력 객체에 대한 신뢰를 바탕으로 테스트 협력 객체와의 상호작용을 검증 구체적인 결과를 중시 메시지 흐름과 동작의 정확성을 중시 변경에 덜 민감한 테스트 변경에 민감할 수 있음


더 나은 테스트를 작성하기 위한 구체적 조언

  1. 테스트 하나 당 목적은 하나

    1. 테스트 하나당 목적은 하나

    2. 분기문이나 반복문 이런 생각을 요하는 추가적인 고민을 필요로하는 로직들이 들어가서는 안됨

  2. 완벽한 제어

    1. 제어할 수 있는 값으로

  3. 테스트 환경의 독립성, 테스트 간 독립성

    1. 공유 변수 사용하는 것 , 한 테스트 다른 테스트 연관되는 것 피하자

  4. Test Fixture

    1. Test Fixture는 특정 항목, 장치 또는 소프트웨어를 일관되게 테스트하는 데 사용되는 장치

      1. https://velog.io/@langoustine/Test-Fixture

  5. deleteAll(), deleteAllInBatch()

    1. deleteAll()이 실행되면, findAll()의 결과로 얻은 리스트를 순회하며 데이터를 한 개씩 삭제

    2. deleteAllInBatch() 결국 테이블에 있는 데이터를 전부 지우는 DELETE 쿼리가 실행됩니다. 즉, 데이터 크기와 관계없이 한 번의 쿼리로도 Repository를 clear 할 수 있게 되므로, deleteAll() 보다는 deleteAllInBatch() 사용이 테스트 속도를 고려했을 때 우선적으로 사용하는 것이 좋음

    3. https://velog.io/@balparang/deleteAll-보다-deleteAllInBatch를-사용하자


학습 테스트

  • 잘 모르는 기능, 라이브러리, 프레임워크를 학습하기 위해 작성하는 테스트

  • 여러 테스트 케이스를 스스로 정의하고 검증하는 과정을 통해 보다 구체적인 동작과 기능을 학습할 수 있다.

  • 관련 문서만 읽는 것보다 훨씬 재미있게 학습할 수 있다.

Spring REST Docs란?

Spring REST Docs는 테스트 코드에 기반하여 API 명세서를 자동으로 생성하는 문서화 도구입니다. 백엔드 개발자가 API를 개발하고 테스트하면서 동시에 신뢰할 수 있는 API 명세서를 만들 수 있도록 도와줍니다.

Spring REST Docs의 주요 특징

  1. 테스트 코드 기반 문서화: 테스트를 통과해야만 문서가 생성되므로 API 명세서의 신뢰도가 높습니다.

  2. AsciiDoc 문법을 사용한 문서화: 기본적으로 AsciiDoc 형식을 사용하여 문서를 생성합니다. AsciiDoc은 Markdown과 유사한 문법으로, 간결하고 가독성이 높은 문서를 작성할 수 있습니다.

  3. 프로덕션 코드 비침투적: 문서화를 위한 설정이 프로덕션 코드와 분리되어, API 동작에는 영향을 주지 않습니다.

Spring REST Docs의 장단점

장점

  • 테스트와 문서가 함께 관리되므로 API 명세의 신뢰도가 높아집니다.

  • 프로덕션 코드와 분리되어 코드 유지보수와 품질에 영향을 주지 않습니다.

단점

  • 테스트를 기반으로 문서가 생성되기 때문에 테스트 코드의 작성량이 많아질 수 있습니다.

  • 설정이 다소 복잡하여, 초기 세팅에 시간과 노력이 필요합니다.

Spring REST Docs를 사용할 때의 유용성

Spring REST Docs는 API 명세가 변경되는 경우에도 항상 최신 상태로 유지됩니다. 이는 테스트가 통과할 때만 문서가 생성되는 구조이기 때문에 가능한 것으로, 코드와 문서의 일관성을 보장합니다. 백엔드 개발자의 입장에서는 API의 품질과 신뢰도를 확보하면서, 협업을 위한 문서화도 자연스럽게 이루어지므로 유지보수가 용이해집니다.

댓글을 작성해보세요.

채널톡 아이콘