스프링 - 테스트 사용 방법

2024. 8. 22. 23:01스프링

2024/08/22

 

 

※   단위 테스트란 무엇일까?

 

 

   ▶  단위 테스트란?

 

👉  버그 발견 시간이 늦어짐에 따라 비용이 기하급수적으로 커지는 걸 알 수 있다.

        

                   1.  Development : 개발
                   2.  Unit Tests (단위 테스트) : 개발자 테스트 
                   3.  QA Testing :
                        ●  블랙박스 테스팅
                        ●  주로 QA 팀이 Production 환경과 유사한 환경(Stage)에서 테스팅

블랙박스 테스트
        소프트웨어의 내부 동작 및 코드에 대한 지식 없이 외부의 입력에 대한 출력을 테스트하는 방법
        설계기법
           등가 분할 : 입력값을 여러 그룹으로 나누고, 각 그룹에 속하는 입력값들이 유사한 동작을 보일 것으로 
                                예상될 때 한 그룹으로 테스트하는 방법
           경계 값 분석 : 입력값의 경계 부분에서 오류가 가장 자주 발생할 가능성이 높다는 개념에
                                     기반한 테스트 기법

           원인-효과 그래픽 : 시스템의 입력값과 결과 사이의 관계를 그래픽 형태로 표현한 다이어그램을
                                            사용하여 테스트 케이스를 도출하는 방법
           상태 전이 : 주로 상태를 가지는 시스템을 테스트할 때 사용되며 시스템이 다양한 상태에서 어떻게
                                동작하는지를 테스트
           테이블 기반 테스트 : 입력값과 예상되는 출력값을 테이블 형태로 정리한 다음, 이를 기반으로
                                                테스트 케이스를 생성. 다양한 상황을 체계적으로 테스트 가능
           사용자 시나리오 :  실제 사요앚가 소프트웨어를 사용할 때 예상되는 시나리오를 기반으로 
                                            테스트 케이스 작성. 실제 사용 환경에서 발생할 수 있는 문제 예방 및 확인
        중요성
        1. 사용자 중심 테스트
        2. 다양한 시나리오 검증
        3. 외부 의존성 검증
        4. 빠른 피드백 제공


                   4. Production : 실 서비스 운영 환경
                        ●  따라서 테스트 코드를 작성한다면 프로그램의 버그를 사전에 발견하여
                            기하급수적인 비용의 증가가능성을 사전에 방지할 수 있다.
                        ●  개발자는 단위 테스트를 작성하여 프로그램을 테스트 할 수 있다.
                             단위 테스트작은 단위로 쪼개서 각 단위가 정확하게 동작하는지를 검사하는 테스트 기법
                             단위 테스트는 1)빠르게 작성할 수 있고 2)문제 발생 시 어느 부분이 잘못 되었는지 빠르고
                               정확하게 확인할 수 있다는 장점이 있다.

 

   ▶  JUnit5 다루기

                  ●  자바 프로그래밍 언어용 단위 테스트 프레임워크JUnit5를 다뤄보자.

JUnit5 User Guide
      https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

                   1.  프로젝트 생성하기(IntelliJ 기준)
                             프로젝트 기본 설정
                              ▪  Name : junit5-practice
                              ▪  Language : Java
                              ▪  Build system : Gradle - Groovy
                              ▪  Group : 자유롭게
                              ▪  JDK : 17
                              ▪  프로젝트 경로(Location) : 자유롭게
                   2.  Before - After
                             @BeforeEach 

@BeforeEach
void setUp() {
    System.out.println("각각의 테스트 코드가 실행되기 전에 수행");
}

                              ▪  각각의 테스트 코드가 실행되기 전에 수행되는 메서드를 만들어 준다.

                             @AfterEach 

@AfterEach
void tearDown() {
    System.out.println("각각의 테스트 코드가 실행된 후에 수행\n");
}

                              ▪  각각의 테스트 코드가 실행되기 후에 수행되는 메서드를 만들어 준다.


                           ○  @BeforeAll 

@BeforeAll
static void beforeAll() {
    System.out.println("모든 테스트 코드가 실행되기 전에 초초로 수행\n");
}

                              ▪  모든 테스트 코드가 수행되기 전에 최초로 수행되는 메서드를 만들어 준다.
                              ▪  static 메서드 로 만들어야 한다.



                             @AfterAll 

@AfterAll
static void afterAll() {
    System.out.println("모든 테스트 코드가 수행된 후 마지막으로 수행");
}

                              ▪  모든 테스트 코드가 수행된 후 마지막으로 수행되는 메서드를 만들어 준다.
                              ▪  static 메서드 로 만들어야 한다.



                   3.  테스트 꾸미기

                             @DisplayName 

@Test
@DisplayName("테스트의 내용을 한눈에 알아볼 수 있게 네이밍 해줄 수 있습니다.")
void test1() {
    System.out.println("테스트의 수행 내용들을 빠르게 파악할 수 있습니다.");
}

                              ▪  테스트의 내용을 한눈에 알아볼 수 있게 네이밍 해줄 수 있다.
                              ▪  테스트의 수행 내용들을 빠르게 파악할 수 있다.

                             @Nested 

@Nested
@DisplayName("주제 별로 테스트를 그룹지어서 파악하기 좋습니다.")
class Test1 {
    @Test
    @DisplayName("Test1 - test1()")
    void test1() {
        System.out.println("Test1.test1");
    }

    @Test
    @DisplayName("Test1 - test2()")
    void test2() {
        System.out.println("Test1.test2");
    }
}

@Nested
@DisplayName("Test2 다른 주제")
class Test2 {
    @Test
    @DisplayName("Test2 - test1()")
    void test1() {
        System.out.println("Test2.test1");
    }

    @Test
    @DisplayName("Test2 - test2()")
    void test2() {
        System.out.println("Test2.test2");
    }
}

                              ▪  주제 별로 테스트를 그룹지어서 파악하기 좋다.

                             @Order 

@Nested
@DisplayName("주제 별로 테스트를 그룹지어서 파악하기 좋습니다.")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class Test1 {

    @Order(1)
    @Test
    @DisplayName("Test1 클래스")
    void test() {
        System.out.println("\nTest1 클래스");
    }

    @Order(3)
    @Test
    @DisplayName("Test1 - test1()")
    void test1() {
        System.out.println("Test1.test1");
    }

    @Order(2)
    @Test
    @DisplayName("Test1 - test2()")
    void test2() {
        System.out.println("Test1.test2");
    }
}

                              ▪  테스트를 메서드 단위로 순서를 매길 때는
                                  @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 애너테이션 설정을 해야한다.
                              ▪  그런 다음 원하는 순서에 맞게 메서드에 @Order(2) 애너테이션을 추가하고
                                  () 괄호안에 순서를 입력한다.

 

                   4.  테스트 반복하기

                             @RepeatedTest 

@RepeatedTest(value = 5, name = "반복 테스트 {currentRepetition} / {totalRepetitions}")
void repeatTest(RepetitionInfo info) {
    System.out.println("테스트 반복 : " + info.getCurrentRepetition() + " / " + info.getTotalRepetitions());
}

                              ▪  @RepeatedTest 를 사용하여 해당 테스트 메서드를 반복할 수 있다.
                              ▪  name 속성을 사용하여 네이밍할 수 있다.
                              ▪  RepetitionInfo값을 파라미터로 받아서 현재 반복 횟수와 총 횟수 값을 확인할 수 있다.


                             @ParameterizedTest 

@DisplayName("파라미터 값 활용하여 테스트 하기")
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5, 6, 7, 8, 9})
void parameterTest(int num) {
    System.out.println("5 * num = " + 5 * num);
}

                              ▪  @ParameterizedTest 를 사용하여 파라미터를 받아 테스트할 수 있는 메서드를 만들 수 있다.
                              ▪  @ValueSource (ints = {1, 2, 3, 4, 5, 6, 7, 8, 9})를 사용하여 파라미터 값을 전달 할 수 있다.
                                ✅ 전달되는 파라미터 수 만큼 테스트 메서드가 수행됨
                                ✅ int, String 등 여러 타입의 파라미터를 전달할 수 있다.


                   5.  Assertions
                      🔎  Calculator를 예시로 살펴보기
                           ○  Calculator

public class Calculator {
    public Double operate(double num1, String op, double num2) {
        switch (op) {
            case "*":
                return num1 * num2;
            case "/":
                if (validateNum(num2)) {
                    return num1 / num2;
                } else {
                    return null;
                }
            case "+":
                return num1 + num2;
            case "-":
                return num1 - num2;
            default:
                throw new IllegalArgumentException("잘못된 연산자입니다.");
        }
    }

    public boolean validateNum(double num) {
        if (num == 0) {
            return false;
        } else {
            return true;
        }
    }
}

 


                           ○  Assertions.assertEquals(expected, actual)

@Test
@DisplayName("assertEquals")
void test1() {
    Double result = calculator.operate(5, "/", 2);
    assertEquals(2.5, result);
}

@Test
@DisplayName("assertEquals - Supplier")
void test1_1() {
    Double result = calculator.operate(5, "/", 0);
    // 테스트 실패 시 메시지 출력 (new Supplier<String>())
    assertEquals(2.5, result, () -> "연산자 혹은 분모가 0이 아닌지 확인해보세요!");
}

@Test
@DisplayName("assertNotEquals")
void test1_2() {
    Double result = calculator.operate(5, "/", 0);
    assertNotEquals(2.5, result);
}

                              ▪ assertEquals() 메서드는 첫 번째 파라미터에 예상값을 넣고 두 번째 파라미터에
                                테스트 결과값(실제값)을 넣어준다.

                              ▪ 예상값과 실제값이 다르면 테스트가 실패한다.
                              ▪ 3번째 파라미터 값에 람다식으로 메시지를 넣어두면 테스트 실패 시
                                 해당 메시지가 출력된다. (new Supplier<String>())


                           ○  Assertions.assertTrue(boolean)

@Test
@DisplayName("assertTrue 와 assertFalse")
void test2() {
    assertTrue(calculator.validateNum(9));
    assertFalse(calculator.validateNum(0));
}

                              ▪ assertTrue() 메서드는 해당 파라미터 값이 true인지 확인한다.


                           ○  Assertions.assertNotNull(actual)

@Test
@DisplayName("assertNotNull 과 assertNull")
void test3() {
    Double result1 = calculator.operate(5, "/", 2);
    assertNotNull(result1);
    Double result2 = calculator.operate(5, "/", 0);
    assertNull(result2);
}

                              ▪ assertNotNull() 메서드는 해당 파라미터 값이 null이 아님을 확인한다.


                           ○  Assertions.assertThrows(expectedType, executable)

@Test
@DisplayName("assertThrows")
void test4() {
    IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> calculator.operate(5, "?", 2));
    assertEquals("잘못된 연산자입니다.", exception.getMessage());
}

                              ▪ assertThrows() 메서드는 첫 번째 파라미터에 예상하는 Exception 클래스 타입을
                               넣고 두 번째 파라미터에 실행 코드를 넣으면 됩니다.

                              ▪ 실행 코드의 결과가 예상한 해당 클래스 타입이라면 테스트에 성공한다.


                   6.  Given - When - Then
                           ○  Given-When-Then 패턴은 Test Code 스타일을 표현하는 방식

                           ○  Given 
                              ▪ 테스트 하고자하는 대상을 실제로 실행하기 전에 테스트에 필요한 값(상태)을
                                미리 선언해 둔다.


                           ○  When 
                              ▪ 테스트 하고자하는 대상을 실제로 실행시킨다.

                           ○  Then 
                              ▪ 어떤 특정한 행동(테스트 대상 실행) 때문에 발생할거라고 예상되는 결과에 대해
                                예측하고 맞는지 확인한다.

class CalculatorTest {

    Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    @DisplayName("계산기 연산 성공 테스트")
    void test1() {
        // given
        int num1 = 5;
        String op = "/";
        int num2 = 2;

        // when
        Double result = calculator.operate(num1, op, num2);

        // then
        assertNotNull(result);
        assertEquals(2.5, result);
    }

    @Test
    @DisplayName("계산기 연산 실패 테스트 : 분모가 0일 경우")
    void test1_1() {
        // given
        int num1 = 5;
        String op = "/";
        int num2 = 0;

        // when
        Double result = calculator.operate(num1, op, num2);

        // then
        assertNull(result);
    }

    @Test
    @DisplayName("계산기 연산 실패 테스트 : 연산자가 잘못됐을 경우")
    void test1_2() {
        // given
        int num1 = 5;
        String op = "?";
        int num2 = 2;

        // when - then
        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> calculator.operate(5, "?", 2));
        assertEquals("잘못된 연산자입니다.", exception.getMessage());
    }
}

 

 

 

 


※ 위 이미지들은 스파르타코딩클럽에 저작권이 있으므로 무단 도용 금지 및 상업 목적으로 사용할 수 없습니다.

 

 

 

 

 

'스프링' 카테고리의 다른 글

스프링 - 통합 테스트란?  (0) 2024.08.26
스프링 - Mockito란?  (0) 2024.08.23
스프링 - JPA Query Methods란 무엇일까?  (0) 2024.08.21
스프링 - JPA Auditing 적용하기  (0) 2024.08.20
스프링 - RestTemplate & Open API  (0) 2024.08.19