Junit5
- 프로그래밍 언어/자바
- 2020. 12. 4. 18:44
*백기선의 더자바 애플리케이션을 테스트하는 다양한 방법을 바탕으로 작성 했습니다.!
Junit5은 자바의 테스팅 프레임워크 이다.
구성
Junit Platform + Junit Jupiter + JUnit Vintage 으로 구성되어 있다.
Junit Platform : TestEngine Api 추상체 , 실행을 위한 런처 제공
Junit Jupiter : Junuit5의 구현체
JUnit Vintage : Junit3,4의 구현체
JUnit Vintage가 필요한 이유 : Junit3,4가 기반인 프로젝트를 실행시키기 위해서.
특징
- 모듈을 통해 관리를 한다.
- 자바 JDK8이상에서 사용이 가능하다.
시작하기
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
api
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
test engine
실행 테스트
public class Study {
public int add(int value) {
return value;
}
}
간단한 코드를 작성한다.
command + shift + t
class StudyTest {
@Test
public void add() {
Study study = new Study();
assertEquals(study.add(5),5);
}
}
값을 넣으면 그 값이 맞는지 확인하는 간단한 코드
그러면 이제 시작해보자.
주요 어노테이션
@Test : 메서드가 테스트임을 나타낸다. (위에 작성함)
@DisplayNameGeneration : 메소드나 클래스에 사용 할 수 있으며, 테스트 이름에 전략을 사용할 수 있다.
@DisplayName : 테스트 사용자 정의 이름을 표시합니다.
@Test
@DisplayName("안녕?")
public void add() {
Study study = new Study();
assertEquals(study.add(5),5);
}
@AfterAll , @BeforeAll
- 딱 한번 프로그램이 시작하거나 끝날때 실행이 된다.
- static을 사용해야 한다.
@AfterEach, @BeforeEach
- 테스트 구문이 실행될때마다 실행된다.
- 굳이 static일 필요는 없다.
@BeforeAll
static void all1() {
System.out.println("전체");
}
@AfterAll
static void all2() {
System.out.println("끝내기");
}
@BeforeEach
void each() {
System.out.println("각각");
}
@AfterEach
void each2() {
System.out.println("끝 각각");
}
@Disabled : 테스트 무시
Assertion
assertXXX를 이용해서 테스트를 실행 한다.
- assertEquals : 테스트할 값과 실제 값이 같은지 테스트를 실행한다,
@Test
void same() {
Study study = new Study();
assertEquals(study.value(3),5, "값이 서로 다릅니다.");
}
추가적으로 string이나 supplier<String>을 추가 할 수 있다.
- assertFalse / assertTrue
boolean값이 틀린지/ 맞는지 테스트를 실행한다,
assertFalse(study.setBoolean(true),"이것은 틀려야 합니다.");
- assertThrows
예외가 발생하는지 테스트 합니다.
@Test
void error() {
assertThrows(
IllegalArgumentException.class,
() -> new Integer(1 / 0)
, "0으로는 나눌 수 없습니다.");
}
- assertTimeout
시간내에 끝나는지 확인합니다. 다만 에러가 종료 될때까지 기다립니다.
@Test
void time() {
assertTimeout(Duration.ofSeconds(3),() -> Thread.sleep(4000) , "3초 이내에 끝나야 합니다.");
}
- assertAll
여러 테스트를 동시에 실행이 가능한다.
@Test
void all() {
assertAll(() -> assertEquals(3, 5, "값이 서로 다릅니다."),
() -> assertFalse(true, "이것은 틀려야 합니다.") ,
() -> assertThrows(
IllegalArgumentException.class,
() -> new Integer(1 / 0)
, "0으로는 나눌 수 없습니다."),
() -> assertTimeout(Duration.ofSeconds(3),() -> Thread.sleep(4000) , "3초 이내에 끝나야 합니다."));
}
여기서 궁금증! 이렇게 하는건 어떨까?
@Test
void all() {assertEquals(3, 5, "값이 서로 다릅니다.");
assertFalse(true, "이것은 틀려야 합니다.");
assertThrows(
IllegalArgumentException.class,
() -> new Integer(1 / 0),
"0으로는 나눌 수 없습니다.");
assertTimeout(Duration.ofSeconds(3),() -> Thread.sleep(4000) , "3초 이내에 끝나야 합니다.");
}
태깅과 필터링
@Tag를 이용해서 그룹을 지정할 수 있다.
@Test
@Tag("fast")
void add2() {
Study study = new Study();
assertEquals(study.value(3), 5, "값이 서로 다릅니다.");
}
이제 tag가 fast인 테스트만 실행하게 만들 수 있다.
만약 ,IDE를 인텔리제이를 사용한다면?
커스텀 태그
Junit5의 에노테이션는 메타 에노테이션으로 사용할 수 있습니다.
ex) @Tag
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface MyFastTest {
}
@Fast 커스텀 태그를 만드는 커스텀 에노테이션입니다.
@Test
@MyFastTest
void error() {
IllegalArgumentException value = assertThrows(
IllegalArgumentException.class,
() -> new Integer(1 / 0)
, "0으로는 나눌 수 없습니다.");
}
이런식으로 설정해둡니다.
@Test
@Tag("fast")
void flag() {
Study study = new Study();
assertFalse(study.setBoolean(true), "이것은 틀려야 합니다.");
}
혹시 모르니 @Tag("fast")는 하나는 남겨두겠습니다.
실행!
테스트 반복하기
1. 단순하게 반복 하기 : @RepeatedTest
@RepeatedTest(10)
void repeat() {
System.out.println("repeat");
}
하지만 이것은 가독성이 떨어진다.
@RepeatedTest(value = 10,name = "이름 표시")
void repeat() {
System.out.println("repeat");
}
이렇게 하면 이름을 지정할 수 있는데
{currentRepetition} : 현재 반복 횟수
{totalRepetitions} : 총 반복 횟수
지금은 테스트 창에서는 나오지만 콘솔?창에는 나오지 않고 있다. 이러한 정보는
TestInfo 또는 RepetitionInfo에 저장되어있다.
특히 getDisplayName같은 경우, 위에서 지정한 반복 테스트 이름의 문자열을 조작하는 방법을 제공한다.
하지만 반복 횟수나 총 횟수는 알 수 없었다.
그러면 RepetitionInfo에서는 알 수 있을까?
따라서 리피트 정보를 알고 싶을 때는 RepetitionInfo에서 찾아보고, 테스트 정보를 찾고 싶으면 TestInfo을 찾아보면 알 수 있다.
2. 단순 반복이 아닌, 인수를 통해 반복하고 싶은 경우,
ex) {"문자열1","문자열2","문자열3"}을 반복 싶다면? 반복문을 통해 index를 구한다?
@ParameterizedTest를 사용한다.
@ParameterizedTest(name = "???")
@ValueSource(strings = { "안녕하세요", "저는", "누구일까요?" })
void palindromes(String str) {
System.out.println(str);
}
심심해서 name을 추가해봣다.
name에 {index} => 횟수 , {displayName} => 메소드 명 , message={0} => 인자값
테스트 인스턴스
Junit은 테스트를 실행할때마다 인스턴스를 재 생성하게 된다.
따라서 맴버 변수를 1로 설정해두고 그 값을 1씩 계속 증가시켜도
항상 1인 이유가 바로 그 때문이다.
int value = 0;
@Test
@DisplayName("같은가?")
void add() {
System.out.println(value++);
Study study = new Study();
assertEquals(study.value(3), 3);
}
@Test
@Tag("fast")
void flag() {
System.out.println(value++);
assertFalse(false, "이것은 틀려야 합니다.");
}
Junit이 이러한 결정을 한 이유는 테스트간에 의존성을 최소화하기 위함이다. 그래서 @BeforeAll 이나 페이스북 @AfterAll을 사용할때 static을 사용한 이유도 이 때문이다. 하지만 인스턴스를 계속 생성해야 하기 때문에 성능적으로 봤을때는 좋지 않을 수 도 있다.
Junit5부터는 Junit의 기본전략을 수정할 수 있다.
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
클래스당 인스턴스를 생성한다는 의미다. 이렇게 하면 @BeforeAll이나 기자 @AfterAll에 static을 붙일 필요는 없어졌다.
같은 코드로 실행해보자.
테스트 순서
Junit의 테스트 순서는 순차적으로 실행된다. (몇번을 실행해도 같은 결과를 도출한다.)
이 결과는 Junit내부에서 얼마든지 바뀔 수 있다.
평소에는 테스트가 어떻게 실행되는지 상관은 없다.
왜냐하면 테스트는 각각 독립적이야 되기 때문이다.
@Test
@DisplayName("같은가?")
void add() {
System.out.println(value++);
Study study = new Study();
assertEquals(study.value(3), 3);
}
@Test
@Tag("fast")
void flag() {
System.out.println(value++);
assertFalse(false, "이것은 틀려야 합니다.");
}
@Test
void hell() {
System.out.println("hello?");
}
@Test
void next() {
System.out.println("다음");
}
@Test
void good() {
System.out.println("좋아요");
}
만약, 일반적인 테스트가 아니라 시나리오테스트 같은 경우는 어떨까?
시나리오 테스트 같은 경우는 순서가 매우 중요하다.
또한, 시나리오 테스트는 테스트간의 의존성이 있어야 한다. 그래야 원하는 테스트를 진행 할 수 있기 때문이다.
먼저 위에서 학습한 클래스당 테스트 인스턴스를 생성하게 변경한뒤,
직접 테스트 순서를 변경할 수 있다.
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class StudyTest {
@TestMethodOrder 어노테이션으로 설정할 수 있는데 다음과 같이 추가할 수 있다.
조금전에 실행했던 방법과 비교하면 좋을 것 같다.
OrderAnnotation : 어노테이션으로 지정된 방법대로 순서를 정한다.
크기는 int의크기를 따른다.
@Test
@DisplayName("같은가?")
@Order(3)
void add() {
System.out.println(value++);
Study study = new Study();
assertEquals(study.value(3), 3);
}
@Test
@Tag("fast")
@Order(4)
void flag() {
System.out.println(value++);
assertFalse(false, "이것은 틀려야 합니다.");
}
@Test
@Order(5)
void hell() {
System.out.println("hello?");
}
@Test
@Order(2)
void next() {
System.out.println("다음");
}
@Test
@Order(1)
void good() {
System.out.println("좋아요");
}
만약 Order어노테이션이 없다면? 맨 나중으로 실행 된다.
순서가 변경되었다.
다른 것들은 자세하게 다루지는 않겠지만,
DisplayName.class : 개발자가 지정한 테스트 명의 알파벳 순서에 따라 테스트를 실행한다.
MethodName.class : 개발자가 지정한 메소드 명의 알파벳 순서에 따라 테스트를 실행한다.
Random.class : 랜덤
확장 모델
Junit을 확장시켜 사용할 수 있다.
예를들어, 시간이 조금 걸리는 테스트에는 @FastTest를 붙이도록 권장하고 싶다면, 확장 모델을 사용해서 해결 할 수 있다.
Junit5에서는 Extention을 이용해서 확장 시킬 수 있다. 여러 모델?이 필요 했던 다른 버전에 비해 단순해진 느낌을 받는다.
등록하는 방법은 총 3가지를 제공하고 있다.
그전에 어떤식으로 확장시킬지에 대한 코드가 필요합니다.
public class TimeExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
@Override
public void beforeTestExecution(ExtensionContext context) throws Exception {
}
@Override
public void afterTestExecution(ExtensionContext context) throws Exception {
}
}
테스트가 시작할때, 어떻게 동작하는지, 끝날때, 어떻게 동작해야하는지 코드를 작성하자.
이 확장 클래스의 핵심은 시간이다. 따라서 프로그래밍상에 시간에 관련된 내용이 추가되야 한다.
ExtensionContext안에는 각종 정보를 알수 있다.
클래스도 확인이 가능하고, 메소드, 테스트 명등을 알 수 있다.
또한, 그 정보들을 Store에 저장시킬 수 있다.
@Override
public void beforeTestExecution(ExtensionContext context) throws Exception {
long start = System.currentTimeMillis();
String name = context.getDisplayName();
String className = context.getClass().getName();
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create(name,className));
store.put("START_TIME",start);
}
이제 이 정보들을 뺄수 도 있는데 이 때는 remove를 사용한다.
@Override
public void afterTestExecution(ExtensionContext context) throws Exception {
String name = context.getDisplayName();
String className = context.getClass().getName();
ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create(name,className));
Long time = store.remove("START_TIME", long.class);
}
이렇게 나온 정보는 현재 시간과의 차이를 통해 얼마나 걸리는지 알 수 있다.
long duration = System.currentTimeMillis() - time;
10ms보다 작으면 동작하게 만들었다.
if (duration < 10) {
System.out.printf("시간이 너무 빠릅니다. @FastTest를 붙여주세요. 현재 %s ms",duration);
}
1. 선언적으로 등록하기
@ExtendWith
@ExtendWith(FastExtension.class)
class StudyTest {
이렇게 추가하면 10ms보다 빠른 테스트는 위 문장을 실행하게 된다. 그런데 이미 @FastTest가 붙어 있으면 나오지 않게 하고 싶다.
이것도 ExtensionContext안에 있을까?
자바의 리플렉션기능을 사용하면 해결 할 수 있다.
MyFastTest annotation = context.getRequiredTestMethod().getAnnotation(MyFastTest.class);
if (duration < 10 && annotation == null ) {
System.out.printf("시간이 너무 빠릅니다. @FastTest를 붙여주세요. 현재 %s ms",duration);
}
이 어노테이션이 null일때만 실행하게 변경하였다.
2. 프로그래 매틱 한 방법으로 등록
@RegisterExtension
이 방법은 각 테스트클래스마다 다르게 적용하고 싶을때 사용된다.
A테스트 클래스에는 5ms미만으로 싶고, B테스트 클래스에는 10ms미만으로 하고 싶다면 어떻게 해야할까?
int thread;
public FastExtension(int thread) {
this.thread = thread;
}
이렇게 하면 각 테스트 클래스마다 적용할 수 있다. 하지만 바로 실행하게 되면 에러가 발생한다.
이럴때 사용되는 어노테이션이 @RegisterExtension이다.
@RegisterExtension
static FastExtension fast = new FastExtension(1);
이제 이 테스트 클래스는 1ms보다 빠르면 코드가 동작한다.
마지막 방법은 설정파일에 추가하는 방법이 있다.
이 밖에도 확장 모델 생명 주기,예외처리등 다양한 확장 방법을 제공하고 있다.
강의를 들으면서 작성하는데, 빠진 부분도 있어서 추후에 다시 추가할 예정입니다. ㅎㅎ;;
'프로그래밍 언어 > 자바' 카테고리의 다른 글
스택 구현(설명x) (0) | 2020.12.07 |
---|---|
Live-Study 대시 보드 만들기. (0) | 2020.12.06 |
반복문과 조건문 (0) | 2020.11.29 |
백기선님 스터디에서 배운거 2가지 (간략히 정리) (0) | 2020.11.29 |
연산자 (0) | 2020.11.26 |