본문 바로가기

기타

[TDD] 좋은 테스트를 작성하기 위한 원칙들

테스트 유닛을 잘 작성하는 방법론들은 많다. 이들에 대한 정리를 하다보면 테스트를 어떻게 작성해야 할 것인지 감을 잡을 수 있을 것 같아서 정리해본다.

FIRST

  • Fast: 테스트 유닛은 빨라야 한다. 테스트라는 작업을 개발중에 자주 실행되는 작업이기 때문에 테스트의 실행 속도는 빨라야 한다.
  • Independent(Isolated): 테스트 유닛의 실행은 독립적이어야 한다. 테스트 유닛 하나의 결과가 다른 테스트 유닛들에게 전파되어서는 안된다. I에 해당되는 내용은 Independent로 적시한 글도 있고 Isolated도 적시한 글도 있다. 이들의 사용한 의미는 동일하기에 필자는 둘 다 맞다고 생각한다.
  • Repeatable: 테스트 유닛은 반복적으로 실행 가능해야 한다. 즉 네트워크, OS 등 외부 환경의 변화에 따라 테스트의 결과가 달라져서는 안된다.
  • Self-Validating: 테스트의 결과는 항상 참, 거짓으로 판별하도록 하여 개발자가 결과물들을 보고 임의로 판단해서는 안된다.
  • Timely: 테스트 유닛은 적시에 만들어야 한다. 예를 들어 red-green-refactor 개발 사이클을 가진 경우에는 red단계에서 테스트 유닛을 만들어야 한다.

A-trip

FIRST원칙과 마찬가지로 좋은 테스트를 작성하기 위한 원칙이다.

  • Automatic: 실행과 결과의 확인은 자동화되어야 한다 => 사람이 눈으로 실행결과를 비교해 테스트의 통과 여부를 판단해선 안된다.
  • Through: 테스트 코드에 문제가 발생할 경우 고쳐야 한다
  • Repeatable: 순서에 관계 없이 반복실행이 가능해야 한다
  • Independent: 테스트 코드는 독립적이어야 한다
  • Professional: 테스트 코드의 역할에 맞게 작성해야 한다.

RIGHT-BICEP

테스트할 대상을 정하기 위한 원칙으로서 다음과 같다.

  • Right: 결과가 올바른가
  • Boundary: 경계 조건에서도 정상동작 하는가
  • Inverse: 역 관계가 존재하는가
  • Cross-check: 다른 수단을 이용해 결과를 교차확인할 수 있는가.
    • Ex) 정렬알고리즘을 구현할 때 자바에 내장되어 있는 정렬 함수를 통해 값을 검증할 수 있어야 한다.
  • Error condition: 에러 조건을 강제로 만들 수 있는가
  • Performance: 성능이 한도가 존재하는가
    • Ex) 정규식을 이용할 때 무제한으로 string이 입력들어올 경우.

Mockito에서 제안하는 좋은 테스트 작성법

java에서 Mock객체를 생성해주는 Mockito 프레임워크에서 제안하는 좋은 테스트를 작성하기 위한 원칙이다. 이 원칙에선 위에 설명한 내용들과 달리 Mock을 어떻게 다뤄야 하는지에 대한 가이드를 설명해준다.

  1. 테스트 코드는 간결하고 읽기 쉬워야 한다
  2. 반복적인 코딩은 피해라
  3. 성공 케이스와 실패 케이스를 많이 보여주기 위해 Coverage를 가능한 채워야 한다.
  4. 직접 만들지 않는 객체에 대해 Mocking하지 말라
  5. 모든 객체를 Mocking 하는 것은 안티패턴이다.
  6. 값 객체는 Mocking하지 말아라

3번에서 주의해야할 것은 Coverage를 채우는 것이 목적이 아니다. 앞의 수식어인 "성공 케이스와 실패 케이스를 최대한 보여주기 위해" 가 주 목적이다. 즉 성공과 실패 케이스를 많이 보여주는 것이 주목적이지 Coverage를 채우는 것이 목적이 아니다.

4~6 번에 대해선 작성할 내용이 많기에 소주제로 빼서 설명하겠다.

4. 직접 만들지 않는 객체에 대해 Mocking하지 말아라

이는 반드시 지켜야 하는 원칙은 아니라고 설명한다. 그러나 이 원칙을 준수하면 사이드 이팩트를 감소하는 이점이 있기에 설명했다고 생각한다.

Mockito에서는 다음과 같은 이유로 사용하지 말라고 권유한다.

  1. 외부 라이브러리의 로직이 변경되어서 테스트 환경과 운용 환경에서의 실행 결과가 달라진다. 테스트 유닛에서는 Mock 객체이므로 외부 로직의 변경사항이 반영되지 않아 테스트에서는 성공하여도 운용 환경에선 실패할 수 있다.
  2. 외부 라이브러리를 Mocking하는 것은 객체가 외부 라이브러리와 결합도가 높다는 의미이므로 객체의 책임을 분리해야 한다는 신호이다.
  3. 외부 라이브러리는 내부 동작을 알 수 없다. 그렇기에 얼마나 많은 객체를 Mocking해야 하는지를 알 수 없다. 만약 이 내용이 복잡하다면 필요한 Mock 객체를 찾기 위한 시간을 써야하고 수많은 객체들을 Mocking해야 하기 때문에 테스트 코드는 간결하고 읽기 쉬워야 한다 는 원칙을 위배한다.

위의 내용을 요약하자면 외부 라이브러리라는 알 수 없는 로직을 가진 객체를 Mocking하는 것은 외부 라이브러리에 의존한다는 의미가 되며 외부 라이브러리의 수정이 실제 운용되는 코드에 전파되어 영향을 미칠 수 있지만 테스트 유닛에서 Mock객체로 만들어서 사용하면 이 영향을 알 수 없는 사이드 이팩트가 발생한다고 알 수 있다.

Mockito에서는 외부 라이브러리를 직접 Mocking하는 것보단 이를 wrapping한 객체를 만들고 이를 Mocking하는 것을 권고한다. 단 이 방식은 Leaky Abstraction 문제가 발생할 수 있다.

Leaky Abstraction(추상화의 구멍): 추상화를 완전히 하지 못한 상황에서 추상화를 하지 못한 부분에 문제가 발생하여 추상화의 역할을 할 수 없는 상황

5. 모든 객체를 Mocking하는 것은 안티패턴이다

이는 간단하게 설명할 수 있다. 모든 객체를 Mocking한다는 의미는 실제 테스트해야하는 코드가 포함된 객체도 Mock 객체로 만들어진다. 즉 진짜 테스트가 필요한 객체까지 Mocking하기 때문에 테스트 코드를 작성하는 의미가 없다. 그래서 안티패턴이다.

6. 값 객체를 Mocking하지 말라

값 객체를 만드는 것이 어려워 Mock 객체로 만들려는 것은 값 객체에 리팩토링이 필요하다는 의미이다. 이를 피하기 위해 값 객체 생성을 builder 패턴으로 만들거나 테스트 클래스에서 팩토리 메소드를 통해 값 객체를 만드는 것을 추천한다. 다음의 코드를 보자.

@ExtendWith(MockitoExtension.class)
class ArticleServiceTest {

    @InjectMocks
    private ArticleService sut;

    @Mock
    private ArticleRepository repository;

    @Test
    void givenArticle_WhenCreateArticle_ThenAddCount() {
        // Given
        ArticleDto articleDto = ArticleDto.builder()
            .title("title!!")
            .content("content!!")
            .build();
        given(repository.save(any(Article.class))).willReturn(any(Article.class));

        // When
        sut.saveArticle(articleDto);

        // Then
        then(repository)
            .should().save(any(Article.class));
    }

}

ArticleDto객체는 Article객체의 DTO객체이다. 이 DTO객체는 저장하는 곳 뿐만 아닌 수정, 삭제를 요청할 때도 사용한다. 이처럼 같은 DTO객체를 여러 장소에서 사용해서 값 객체를 생성하기가 어려울 때는 builder 패턴이나 테스트 클래스에서 Factory Method 패턴을 통해 만듬으로서 값 객체를 반드시 만들어서 사용하는 것을 권장한다.

결론

대부분의 테스트 원칙은 비슷한 말을 반복하고 있다. 테스트 유닛은 하나의 코드이자 문서의 역할을 하기에 이 관점으로 위의 원칙들을 바라본다면 어렵지 않게 이해될 것이다. 필자 입장에서는 Mockito의 Mock객체를 다루는 방법에 대해서 흥미로웠다. 이는 다른 원칙들에서 찾아볼 수 없는 오직 Mock을 다룰 때 필요한 주의점들이 흥미로웠다.


Reference

'기타' 카테고리의 다른 글

정규식 정리2  (0) 2021.10.20
정규식 정리  (0) 2021.10.20