Post

테스트 주도 개발 시작하기 리뷰 - 4~6장

테스트 주도 개발 시작하기 리뷰 - 4~6장

들어가며

이 포스트는 최범균의 「테스트 주도 개발 시작하기」 3장을 읽고 개인적으로 학습한 내용을 정리한 글입니다.

  • 책: 테스트 주도 개발 시작하기
  • 저자: 최범균
  • 출판사: 가메출판사
  • 챕터: 4~6장

핵심 개념 요약

4장 TDD-기능 명세,설계

기능 명세

  • 사용자에게 제공할 기능을 구현하려면 기능을 두 가지로 구분 할 수 있음
    • 입력 : 입력값 (ex. id/pw)
    • 결과 : 결과값 -> 성공 - 실패 - 변경

설계 과정을 지원하는 TDD

  • TDD는 테스트를 먼저 만듦
  • 테스트 코드를 만들기 위해 필요한 것
    • 테스트할 기능을 실행 : 기능이 없으면 실행할 수 없음 => 테스트에서 실행할 수 있는 객체나 함수가 있어야함
    • 실행 결과를 검증 : 테스트는 기능을 올바르게 동작하는지 검증하는 것이므로 실행 결과를 검증

필요한 만큼 설계하기

  • TDD는 테스트를 통과할 만큼만 코드를 작성
  • 이를 설계에 적용해 실제 테스트 사례를 추가하고 통과시키는 과정에서 필요한 만큼 설계를 변경
  • TDD로 개발을 진행하면 현시점에서 테스트를 통과시키는 데 필요한 만큼의 코드만 만들게 된다

기능 명세 구체화

  • 테스트 코드는 구체적인 입력과 결과를 이용해 작성하므로 개발자는 예를 통해 기능 명세를 구체화하게 된다
  • 모호한 상황을 만나면 이를 구체적인 예로 바꾸어 테스트 코드에 반영 -> 테스트 코드는 예를 이용해 구체적인 명세가 된다
  • 테스트 코드는 바로 실행할 수 있다. 테스트 코드를 이용하면 구체적인 예를 이용해 기능을 바로 실행해 볼 수 있다
  • 복잡한 로직을 구현해야 하는 것은 결국 개발자이므로 개발자는 최대한 예외적인 상황이나 복잡한 상황에 해당하는 구체적인 예를 끄집어내야 한다

5장 JUnit 5 기초

JUnit 5 모듈 구성

  • JUnit 5의 구성 요소
    • JUnit 플랫폼 : 테스팅 프레임워크를 구동하기 위한 런처와 테스트 엔진을 위한 API를 제공
    • JUnit Jupiter : JUnit 5를 위한 테스트 API와 실행 엔진을 제공한다
    • JUnit Vintage : JUnit 3,4로 작성된 테스트를 JUnit 5 플랫폼에서 실행하기 위한 모듈을 제공

@Test 애노테이션과 테스트 메서드

  • Test 어노테이션
1
2
3
4
5
6
7
8
9
10
11
12
import org.junit.jupiter.api.Test;

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

public class SumTest {

    @Test
    void sum(){
        int result = 2 + 3;
        assertEquals(5, result);
    }
}

주요 단언 메서드

메서드설명
assertEquals(expected,actual)실제 값이 기대하는 값과 같은지 검사
assertNotEquals(expected,actual)실제 값이 특정 값과 같지 않은지 검사
assertSame(Object expected, Object actual)두 객체가 동일한 객체인지 검사
assertNotSame(Object umexpected, Object actual)두 객체가 동일하지 않은 객체인지 검사
assertTrue(boolean condition)값이 true인지 검사
assertFalse(boolean condition)값이 false인지 검사
assertNull(Object actual)값이 null인지 검사
assertNotNull(Object actual)값이 null이 아닌지 검사
fail()테스트를 실패 처리

Assertions가 제공하는 익셉션 발생 유무 검사 메소드

메서드설명
assertThrows(Class<T> expectedType, Executable executable)executable 실행 시 expectedType 익셉션이 발생하는지 검사
assertDoesNotThrow(Executable executable)executable 실행 시 익셉션이 발생하지 않는지 검사

테스트 라이프사이클

@BeforeEach 애노테이션과 @AfterEach 애노테이션

  • Junit의 테스트 코드 실행 순서
    1. 테스트 메서드를 포함한 객체 생성
    2. (존재하면) @BeforeEach 애노테이션이 붙은 메서드 실행
    3. @Test 애노테이션이 붙은 메서드 실행
    4. (존재하면) @AfterEach 애노테이션이 붙은 메서드 실행
  • @BeforeEach 애노테이션
    • 테스트를 실행하는데 필요한 준비 작업을 할 때 사용
    • 테스트에서 사용할 임시 파일을 생성하거나 테스트 메서드에서 사용할 객체를 생성
  • @AfterEach 애노테이션
    • 테스트를 실행한 후에 정리할 것이 있을 때 사용
    • 테스트에서 사용한 임시 파일을 삭제해야 할 때 사용

@BeforeAll 애노테이션과 @AfterAll 애노테이션

  • @BeforeAll 애노테이션
    • 한 클래스의 모든 테스트 메서드가 실행되기 전에 특정 작업 수행
    • 이 메서드는 클래스의 모든 테스트 메서드를 실행하기 전에 한 번 실행
  • @AfterAll 애노테이션
    • 클래스의 모든 테스트 메서드를 실행한 뒤에 실행

테스트 메서드 간 실행 순서 의존과 필드 공유하지 않기

  • 코드 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BadTest {
    private FileOperator op = new FileOperator();
    private static File file;

    @Test
    void fileCreationTest(){
        File createdFile = op.createFile();
        assertTrue(createdFile.length() > 0);
        this.file = createdFile;
    }

    @TEst
    void readFileTest(){
        long data = op.readData(file);
        assertTrue(data > 0);
    }
}
  • 이 테스트는 fileCreationTest() 메서드가 readFileTest() 메서드보다 먼저 실행된다는 것을 가정
  • 하지만 테스트 메서드가 특정 순서대로 실행된다는 가정하에 테스트 메서드를 작성하면 안된다
  • Junit이 테스트 순서를 결정하긴 하지만, 그 순서는 버전에 따라 달라져 readFileTest()가 먼저 실행되면 file 필드가 null이라 테스트가 실패한다
  • 테스트 메서드는 서로 독립적으로 동작해야 한다.

추가 애노테이션: @DisplayName, @Disabled

  • @DisplayName 애노테이션
    • @DisplayName 애노테이션을 사용해 테스트에 표시 이름을 붙일 수 있다
  • @Disabled 애노테이션
    • 특정 테스트를 실행하지 않고 싶을 때 사용

모든 테스트 실행하기

  • 모든 테스트를 실행하는 방법
    • mvn test
    • gradle test

6장 테스트 코드의 작성

테스트 코드의 구성 요소 : 상황, 실행, 결과 확인

  • 기능은 상황에 따라 결과가 달라진다
  • 테스트의 구성 요소
    • 상황, 실행, 결과
    • 어떤 상황이 주어지고, 그 상황에서 기능을 실행하고, 실행한 결과를 확인하는 세 가지가 테스트 코드의 기본 골격
  • 상황-실행-결과 확인 구조에 너무 집착하지는 말자. 이 구조가 테스트 코드를 작성하는 데 도움이 되는 것은 맞지만 꼭 모든 테스트 메서드를 이 구조로 만들어야 하는 것은 아니다

외부 상황과 외부 결과

  • 항상 테스트에 성공할 것이라는 보장은 없다

외부 상태가 테스트 결과에 영향을 주지 않게 하기

  • TDD를 진행하는 동안에도 테스트 코드는 계속 실행하고 개발이 끝난 이후에도 반복적으로 테스트를 실행해서 문제가 없는지 검증 => 테스트는 언제 실행해도 항상 정상적으로 동작하는 것이 중요
  • 간헐적으로 실패하거나 다른 테스트 다음에 실행해야 송공하면 테스트 결과를 믿을 수 없게 된다

외부 상태와 테스트 어려움

  • 테스트 대상이 아닌 외부 요인은 테스트 코드에서 다루기 힘든 존재
  • 외부 상황은 테스트 코드에서 마음대로 제어할 수 없는 경우가 있다
  • 테스트 코드에서 생성한 외부 결과를 마음대로 초기화하기 힘들 때도 있다
  • 테스트 대상의 상황과 결과에 외부 요인이 관여할 경우 대역을 사용하면 테스트 작성이 쉬워진다
This post is licensed under CC BY 4.0 by the author.