JAVA/Spring

테스트 코드의 중요성과 작성 방법에 대한 심층적인 이해

min민 2024. 12. 14.

테스트 코드의 중요성

테스트 코드는 소프트웨어 개발에서 필수적인 역할을 한다. 코드가 의도한 대로 작동하는지 검증할 수 있는 안전망 역할을 한다. 개발 과정에서 기능을 추가하거나 기존 코드를 리팩터링할 때, 테스트 코드는 코드의 안정성을 유지하도록 돕는다. 이를 통해 예상치 못한 오류를 빠르게 발견하고 수정할 수 있다.

또한 테스트 코드는 개발자의 생산성을 높이는 데 기여한다. 수동으로 기능을 테스트하는 시간과 노력을 줄일 수 있다. 특히, 큰 프로젝트에서는 기능이 서로 얽혀 있어 수동 테스트만으로는 모든 문제를 발견하기 어렵다. 테스트 코드는 이러한 문제를 자동화된 방식으로 해결해 준다.

마지막으로, 테스트 코드는 개발자 간의 소통 도구로도 활용된다. 코드가 어떤 동작을 수행해야 하는지 명확하게 보여주기 때문에, 팀원들 간의 이해를 돕는다. 이를 통해 협업의 효율성도 높아진다.

 

즉 

 

테스트 코드는 소프트웨어 품질을 보장하기 위한 핵심 도구로, 개발 과정 전반에 걸쳐 안정성과 신뢰성을 확보하는 데 사용된다. 특히, 기능 추가나 리팩터링 시 예상치 못한 **회귀(regression)**를 방지하고, 코드가 의도대로 동작하고 있음을 지속적으로 확인할 수 있다.

 

테스트 코드는 다음과 같은 이유로 중요하다:

  1. 코드 안정성 보장: 기능 단위별로 검증을 수행하여 특정 변경 사항이 시스템에 부정적인 영향을 미치지 않도록 한다.
  2. 문서화 역할: 테스트 코드는 기능의 의도를 명확히 전달하여 코드가 수행해야 할 동작을 문서화한다. 이는 팀 협업에서도 중요한 역할을 한다.
  3. 리팩터링 지원: 테스트 코드는 코드 변경 후에도 기존 기능이 제대로 동작하는지 확인할 수 있는 안전망을 제공한다.
  4. 생산성 향상: 수동 테스트를 자동화하여 테스트 시간을 단축하고, 버그를 조기에 발견함으로써 디버깅 시간을 줄인다.

 

테스트 코드에서 Assertions의 역할

Assertions는 테스트의 핵심으로, 테스트 코드가 기대하는 결과와 실제 결과를 비교하여 테스트의 성공 또는 실패를 판단한다.

Assertions의 주요 특징

  • 가독성: Assertions.assertThat()과 같은 메서드는 테스트 결과를 직관적으로 표현한다.
  • 디버깅 지원: 테스트 실패 시, 기대했던 값과 실제 값의 차이를 명확히 보여준다.
  • 표준화: 테스트 프레임워크(JUnit, AssertJ 등)에서 제공하는 다양한 assertion 메서드를 통해 다양한 시나리오를 검증할 수 있다.

예제: Assertions를 활용한 테스트

아래 코드는 회원 저장과 조회 기능을 테스트하며, Assertions를 통해 결과를 검증한다.

 

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

public class MemberServiceTest {

    MemberService memberService = new MemberServiceImpl();

    @Test
    void join() {
        // given
        Member member = new Member(1L, "memberA", Grade.VIP);

        // when
        memberService.join(member);
        Member findMember = memberService.findMember(1L);

        // then
        Assertions.assertThat(findMember).isNotNull(); // 저장된 객체가 null이 아닌지 확인
        Assertions.assertThat(findMember.getName()).isEqualTo("memberA"); // 이름 확인
        Assertions.assertThat(findMember).isEqualTo(member); // 객체 자체 비교
    }
}

 

Assertions의 주요 메서드

  1. isEqualTo(): 객체 값의 동등성을 비교한다.
  2. isNotNull(): 객체가 null이 아님을 검증한다.
  3. isTrue()/isFalse(): 논리 조건을 검증한다.
  4. contains(): 리스트나 문자열에서 특정 값이 포함되어 있는지 확인한다.
  5. hasSize(): 컬렉션의 크기를 검증한다.

이처럼 다양한 메서드를 활용하여 테스트 대상을 세밀하게 검증할 수 있다.

 

 

다양한 테스트 코드의 예시

1. 예외 상황 검증

테스트 코드는 정상 동작뿐만 아니라 예외 상황을 검증하는 데도 사용된다.

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertThrows;

public class MemberServiceTest {

    MemberService memberService = new MemberServiceImpl();

    @Test
    void join_withDuplicateId_shouldThrowException() {
        // given
        Member member1 = new Member(1L, "memberA", Grade.VIP);
        Member member2 = new Member(1L, "memberB", Grade.VIP);

        // when
        memberService.join(member1);

        // then
        assertThrows(IllegalStateException.class, () -> memberService.join(member2));
    }
}
 

이 테스트는 동일한 ID로 두 번 가입하려 할 때 IllegalStateException이 발생하는지 검증한다.

  • assertThrows() 메서드는 특정 예외가 발생하는지 확인할 때 사용한다.

assertThrows(IllegalStateException.class, () -> memberService.join(member2)); // 두 번째 회원 가입 시도 (ID가 중복되므로 예외 발생)

2. 컬렉션 테스트

여러 데이터를 처리하는 로직을 테스트할 때는 리스트나 컬렉션의 상태를 검증해야 한다.

import org.junit.jupiter.api.Test;
import org.assertj.core.api.Assertions;

import java.util.List;

public class MemberServiceTest {

    MemberService memberService = new MemberServiceImpl();

    @Test
    void findAllMembers() {
        // given
        Member member1 = new Member(1L, "memberA", Grade.VIP);
        Member member2 = new Member(2L, "memberB", Grade.BASIC);
        memberService.join(member1);
        memberService.join(member2);

        // when
        List<Member> members = memberService.findAllMembers();

        // then
        Assertions.assertThat(members).hasSize(2); // 리스트 크기 확인
        Assertions.assertThat(members).contains(member1, member2); // 리스트에 특정 객체 포함 확인
    }
}

 

3. 경계 값 테스트 (Boundary Testing)

경계 값을 처리하는 로직은 오류가 발생하기 쉬운 영역이므로, 이를 검증하는 테스트가 필요하다.

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class DiscountServiceTest {

    DiscountService discountService = new DiscountService();

    @Test
    void applyDiscount_boundaryValue() {
        // when & then
        assertThrows(IllegalArgumentException.class, () -> discountService.applyDiscount(-1)); // 음수 금액
        assertThrows(IllegalArgumentException.class, () -> discountService.applyDiscount(1_000_001)); // 최대 한도 초과
    }
}

 

 

총 정리 point

테스트 코드는 소프트웨어 개발에서 선택이 아닌 필수이다. 이는 단순한 오류 검출 도구를 넘어 코드의 신뢰성과 유지보수를 책임지는 강력한 수단이다.
Assertions를 활용해 명확하고 직관적인 테스트를 작성하고, 예외 상황, 경계 값, 컬렉션 처리 등 다양한 시나리오를 다룰 수 있어야 한다.

이러한 테스트 코드는 코드 품질 향상과 더불어 팀 협업에서도 중요한 역할을 한다. 지속적으로 테스트 코드를 개선하고 학습하여 더욱 견고한 소프트웨어를 개발해야 한다.

댓글