본문 바로가기
WEB/Spring

TDD(테스트 주도 개발, Test-Driven Development), Junit

by 정권이 내 2024. 11. 5.

TDD(테스트 주도 개발, Test-Driven Development) & Junit

 

TDD(Test-Driven Development)

TDD는 "테스트를 먼저 작성하고 코드를 구현하는 방식"으로, 소프트웨어 개발을 더욱 견고하고 신뢰성 있게 만드는 방법론입니다. TDD의 핵심 과정은 반복적으로 테스트 작성 -> 코드 구현 -> 리팩토링을 통해 코드를 점진적으로 개선해 나가는 것입니다.

 

TDD 개발 프로세스

img

Red: 실패하는 테스트 작성

  • 먼저 실패하는 테스트 코드를 작성합니다. 이 단계에서 JUnit을 활용해, 단위 테스트 메서드를 작성하고 이 테스트가 실행되었을 때 실패하도록 합니다.
  • 실패를 통해 "구현되지 않은 기능"을 명확히 인지하고, 필요한 기능에 대한 요구사항을 확인하게 됩니다.
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;

public class CalculatorTest {

    @Test
    void testAdd() {
        Calculator calculator = new Calculator();
        // 이 코드는 실패: add 메서드가 아직 없거나 메서드 내부에 기능이 구현되지 않음
        assertEquals(5, calculator.add(2, 3));
    }
}

 

Green: 테스트를 통과하는 최소한의 코드 작성

  • 테스트가 성공하도록 하는 최소한의 코드를 작성합니다. 이때는 가독성이나 최적화보다는 테스트를 통과시키는 것에 집중합니다.
  • 이 단계가 끝나면 JUnit 테스트를 실행했을 때 모든 테스트가 통과하는 초록색 성공 표시를 확인할 수 있습니다.
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

 

Refactor: 코드 개선 및 리팩토링

  • 테스트가 통과되면, 이제 코드의 가독성과 성능을 개선합니다.
  • JUnit 테스트를 다시 실행해 리팩토링 후에도 기능이 정상 작동하는지 확인합니다.

 

Junit, 단위 테스트

img

JUnit는 Java에서 가장 널리 사용되는 단위 테스트 프레임워크 중 하나로, 코드의 특정 부분이 예상대로 작동하는지 검증하는 데 사용됩니다.

JUnit은 주로 테스트 메서드를 작성하고 실행하는 기능을 제공하여 개발자가 개별 메서드나 클래스의 동작을 독립적으로 테스트할 수 있도록 해줍니다. 특히 JUnit은 단위 테스트뿐만 아니라 통합 테스트에서도 널리 사용됩니다.

 

Given, When, Then 패턴

img

  • JUnit에서 Given-When-Then 패턴은 단위 테스트를 작성할 때 코드를 더 명확하고 읽기 쉽게 작성하도록 도와주는 구조입니다. 테스트 시나리오를 세 단계로 나누어 기대하는 결과를 예측하고 확인할 수 있게 합니다.

 

1. Given (준비)

  • 테스트에 필요한 상황을 설정하는 단계입니다.
  • 테스트 대상 객체를 생성하고, 필요한 초기 상태나 조건을 설정합니다.
// Given
Calculator calculator = new Calculator();
int a = 5;
int b = 3;

 

2. When (실행)

  • 테스트할 동작을 수행하는 단계입니다.
  • 보통 하나의 메서드를 호출하거나 특정한 행위를 수행하는 부분이 여기에 들어갑니다.
// When
int result = calculator.add(a, b);

 

3. Then (검증)

  • 테스트 결과가 예상과 일치하는지 검증하는 단계입니다.
  • assertEquals, assertTrue 등과 같은 JUnit의 검증 메서드를 사용하여 실제 결과가 예상 결과와 같은지 확인합니다.
// Then
assertEquals(8, result);

 

전체 코드

@Test
void testAdd() {
    // Given: 초기 상태를 설정
    Calculator calculator = new Calculator();
    int a = 5;
    int b = 3;

    // When: 특정 동작 수행
    int result = calculator.add(a, b);

    // Then: 결과 확인
    assertEquals(8, result);
}

 

Junit 애노테이션

  • 단위테스트를 수행하기 위해 사용하는 @Test 애노테이션 외에도 유용한 애노테이션들이 있습니다. 테스트 과정에서 중복 코드를 줄이고 가독성을 향상 시키는데 도움을 줍니다.

 

1. @BeforeEach: 각각의 테스트 메서드가 수행되기전에 수행

  • 테스트 메서드에서 사용되는 객체를 초기화 하는데 유용합니다.

 

아래 코드에서는 각 테스트 메서드가 실행되기 전에 setup() 메서드가 호출됩니다. 두 개의 테스트가 각각 실행될 때마다 Calculator 인스턴스가 초기화됩니다.

@BeforeEach
void setup() {
    // 테스트마다 Calculator 인스턴스 생성
    calculator = new Calculator();
}

@Test
void testAdd() {
    int result = calculator.add(2, 3);
    assertEquals(5, result);
}

@Test
void testSubtract() {
    int result = calculator.subtract(5, 3);
    assertEquals(2, result);
}

 

2. @BeforeAll: 모든 테스트가 실행되기 전, 한 번만 수행

  • 일반적으로 무거운 리소스를 초기화하거나, 테스트 클래스 내에서 한 번만 생성되어야 하는 공유 자원이 있을 때 사용됩니다.
  • @BeforeAll 메서드는 static으로 선언해야 합니다.
@BeforeAll
static void globalSetup() {
    // 테스트 DB 연결 설정
    databaseConnection = new DatabaseConnection();
    databaseConnection.connect();
}

 

3. @AfterEach: 각 테스트 메서드가 수행된 후에 수행

  • 주로 테스트 중 변경된 리소스를 정리하거나, 데이터베이스에서 데이터 삭제 등 상태를 원래대로 돌릴 때 사용됩니다.
@AfterEach
void tearDown() {
    // Calculator 인스턴스를 null로 설정해 해제
    calculator = null;
}

 

4. @AfterAll: 모든 테스트가 완료되고 한 번만 수행

  • 주로 @BeforeAll에서 설정한 리소스를 정리하거나, 전체 테스트 종료 후 수행해야 할 정리 작업을 넣습니다.
  • @AfterAll 메서드도 static으로 선언해야 합니다.
@AfterAll
static void globalTearDown() {
    // 테스트 DB 연결 해제
    databaseConnection.disconnect();
}

 

Assertions, 테스트 검증

  • Assertions는 테스트의 예상 결과와 실제 결과를 비교해 성공, 실패를 판단합니다.
  • JUnit에서 제공하는 assertEquals, assertTrue, assertFalse, assertNotNull 같은 메서드를 사용하여 테스트 결과를 검증할 수 있습니다.

 

1. assertEquals(expected, actual)

  • 예상 값과 실제 값이 같은지를 확인합니다.
@Test
void testAddition() {
    int result = Calculator.add(2, 3); // add 메서드는 2 + 3을 계산한다고 가정
    assertEquals(5, result, "2 + 3은 5가 되어야 합니다.");
}

 

2. assertTrue(condition), assertFalse(condition)

  • 조건이 참(true) 혹은 거짓(false) 인지 확인합니다.
@Test
void testIsPositive() {
    int number = 10;
    assertTrue(number > 0, "number는 양수여야 합니다.");
}
@Test
void testIsNegative() {
    int number = -5;
    assertFalse(number >= 0, "number는 음수여야 합니다.");
}

 

3. assertNotNull(object)

  • 객체가 null이 아님을 확인합니다.
@Test
void testFindUser() {
    User user = userService.findUserById(1); // ID가 1인 사용자 검색
    assertNotNull(user, "ID가 1인 사용자는 존재해야 합니다.");
}

 

4. assertThrows

  • 특정 예외가 발생하는지를 확인합니다. 예상된 예외가 발생할 경우에만 성공하도록 설계되어 있습니다.
  • 코드가 예상대로 잘못된 상황에서 예외 처리 로직을 테스트하는 데 유용합니다.
@Test
void testExceptionThrowing() {
    // 예외를 던질 것으로 예상하는 코드 블록을 assertThrows로 감싸기
    assertThrows(IllegalArgumentException.class, () -> {
        // 테스트할 코드 (예외를 던질 코드)
        myService.someMethod(null);
    });
}

 

  • assertThrows 는 던져진 예외 객체를 반환하므로, 이 예외를 이용해 예외 메시지나 기타 정보를 추가로 검증할 수 있습니다.
@Test
void testExceptionWithMessage() {
    Exception exception = assertThrows(IllegalArgumentException.class, () -> {
        myService.someMethod(null);
    });

    // 예외 메시지 확인
    assertEquals("Invalid input", exception.getMessage());
}

 

반복 테스트, @ParameterizedTest & @ValueSource

  • @ParameterizedTest, @ValueSource 는 Junit에서 반복 테스트를 할수 있도록 하는 애노테이션 입니다.
  • 다양한 입력값으로 반복해 검증할 때 사용되며, 테스트 코드의 효율성과 가독성을 높이는 데 유용합니다.

 

@ParameterizedTest

  • @Test 대신 사용되며, 여러 입력값을 받아 같은 테스트를 반복 실행합니다.

 

@ValueSource

  • @ParameterizedTest와 함께 사용되며, 다양한 단일 매개변수를 간편하게 제공하는 역할을 합니다.
  • 기본적으로 int, double, String, long, short, char, byte, boolean 배열을 인수로 받을 수 있습니다.

 

예제 코드

@ParameterizedTest
@ValueSource(strings = {"apple", "banana", "cherry"})
void testWithMultipleValues(String fruit) {ㄴ
    assertTrue(fruit.length() > 0);
}
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void testWithIntValues(int number) {
    assertTrue(number > 0);
}
반응형

댓글