ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JUnit] JUnit 핵심 (2) 중첩 테스트 / 태그 테스트 / 단언문 / 가정문
    DEV/JUnit 2024. 6. 20. 21:59

    - 중첩 테스트 : @Nested 애노테이션을 사용하여 내부 클래스와 외부 클래스 중첩 테스트

    • 개발자가 비즈니스 로직을 잘 따르게 하고, 분명한 테스트 코드를 작성하도록 유도하여 개발자가 테스트 프로세스에 자연스럽게 적응하도록 만든다.
    • 결합도 관점에서 개발자가 테스트 그룹 간의 관계를 표현하는 데에도 도움이 된다.
    package com.study.junit.ch02;
    
    import static org.junit.jupiter.api.Assertions.*;
    import org.junit.jupiter.api.Nested;
    import org.junit.jupiter.api.Test;
    
    import java.text.ParseException;
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    
    public class NestedTestsTest { //메인 테스트
        
        //모든 중첩 테스트에 사용할 고객의 이름과 성을 선언
        private static final String FIRST_NAME = "John";
        private static final String LAST_NAME = "Smith";
    
        @Nested
        class BuilderTest { //중첩 테스트
    
            private String MIDDLE_NAME = "Michael";
    
            @Test
            void customerBuilder() throws ParseException { //빌더 패턴을 활용하여 Customer객체를 제대로 생성했는지 검증
                DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("MM-dd-yyyy");
                LocalDate customerDate = LocalDate.parse( "04-21-2019", dateFormatter);
    
                Customer customer =  new Customer.Builder(Gender.MALE, FIRST_NAME, LAST_NAME)
                        .withMiddleName(MIDDLE_NAME)
                        .withBecomeCustomer(customerDate)
                        .build();
                
                assertAll(() -> {
                    assertEquals(Gender.MALE, customer.getGender());
                    assertEquals(FIRST_NAME, customer.getFirstName());
                    assertEquals(LAST_NAME, customer.getLastName());
                    assertEquals(MIDDLE_NAME, customer.getMiddleName());
                    assertEquals(customerDate, customer.getBecomeCustomer());
                });
            }
    
        }
    
    }

     

     

    - 태그 테스트 : 테스트 클래스와 테스트 메서드에 @Tag 애노테이션을 사용하여 테스트 실행시 필터를 걸수있다.

    • JUnit4의 category를 대체
    • 테스트를 카테고리로 범주화 할 때 사용
    package com.study.junit.ch02;
    
    import org.junit.jupiter.api.Tag;
    import org.junit.jupiter.api.Test;
    import static org.junit.jupiter.api.Assertions.*;
    
    @Tag("individual")
    public class CustomerTest {
        private String CUSTOMER_NAME = "John Smith";
    
        @Test
        void testCustomer() {
           Customer customer = new Customer(CUSTOMER_NAME);
           assertEquals("John Smith", customer.getName());
        }
    }

     

    이와 같이 클래스에 태그를 적용 시킨 후

     

    인텔리제이 상단에서 Edit Configurations... 클릭 후 JUnit 추가하여 태그를 설정하면 해당 태그가 걸려 있는 테스트만 실행 할 수 있다.

     

     

    - 단언문 : Assertions 클래스에서 결괏값을 검증하기위한 메서드를 제공한다.

    단언문 메서드 활용 목적
    assertAll 안에 있는  executable 객체 중 어느것도 예외를 던지지 않는다고 단언한다.
    assertArraysEquals 예상 배열과 실제 배열이 동등하다고 단언한다.
    assertEquals 예상 값과 실제 값이 동등하다고 단언한다.
    assertX(..., String message) 실패했을 경우 message를 테스트 프레임워크에 전달하는 단언문이다.
    assertX(..., Supplier<String> messageSupplier) 실패했을 경우 messageSupplier를 테스트 프레임워크에 전달하는 단언문이다. 실패 메시지는 messageSupplier에서 지연전달 된다.

     

    JUnit5는 과거에 Hamcrest 매처와 함께 사용했던 assertThat 메서드는 더이상 지원하지 않는다.

    권장하는 방식은 MatcherAssert.assertThat을 오버로딩한 메서드를 사용하는 것이다.

     

    assertAll 메서드의 좋은 점은 일부 단언문이 실패하더라도 모든 단언문을 항상 검증한다.

    JUnit4에서는 여러개 assert메서드 중 하나가 실패하면 그 실패로 인해 전체 메서드가 중단됐다.

     

    package com.study.junit.ch02;
    
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;
    import static org.junit.jupiter.api.Assertions.*;
    
    public class AssertAllTest {
        @Test
        @DisplayName("기본적으로 테스트 대상 시스템은 검증하지 않는다.")
        void testSystemNotVerified() {
            SUT systemUnderTest = new SUT("테스트 대상 시스템");
    
            assertAll("테스트 대상 시스템을 검증하지 않았는지 확인", //아래 단언문중 예외 발생시 메시지
                    () -> assertEquals("테스트 대상 시스템",systemUnderTest.getSystemName()),
                    () -> assertFalse(systemUnderTest.isVerified()));
        }
    
        @Test
        @DisplayName("테스트 대상 시스템을 검증한다")
        void testSystemUnderVerification() {
            SUT systemUnderTest = new SUT("테스트 대상 시스템");
    
            systemUnderTest.verify();
    
            assertAll("테스트 대상 시스템을 검증했는지 확인", //아래 단언문중 예외 발생시 메시지
                    () -> assertEquals("테스트 대상 시스템", systemUnderTest.getSystemName()),
                    () -> assertTrue(systemUnderTest.isVerified()));
        }
    }

     

    Supplier<String>을 사용하면 테스트가 성공했을 때 오류 메시지를 만들지 않는다.

    이런식으로 람다식이나 메서드 참조를 사용하여 시스템을 검증하면 성능을 조금 높일 수 있다.

        @Test
        @DisplayName("테스트 대상 시스템을 검증한다")
        void testSystemUnderVerification() {
            SUT systemUnderTest = new SUT("테스트 대상 시스템");
    
            systemUnderTest.verify();
    
            /*
            assertAll("테스트 대상 시스템을 검증했는지 확인", //아래 단언문중 예외 발생시 메시지
                    () -> assertEquals("테스트 대상 시스템", systemUnderTest.getSystemName()),
                    () -> assertTrue(systemUnderTest.isVerified()));
    
             */
    
            assertTrue(systemUnderTest.isVerified(),
                    () -> "테스트 대상 시스템을 검증했는지 확인");
    
        }
    
        @Test
        @DisplayName("테스트 대상 시스템을 검증하지 않았다")
        void testSystemNotUnderVerification() {
            SUT systemUnderTest = new SUT("테스트 대상 시스템");
            assertFalse(systemUnderTest.isVerified(),
                    () -> "테스트 대상 시스템을 검증하지 않았는지 확인");
        }
    
        @Test
        @DisplayName("현재 테스트 대상 시스템은 작업이 없다")
        void testNoJob() {
            SUT systemUnderTest = new SUT("테스트 대상 시스템");
            assertNull(systemUnderTest.getCurrentJob(),
                    () -> "테스트 대상 시스템은 현재 작업이 없는지 확인");
        }

     

    각각의 단언문이 실패하면 메시지가 지연전달된다.

    이처럼 단언문에서 람다식을 파라미터로 사용할 때의 이점은 지연 전달 덕분에 성능이 향상된다.

     

    JUnit4의 Timeout rule을 대체하는 assertTimeout, assertTimeoutPreemptively 메서드는

    시스템의 성능이 충분한지 확인 할 수 있다. 즉 제한 시간내에 작업을 수행할 수 있는지 검증할 수 있다.

     

    package com.study.junit.ch02;
    
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;
    
    import java.time.Duration;
    
    import static org.junit.jupiter.api.Assertions.*;
    
    public class AssertTimeoutTest {
        private SUT systemUnderTest = new SUT("테스트 대상 시스템");
    
        @Test
        @DisplayName("작업을 마칠 때까지 기다리는 assertTimeout 메서드")
        void testTimeout() throws InterruptedException {
            systemUnderTest.addJob(new Job("Job 1"));
            assertTimeout(Duration.ofMillis(500), () -> systemUnderTest.run(600));
        }
    
        @Test
        @DisplayName("시간이 지나면 작업을 중지시키는 assertTimeoutPreemptively 메서드")
        void testTimeoutPreemptively() throws InterruptedException {
            systemUnderTest.addJob(new Job("Job 1"));
            assertTimeoutPreemptively(Duration.ofMillis(500), () -> systemUnderTest.run(600));
        }
    }

     

    assertTimeout 메서드는 executable 객체가 작업을 마칠 때까지 기다린다.

    만약 테스트가 주어진 시간을 초과하면 테스트가 얼마나 늦어졌는지 알려준다.

     

    assertTimeoutPreemptively 메서드는 시간이 지나면 executable 객체를 중지시킨다.

    만약 테스트가 실패한다면 지정한 시간 안에 테스트가 완료되지 못했다고 알려준다.

     

    예외가 발생할 만한 테스트를 수행할 때는 assertThrows 메서드를 사용한다.

    JUnit4에서 @Test 애노테이션 안에 쓰이는 ExpectedException rule을 대체한다.

     

    assertThrows 메서드는 예외가 발생했을 때 Throwable 객체를 반환한다.

    개발자는 Throwable 객체를 단언문으로 검증할 수 있다.

    package com.study.junit.ch02;
    
    import org.junit.jupiter.api.DisplayName;
    import org.junit.jupiter.api.Test;
    import static org.junit.jupiter.api.Assertions.*;
    
    public class AssertThrowsTest {
        private SUT systemUnderTest = new SUT("테스트 대상 시스템");
    
        @Test
        @DisplayName("예외가 발생하는지 검증한다")
        void testExpectedException() {
            //run 메서드 호출시 NoJobException이 발생하는지 검증
            assertThrows(NoJobException.class, systemUnderTest::run);
        }
    
        @Test
        @DisplayName("예외가 발생하고 예외에 대한 참조가 유지되는지 검증한다")
        void testCatchException() {
            //systemUnderTest.run(1000) 문장이 NoJobException을 던지는지 검증, throwable 예외에 대한 참조가 유지되었는지도 검증 
            Throwable throwable =  assertThrows(NoJobException.class,() -> systemUnderTest.run(1000));
            
            //에러메시지가 다음 문장과 일치하는지 검증
            assertEquals("테스트 대상 시스템은 현재 작업이 없는지 확인",
                    throwable.getMessage());
        }
    }

     

     

    - 가정문 : 테스트를 수행하는 데 필수인 전제 조건이 충족되었는지 검증한다.

    • 테스트 리포트에서 가정문에 의해 중단된 테스트는 실패한 것으로 처리한다.
    • Assumptions 패키지에 속하는 정적 메서드다.
    • JUnit4의 assumeThat 대신 assumingThat 메서드를 제공한다.
    package com.study.junit.ch02;
    
    import static org.junit.jupiter.api.Assumptions.*;
    import static org.junit.jupiter.api.Assertions.*;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    
    public class AssumptionTest {
        private static String EXPECTED_JAVA_VERSION = "1.8";
        private TestsEnvironment environment = new TestsEnvironment(
                new JavaSpecification(
                        System.getProperty("java.vm.specification.version")
                ),
                new OperationSystem(
                        System.getProperty("os.name"),
                        System.getProperty("os.arch")
                )
        );
    
        private  SUT systemUnderTest = new SUT();
    
        @BeforeEach //각 테스트가 실행 되기 전 실행되는 메서드로 OS가 window인지 확인한다.
        void setUp() {
            assumeTrue(environment.isWindows());
        }
    
        @Test
        void testNoJobToRun() {
            assumingThat(
                    //자바 버전이 1.8인지 검증한다.
                    () -> environment.getJavaVersion().equals(EXPECTED_JAVA_VERSION),
                    //자바 버전이 1.8일때만 현재 실행중인 작업이 없음을 검증한다.
                    () -> assertFalse(systemUnderTest.hasJobToRun())
            );
        }
        
        @Test
        void testJobToRun() {
            //현재 아키텍처가 사전에 가정한 환경인지 검증한다.
            assumeTrue(environment.isAmd64Architecture());
            //아키텍처가 AMD64인 경우에만 시스템에서 새로운 작업을 수행한다.
            systemUnderTest.run(new Job("Job 1"));
            //그리고 시스템에 실행할 작업이 있는지 검증한다.
            assertTrue(systemUnderTest.hasJobToRun());
        }
    }

     

    해당 예제는 가정문에 의해 윈도우 OS와 자바 8에서만 실행되는 테스트이다.

    댓글

Designed by Tistory.