JAVA/Test

[1028 from 더 자바 백기선 01] JUnit5

 

JUnit5 1. Jupiter(JUnit3,4) + Vintage(JUnit5)
2. @Test  public 일 필요가 없다(Java Reflection 영향)
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)  클래스 에 작성, 언더바를 공백으로/클래스 하위 적용
@DisplayName("이름 바꾸기 \uD83D\uDE31") 특수문자, 한글 가능
@DisplayNameGeneration 메소드, 클래스 레퍼런스 테스트이름표기, DisplayName 보다 우선순위 낮음
org.junit.jupiter.api.Assertions.*  AssertEquals.assertEquals(expected, actual, message);
 assertNotNull(actual)
assertTure(boolean)
assertThrows(expectedType, executable)
assertTimeout(duration, executable) //끝날때까지 실행완료 후 종료
람다식 이용 assertEquals(StudyStatus.DRAFT, study.getStatus(), ()-> "스터디 처음은 상태 "+ StudyStatus.DRAFT);

//메시지 부분에 연산이 있을때 람다식으로 만들면 연산횟수가 줄어든다. 

// 람다를 안쓰면 무조건 연산하고 람다일때는 실패했을때만 연산함. 

assertTimeout vs assertTimeoutPreemptively

assertTimeoutPreemptively  는 조건시간 되면 종료. 사용주의

assertTimeout(Duration.ofMillis(10), ()->{
new Study(10);
Thread.sleep(1000);
});//끝날때까지 실행완료 후 종료

 

assertTimeoutPreemptively(Duration.ofMillis(10), ()->{
new Study(10);
Thread.sleep(1000);
});//조건 넘으면 종료

//ThreadLocal 사용하는 코드가 있으면 assertTimeoutPreemptively  사용할때 문제 생김. 

// 다른 쓰레드에 공유가 안되서. 롤백이 안되고 DB반영. 

 

org.junit.jupiter.api.Assumptions.*

-assumeTrue(조건)

-assumingThat(조건, 테스트)

@Enabled_와 @Disabled_

@Enabled_와 @Disabled_

-OnOS

-OnJre

-IfSystemProperty

-IfEviromentVariable

-If

@Tag

<plugin> <artifactId>maven-surefire-plugin</artifactId> <configuration> <groups>fast | slow</groups> </configuration> </plugin

fast 일때 실행 or slow 일때 실행. 

커스텀 테그 만들기 @interface FastTest

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Tag("fast") @Test public @interface FastTest{ }

반복테스트

@RepeatedTest

@ParameterizedTest

 
import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

 

Assertion  강한 선언, 주장, 권리행사

 assert 주장하다. 

executables 실현가능한

드레프트 : draft   초안,  복식에서 옷윤곽을 그린 처벌그림, 체육에서 신인선수 선발


백기선님 강의보면서 만든 노트입니다.. 다 직접타이핑이니 오타주의해서보세요~


수업중 사용한 인텔리제이 단축키

Ctrl + Shift + T
테스트 생성

이펜젼시 웹추가
File > Project Structure.
Modules > 프로젝트 > Dependencies > + 클릭 > JARs or directories...
추가할 라이브러리 선택 > OK 클릭
라이브러리 추가 적용 확인 후 OK 클릭


shift + shift 명령어 조회


ctrl + shift +F10 실행


겟터 셋터 단축키 : alt + Insert

임포트 : alt + Enter


1. JUnit

자바개발자가 가장 많이 사용하는 테스팅 프레임워크

- JUnit테스트 작성 자바개발자 93프로 사용.   모키토는 50% 사용 

- JUnit5  > 자바 8 이상  >> 2017년 출시 

-대체제: TestNG, Spack

 

JUnit5 모듈

  • Platform : 테스트를 실행해주는 런처 제공 TestEngine API 제공
  • Jupiter :  TestEngine API 구현체로 JUnit5제공
  • Vintage: JUnit4와 3을 지원하는 TestEngine구매

https://junit.org/junit5/docs/current/user-guide/

 

 

 

2. JUnit5 시작

스프링 부트 2.2 이상은 기본으로 JUnit5의존성 추가됨. 

스프링부트 미사용시 

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.5.2</version>
    <scope>test</scope>
</dependency>

기본애노테이션

- @Test

- @BeforeAll / @AfterAll

- @BeforeEach / @AfterEAch

- @Disabled

 

JUnit5 부터 매서드 Public 일 필요가 없다. 

>>Java Reflection >> privte 에도 접근이 가능해 public 일 필요가 없다. 디버거 테스트 위해 일부실행

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

3. JUnit 5 : 테스트 이름 표시하기

기본 표기는 이름이 표기 >> 메서드는 카멜보다 언더바이용 추천. 

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) >> 언더바를 공백으로/클래스 하위 적용

@DisplayName("이름 바꾸기 \uD83D\uDE31")

 

 

@DisplayNameGeneration

- Method와 Class레퍼런스를 사용해서 테스트 이름을 표기하는 방법 설정

- 기본 구현체로 ReplaceUnderscores 제공

@DisplayName

- 어떤 테스트인지 테스트 이름을 보다 쉽게 표현 할 수 있는 방법을 제공하는 애노테이션

@DisplayNameGeneration 보다 우선 순위가 높다. 

https://junit.org/junit5/docs/current/user-guide/#writing-tests-display-names

4. JUnit5: Assertion

org.junit.jupiter.api.Assertions.*     

실제값이 기대한 값과 같은지 확인 : AssertEquals.assertEquals(expected, actual, message);

값이 null이 아닌지확인 : assertNotNull(actual)

다음 조건이 참(true)인지 확인 : assertTure(boolean)

모든 확인 구문 확인 : assertAll(Executable... executables)   >> 여러개 동시 체크

예외 발생 확인 : assertThrows(expectedType, executable)

특정시간 안에 실행이 완료되는지 확인: assertTimeout(duration, executable)

 

마지막 매개변수로 Supplier<String>타입의 인스턴스를 람다 형태로 제공할 수 있다. 

- 복잡한 메시지 생성해야 하는 경우 사용하면 실패한 경우에만 해당 메시지를 만들게 할 수 있다. 

AssertJ, Hemcrest, Truth 등의 라이브러리를 사용할수도 있다. 

 

assertEquals(StudyStatus.DRAFT, study.getStatus(), new Supplier<String>() {
@Override
public String get() {
return "스터디 처음은 상태 "+StudyStatus.DRAFT;
}
});
assertEquals(StudyStatus.DRAFT, study.getStatus(), ()-> "스터디 처음은 상태 "+ StudyStatus.DRAFT);
//메시지 부분에 연산이 있을때 람다식으로 만들면 연산횟수가 줄어든다. 

// 람다를 안쓰면 무조건 연산하고 람다일때는 실패했을때만 연산함. 

 

assertTimeout(Duration.ofMillis(10), ()->{
new Study(10);
Thread.sleep(1000);
});//끝날때까지 실행완료 후 종료

 

assertTimeoutPreemptively(Duration.ofMillis(10), ()->{
new Study(10);
Thread.sleep(1000);
});//조건 넘으면 종료

//ThreadLocal 사용하는 코드가 있으면 assertTimeoutPreemptively  사용할때 문제 생김. 

// 다른 쓰레드에 공유가 안되서. 롤백이 안되고 DB반영. 

 

cf)

Assertion  강한 선언, 주장, 권리행사

 assert 주장하다. 

executables 실현가능한

드레프트 : draft   초안,  복식에서 옷윤곽을 그린 처벌그림, 체육에서 신인선수 선발

 

Supplier, executable

더보기
package com.web.javademo1028web;

public class Study {

    private StudyStatus status;
    private int limit;

    public Study(int limit) {
        if (limit < 0 ){
            throw new IllegalArgumentException("limit 0 초과");
        }
        this.limit = limit;
        this.status = StudyStatus.DRAFT;
    }

    public StudyStatus getStatus() {
        return this.status;
    }

    public int getLimit() {
        return limit;
    }
}
package com.web.javademo1028web;

public enum StudyStatus {
    DRAFT, STARTED, ENDED
}
package com.web.javademo1028web;

import org.junit.jupiter.api.*;

import java.time.Duration;
import java.util.function.Supplier;

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

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @Test
    void create_test_study(){

        assertTimeoutPreemptively(Duration.ofMillis(10), ()->{
            new Study(10);
            Thread.sleep(1000);
        });//끝날때까지 실행완료 후 종료


        /*IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, ()->new Study(-10));
        String msg = exception.getMessage();
        System.out.println(msg);
        assertEquals("limit 0 초과 아님",exception.getMessage(), "초과문제");
        */
        //Study study = new Study(3);
        //assertNotNull(study);
        //System.out.println("Create");

        /*assertEquals(StudyStatus.DRAFT, study.getStatus(), "스터디 처음은 상태 "+ StudyStatus.DRAFT);
        assertEquals(StudyStatus.DRAFT, study.getStatus(), new Supplier<String>() {
            @Override
            public String get() {
                return "스터디 처음은 상태 "+StudyStatus.DRAFT;
            }
        });*/
        /*assertEquals(StudyStatus.DRAFT, study.getStatus(), ()-> "스터디 처음은 상태 "+ StudyStatus.DRAFT);
        assertTrue(study.getLimit()>0, "스터디 최대 참석가능인원은 0보다 크다");
        */
        /*assertAll(
                ()->assertNotNull(study),
                ()->assertEquals(StudyStatus.DRAFT, study.getStatus()
                        ,()->"스터디 처음"+StudyStatus.DRAFT),
                ()->assertTrue(study.getLimit()>0,"스터디 최소 참석 0")
        );*/


    }

    @Test
    @DisplayName("이름 바꾸기 \uD83D\uDE31")
    void create1(){
        //System.out.println("Create1");
    }


    @Test
    @Disabled
    void create2(){
        //System.out.println("Create2");
    }

    @BeforeAll//딱한번 호출, static, void
    static void beforeAll(){
        //System.out.println("before All");
    }

    @AfterAll
    static void afterAll(){
        //System.out.println("after all");
    }

    @BeforeEach
    void BeforeEach(){
        //System.out.println("BeforeEach");
    }

    @AfterEach
    void AfterEach(){
        //System.out.println("AfterEach");
    }

}

5. JUnit5: 조건에 따라 테스트 실행하기

특정한 조건(java버전, 환경변수)을 만족하는 경우에 테스트를 실행하는 방법

org.junit.jupiter.api.Assumptions.*

-assumeTrue(조건)

-assumingThat(조건, 테스트)

 

@Enabled_와 @Disabled_

-OnOS

-OnJre

-IfSystemProperty

-IfEviromentVariable

-If

 

더보기
package com.web.javademo1028web;

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.condition.*;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumingThat;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @Test
    @EnabledOnOs({OS.MAC, OS.LINUX})
    @DisabledIfEnvironmentVariable(named="TEST_ENV", matches = "DEV")
    @EnabledOnJre({JRE.JAVA_8,JRE.JAVA_9})
    @EnabledIfEnvironmentVariable(named="TEST_ENV", matches = "LOCAL")
    void create_test_study(){
        String env = System.getenv("TEST_ENV") ;
        System.out.println(env);
        assumeTrue("LOCAL".equalsIgnoreCase(env));
//터미넬  export TEST_ENV=LOCAL
        assumingThat("local".equalsIgnoreCase(env), ()->{
            Study actual = new Study(10);
            assertThat(actual.getLimit()).isGreaterThan(0);
        });
    }
}

6. JUnit5 태깅과 필터링

테스트 그룹을 만들고 원하는 테스트 그룹만 테스트를 실행할수있는기능

@Tag

- 테스트 메소드에 태그를 추가할수 있다. 

- 하나의 테스트 메소드에 여러 태그를 사용할수 있다.

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <groups>fast | slow</groups>
    </configuration>
</plugin
더보기
	<profiles>
		<profile>
			<id>default</id>
			<activation>
				<activeByDefault>true</activeByDefault>
			</activation>
			<build>
				<plugins>
					<plugin>
						<artifactId>maven-surefire-plugin</artifactId>
						<configuration>
							<groups>fast</groups>
						</configuration>
					</plugin>
				</plugins>
			</build>
		</profile>
		<profile>
			<id>ci</id>
			<build>
				<plugins>
					<plugin>
						<artifactId>maven-surefire-plugin</artifactId>
						<configuration>
							<groups>fast | slow</groups>
						</configuration>
					</plugin>
				</plugins>
			</build>
		</profile>
	</profiles>
package com.web.javademo1028web;

import org.junit.jupiter.api.*;

import static org.assertj.core.api.Assertions.assertThat;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @Test
    @DisplayName("스터디 fast")
    @Tag("fast")
     void create_test_study(){
        Study actual = new Study(10);
        assertThat(actual.getLimit()).isGreaterThan(0);
    }

    @Test
    @DisplayName("스터디 slow")
    @Tag("slow")
    void create_test_study2(){
        Study actual = new Study(10);
        assertThat(actual.getLimit()).isGreaterThan(0);
    }
}

 

7. JUnit5 커스텀 태그

JUnit5 애노테이션을 조합하여 커스텀 태그를 만들 수 있다. 

 

FastTest.java

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@Test
public @interface FastTest{
}


@FestTest
@DisplayName("스터디 만들기 fast")
void create_now_study(){
}

@SlowTest
@DisplayName("스터디 만들기 slow")
void create_new_study_again(){
}

 

8. JUnit5 테스트 반복하기 1부

@RepeatedTest

-반복횟수와 반복 테스트 이름을 설정할 수 있다. 

-{dispplayName}

-{currentRepetition}

-{totalRepetitions}

-RepetitionInfo 타입의 인자를 받을수 있다. 

더보기
package com.web.javademo1028web;

import org.junit.jupiter.api.*;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @DisplayName("스터디만들기")
    @RepeatedTest(value =10, name = "{displayName}:{currentRepetition}/{totalRepetitions}")
     void create_test_study(RepetitionInfo info){
        System.out.println("test"+ info.getCurrentRepetition()+"/"+info.getTotalRepetitions());
    }

}

@ParameterizedTest

-테스트에 여러 다른 매개변수를 대입해가며 반복 실행한다. 

-{displayName}

-{index}

-{arguments}

-{0}, {1}...

더보기
package com.web.javademo1028web;

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @DisplayName("JUnit5")
    @ParameterizedTest(name="{index} {displayName}_ {0}")
    @ValueSource(strings = {"날씨가","많이","더워지고"})
    void ParameterizedTest(String msg){
        System.out.println("test:"+ msg);
    }

}

9. JUnit5 테스트 반복하기 2부

인자 값들의 소스

-@ValueSource

-@NullSource, @EmptySource, @NullAndEmptySource

-@EnumSource

-@MethodSource

-@CvsSource

-@CvsFileSource

-@ArgumentSource

 

인자 값 타입 변환

- 암묵적 타입변환 :  https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests-argument-conversion-implicit 

더보기
Target TypeExample

boolean/Boolean

"true"  true

byte/Byte

"15", "0xF", or "017"  (byte) 15

char/Character

"o"  'o'

short/Short

"15", "0xF", or "017"  (short) 15

int/Integer

"15", "0xF", or "017"  15

long/Long

"15", "0xF", or "017"  15L

float/Float

"1.0"  1.0f

double/Double

"1.0"  1.0d

Enum subclass

"SECONDS"  TimeUnit.SECONDS

java.io.File

"/path/to/file"  new File("/path/to/file")

java.lang.Class

"java.lang.Integer"  java.lang.Integer.class (use $ for nested classes, e.g. "java.lang.Thread$State")

java.lang.Class

"byte"  byte.class (primitive types are supported)

java.lang.Class

"char[]"  char[].class (array types are supported)

java.math.BigDecimal

"123.456e789"  new BigDecimal("123.456e789")

java.math.BigInteger

"1234567890123456789"  new BigInteger("1234567890123456789")

java.net.URI

"https://junit.org/"  URI.create("https://junit.org/")

java.net.URL

"https://junit.org/"  new URL("https://junit.org/")

java.nio.charset.Charset

"UTF-8"  Charset.forName("UTF-8")

java.nio.file.Path

"/path/to/file"  Paths.get("/path/to/file")

java.time.Duration

"PT3S"  Duration.ofSeconds(3)

java.time.Instant

"1970-01-01T00:00:00Z"  Instant.ofEpochMilli(0)

java.time.LocalDateTime

"2017-03-14T12:34:56.789"  LocalDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000)

java.time.LocalDate

"2017-03-14"  LocalDate.of(2017, 3, 14)

java.time.LocalTime

"12:34:56.789"  LocalTime.of(12, 34, 56, 789_000_000)

java.time.MonthDay

"--03-14"  MonthDay.of(3, 14)

java.time.OffsetDateTime

"2017-03-14T12:34:56.789Z"  OffsetDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)

java.time.OffsetTime

"12:34:56.789Z"  OffsetTime.of(12, 34, 56, 789_000_000, ZoneOffset.UTC)

java.time.Period

"P2M6D"  Period.of(0, 2, 6)

java.time.YearMonth

"2017-03"  YearMonth.of(2017, 3)

java.time.Year

"2017"  Year.of(2017)

java.time.ZonedDateTime

"2017-03-14T12:34:56.789Z"  ZonedDateTime.of(2017, 3, 14, 12, 34, 56, 789_000_000, ZoneOffset.UTC)

java.time.ZoneId

"Europe/Berlin"  ZoneId.of("Europe/Berlin")

java.time.ZoneOffset

"+02:30"  ZoneOffset.ofHoursMinutes(2, 30)

java.util.Currency

"JPY"  Currency.getInstance("JPY")

java.util.Locale

"en"  new Locale("en")

java.util.UUID

"d043e930-7b3b-48e3-bdbe-5a3ccfb833db"  UUID.fromString("d043e930-7b3b-48e3-bdbe-5a3ccfb833db")

 

- 명시적타입변환 : 

   - SimpleArgumentConverter 상속 은 구현체 제공

더보기
package com.web.javademo1028web;

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.converter.ArgumentConversionException;
import org.junit.jupiter.params.converter.ConvertWith;
import org.junit.jupiter.params.converter.SimpleArgumentConverter;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.EmptySource;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @DisplayName("JUnit5")
    @ParameterizedTest(name="{index} {displayName}_ {0}")
    @ValueSource(ints = {10,24,45})
    //@EmptySource
    //@NullAndEmptySource
    //@CsvSource({"java, '자바스터디'","20, '스프링'"})
    void ParameterizedTest(@ConvertWith(StudyConverter.class) Study study){
        System.out.println("test:"+ study.getLimit());
    }

    static class StudyConverter extends SimpleArgumentConverter{

        @Override
        protected Object convert(Object source, Class<?> targetType) throws ArgumentConversionException {
            Assertions.assertEquals(Study.class,targetType, "only Study");
            return new Study(Integer.parseInt(source.toString()));
        }
    }

}

   - @ConvertWith

인자값 조합

 - ArgumentsAccessor

더보기
package com.web.javademo1028web;

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
import org.junit.jupiter.params.provider.CsvSource;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @DisplayName("JUnit5")
    @ParameterizedTest(name = "{index} {displayName}_ {0}")
    @CsvSource({"10, '자바스터디'", "20, 스프링"})
    void ParameterizedTest(ArgumentsAccessor aa) {
        Study study = new Study(aa.getInteger(0), aa.getString(1));
        System.out.println(study.toString());
    }
}

 - 커스텀Accessor

    - ArgumentsAggregator 인터페이스 구현

    - @AggregateWith

inner static or 퍼블릭이어야

더보기
package com.web.javademo1028web;

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.aggregator.AggregateWith;
import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
import org.junit.jupiter.params.aggregator.ArgumentsAggregationException;
import org.junit.jupiter.params.aggregator.ArgumentsAggregator;
import org.junit.jupiter.params.provider.CsvSource;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @DisplayName("JUnit5")
    @ParameterizedTest(name = "{index} {displayName}_ {0}")
    @CsvSource({"10, '자바스터디'", "20, 스프링"})
    void ParameterizedTest(@AggregateWith(StudyAggregator.class) Study study) {

        System.out.println(study.toString());
    }
    
    static class StudyAggregator implements ArgumentsAggregator {

        @Override
        public Object aggregateArguments(ArgumentsAccessor acc, ParameterContext parameterContext) throws ArgumentsAggregationException {
            return new Study(acc.getInteger(0), acc.getString(1));
        }
    }

}

 

aggregator <여러 회사의 상품이나 서비스에 대한 정보를 모아 하나의 웹사이트에서 제공하는 인터넷 회사·사이트>

10. JUnit5 테스트 인스턴스

JUnit은 테스트 메소드 마다 테스트 인스턴스를 새로 만든다. 

- 이것이 기본 전략

- 테스트 메소드를 독립적으로 실행하여 예상치 못한 부작용을 방지하기 위함이다. 

- 이 전략을 JUnit 5에서 변경할 수 있다. 

 

@TestInstance(Lifecycle.PER_CLASS)

- 테스트 클래스당 인스턴스를 하나만 만들어 사용한다. 

- 경우에 따라 테스트 간에 공유하는 모든 상태를 @BeforeEach 또는@AfterEach에서 초기화할 필요가 있다. 

- @BeforeAll 과 @AfterAll을 인스턴스 메소드 또는 인터페이스에 정의한 default메소드로 정의 할 수 도 있다. 

 

11. JUnit5 테스트 순서

실행할 테스트 메소드 특정한 순서에 의해 실행되지만 어떻게 그 순서를 정하는 지 의도적으로 분명히 하지 않는다. (테스트 인스턴스를 테스트마다 새로 만드는 것과 같은이유)

 

경우에 따라, 특정 순서대로 테스트를 실행하고 싶을 때도 있다. 그 경우에는 테스트 메소드를 원하는 순서에 따라. 

실행하도록 @TestInstance(Lifecycle.PER_CLASS)와 함께 @TestMethodOrder를 사용할수 있다. 

- MethodOrderer구현체를 설정한다. 

- 기본구현체

  -Alphanumeric

  - OrderAnnotation

  - Random

더보기
package com.web.javademo1028web;

import org.junit.jupiter.api.*;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class StudyTest {

    @Order(1)
    @Test
    void study_test_01() {
        System.out.println("study_test_01");
    }

    @Order(3)
    @Test
    void study_test_02() {
        System.out.println("study_test_02");
    }


    @Order(2)
    @Test
    void study_test_03() {
        System.out.println("study_test_03");
    }

    @Order(5)
    @Test
    void study_test_04() {
        System.out.println("study_test_04");
    }

}

12. JUnit5 : junit-platform.properties

JUnit설정 파일로 클래스패스 루트 (src/test/resources)에 넣어두면 적용된다. 

 

테스트 인스턴스 라이프사이클설정

junit.jupiter.testinstance.lifecycle.default = per_class

 

확장팩 자동 감지기능

junit.jupiter.extension.autodetection.enabled=true

 

@Disabled 무시하고 실행하기

junit.jupiter.conditions.deactivate=org.junit.*DisabledCondition

 

테스트이름 표기 전략설정

junit.jupiter.displayname.generation.default = \org.junit.jupiter.api.DisplayNameGeneratior$ReplaceUnderscores

 

 

13 JUnit5 확장모델

JUnit4의 확장모델 @RunWith(Runner), TestRule, MethodRule

JUnit5의 확장모델 단하나 Extension

 

확장팩 등록방법

- 선언적인 등록 @ExtenedWith

- 프로그래밍 등록 @RegisterExtension

- 자동 등록 자바 ServiceLoader 이용

 

확장팩 만드는 방법

- 테스트 실행조건

- 테스트 인스턴스 팩토리

- 테스트 인스턴스 후 처리기

- 테스트 매개변수 리졸버

- 테스트 라이프사이클 콜백

- 예외처리

 

참고 : 

14. JUnit5 JUnit4 마이그레이션

junit-vintage-engne을 의존성으로 추가하면, JUnit5의 junit-platform으로 JUnit 3과 4로 작성된 테스트를 실행할수 있다. 

- @Rule은 기본적으로 지원하지 않지만, junit-jupiter-migrationsupport 모듈이 제공하는 @EnableRuleMigrationSupport를 사용하면 다음타입의 룰을 지원한다. 

  1. ExternalResource
  2. Verifier
  3. ExpectedException
JUnit4 JUnit5
@Category(Class) @Tag(String)
@RunWith, @Rule, @ClassRule @ExtendWith , @RegisterExtension
@Ignore @Disabled
@Before, @After, @BeforeClass, @AfterClass @BeforeEach, @AfterEach, @BefreAll, @AfterAll

 

15. JUnit5 연습문제

1. 테스트 이름을 표기하는 방법으로 공백, 특수 문자 등을 자유롭게 쓸 수 있는 애노테이션은?

답: @DisplayName

2. JUnit 5, jupiter는 크게 세가지 모듈로 나눌 수 있습니다. 다음 중에서 테스트를 실행하는 런처와 테스트 엔진의 API를 제공하는 모듈은 무엇일까요?

① junit jupiter ② junit vintage ③ junit platform

답: 

3. JUnit 5에서 테스트 그룹을 만들고 필터링 하여 실행하는데 사용하는 애노테이션은?

 답: @Tag

4. 다음 코드는 여러 Assertion을 모두 실행하려는 테스트 코드입니다. 빈칸에 적절한 코드는 무엇인가요?

@Test

@DisplayName("스터디 만들기")

void create_new_study() {

    Study actual = new Study(1, "테스트 스터디");

    ________(

        () -> assertEquals(1, actual.getLimit()),

        () -> assertEquals("테스트 스터디", actual.getName()),

        () -> assertEquals(StudyStatus.DRAFT, actual.getStatus())

    );

}

 답: assertAll

5. 다음은 JUnit 5가 제공하는 애노테이션으로 컴포짓 애노테이션을 만드는 코드입니다. 이 애노테이션에 적절한 Rention 전략은 무엇인가요?

@Target(ElementType.METHOD)

@Retention(______________)

@Test

@Tag("fast")

public @interface FastTest {

}

 답: Retention : 메모리 언제까지 유지?  RetentionPolicy.RUNTIME : 런타임시까지 유지

6. 다음 중 JUnit 5가 제공하는 확장팩 등록 방법이 아닌것은?
① @ExtendWith

② @Rule

③ @RegisterExtension

④ ServiceLoader

 답 : 

- 선언적인 등록 @ExtenedWith

- 프로그래밍 등록 @RegisterExtension

- 자동 등록 자바 ServiceLoader 이용

7. 다음 코드는 유즈케이스 테스트를 작성한 것입니다. 다음 빈 칸에 적절한 코드는?



@TestInstance(TestInstance.Lifecycle.________)

@TestMethodOrder(MethodOrderer.___________.class)

public class StudyCreateUsecaseTest {


    private Study study;


    @Order(1)

    @Test

    @DisplayName("스터디 만들기")

    public void create_study() {

        study = new Study(10, "자바");

        assertEquals(StudyStatus.DRAFT, study.getStatus());

    }


    @Order(2)

    @Test

    @DisplayName("스터디 공개")

    public void publish_study() {

        study.publish();

        assertEquals(StudyStatus.OPENED, study.getStatus());

        assertNotNull(study.getOpenedDateTime());

    }


}

 답: @TestMethodOrder(MethodOrderer.OrderAnnotation.class)

@TestInstance(TestInstance.Lifecycle.PER_CLASS)

8. 다음은 여러 매개변수를 바꿔가며 동일한 테스트를 실행하는 코드입니다. 빈칸에 적잘한 코드는?

 

@Order(4)

@DisplayName("스터디 만들기")

@________________(name = "{index} {displayName} message={0}")

@CsvSource({"10, '자바 스터디'", "20, 스프링"})

void parameterizedTest(@___________(StudyAggregator.class) Study study) {

    System.out.println(study);

}


static class StudyAggregator implements ArgumentsAggregator {

    @Override

    public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) throws ArgumentsAggregationException {

        return new Study(accessor.getInteger(0), accessor.getString(1));

    }

}

 답  : @ParameterizedTest(name="{index} {displayName}_ {0}")

@AggregateWith

 

 

 

 

더보기
package com.web.javademo1028web;

import org.junit.jupiter.api.*;

import static org.assertj.core.api.Assertions.assertThat;

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class StudyTest {

    @Test
    @DisplayName("스터디 fast")
    @Tag("fast")
     void create_test_study(){
        Study actual = new Study(10);
        assertThat(actual.getLimit()).isGreaterThan(0);
    }

    @Test
    @DisplayName("스터디 slow")
    @Tag("slow")
    void create_test_study2(){
        Study actual = new Study(10);
        assertThat(actual.getLimit()).isGreaterThan(0);
    }
}
	<profiles>
		<profile>
			<id>default</id>
			<activation>
				<activeByDefault>true</activeByDefault>
			</activation>
			<build>
				<plugins>
					<plugin>
						<artifactId>maven-surefire-plugin</artifactId>
						<configuration>
							<groups>fast</groups>
						</configuration>
					</plugin>
				</plugins>
			</build>
		</profile>
		<profile>
			<id>ci</id>
			<build>
				<plugins>
					<plugin>
						<artifactId>maven-surefire-plugin</artifactId>
						<configuration>
							<groups>fast | slow</groups>
						</configuration>
					</plugin>
				</plugins>
			</build>
		</profile>
	</profiles>