백기선님 자바스터디 4주차 : 제어문 2) 과제0. JUnit 5 학습
과제 0. JUnit 5 학습하세요.
- 아래 내용은 https://blog.jetbrains.com/idea/2020/09/writing-tests-with-junit-5/ 를 보며 따라 학습했다.
- 출처를 따라하며
assertEqual
을 사용했으나 사실aseertThat
을 사용하는 것이 더 직관적으로 읽기 쉽고, 타입 안정성을 가진다. 지난 번에 정리했던 글 - 목차
- 0.1. 환경설정
- 0.2. 간단한 테스트
- 0.3. 여러 개의 Assertions, assertAll
- 0.4. Assumptions
- 0.5. 예외 체크
- 0.6. JUnit5 어노테이션
- 어노테이션1 : @BeforeEach 와@BeforeAll 의 차이
- 어노테이션2 : @TestInstance
- 어노테이션3 : @Disabled
- 어노테이션4 : @DisplayName
- 어노테이션5 : @ParameterizedTest
- 어노테이션6 : @Nested
1. 환경설정
- JUnit5를 실행하기 위해서는 빌드툴(Gradle, Maven)이 필요하다.
- gradle을 사용해서 프로젝트 만들기
- 초기 설정된 dependency
- 별도의 지정이 없어도 기본적으로 작성된다.
- 출처에 따르면 dependency에
compile 'org.junit.jupiter:junit-jupiter-api:5.7.2'
가 추가하라고 하는데 그것이 없어도 잘 작동된다.- Gradle projects 세팅을 Gradle로 했을 때는 문제 없었는데 IntelliJ IDEA로 바꾸니까 작동이 안되었다. 출처에 따라서 dependecy에 추가해주니 작동 잘되었다
2. 간단한 테스트
- Junit5는 Assertins 클래스가 있다.
- 테스트 :
assertEquals(좌, 우)
좌항과 우항이 같은지 test
public class ExampleTest {
@Test
void shouldShowSimpleAssertion() {
Assertions.assertEquals(1, 1);
Assertions.assertEquals(1, 2);
}
}
- 결과 : 이렇게 테스트가 틀렸을 경우 기댓값과 실제값을 알려준다
3. 여러 개의 Assertions, assertAll
- 기본적으로 하나의 테스트 메소드 내에 여러개의 assertions을 넣을 수 있다.
- 테스트 :
@Test @DisplayName("Should check all items in the list") void shouldCheckAllItemsInTheList() { List<Integer> numbers = List.of(2, 3, 5, 7); Assertions.assertEquals(2, numbers.get(0)); Assertions.assertEquals(3, numbers.get(1)); Assertions.assertEquals(5, numbers.get(2)); Assertions.assertEquals(7, numbers.get(3)); }
- 결과 :
- 그러나 도중에 하나의 test가 실패하면 우리는 다른 테스트의 결과가 실패인지 통과인지 알수가 없다.
- 테스트 : 실패할 assertions을 포함하여 작성
@Test @DisplayName("Should check all items in the list") void shouldCheckAllItemsInTheList() { List<Integer> numbers = List.of(2, 3, 5, 7); Assertions.assertEquals(2, numbers.get(0)); Assertions.assertEquals(3, numbers.get(3)); Assertions.assertEquals(5, numbers.get(2)); Assertions.assertEquals(7, numbers.get(3)); }
- 결과 : 결과에 보여지는 테스트 외에 다른 테스트는 통과인지 실패인지 알 수가 없다.
- 그래서
assertAll
이 있다. 도중에 하나가 실패해도 모든 assertion을 체크하기 때문이다. 람다식으로 표현해서assertAll
로 묶을 수 있다.- 테스트 :
assertAll
을 활용한 테스트
@Test @DisplayName("Should check all items in the list") void shouldCheckAllItemsInByAssertAll() { List<Integer> numbers = List.of(2, 3, 5, 7); Assertions.assertAll( () -> assertEquals(1, numbers.get(0)), //실패 () -> assertEquals(3, numbers.get(1)), () -> assertEquals(1, numbers.get(2)), //실패 () -> assertEquals(7, numbers.get(3))); }
- 결과 : 두 개의
assertEquals
가 fail되게 작성했을 때 이 모든 것을 잡아주는 것을 알 수 있다.
- 테스트 :
4. Assumptions
-
특정 assumptions(가정)이
true
일 때만 test를 하고 싶을 때 사용한다.- 테스트 : 현재 날짜를 받아서 이것이 "WENDSDAY"일 때만 실행하게 하는 코드
public class ExampleTest { private final String currentDayOfWeek = LocalDate.now().getDayOfWeek().toString(); @Test @DisplayName("Should only run the test if some criteria are met") void shouldOnlyRunTheTestIfSomeCriteriaAreMet() { Assumptions.assumeTrue(currentDayOfWeek.equals("WEDNESDAY")); assertEquals(1, 1); } }
- 결과 :
assumptions
이true
이기 때문에 테스트가 잘 통과된다 - 테스트 :
assumptions
을 틀리게 작성
@Test @DisplayName("Should only run the test if some criteria are met") void shouldOnlyRunTheTestIfSomeCriteriaAreMet() { Assumptions.assumeTrue(currentDayOfWeek.equals("TUESDAY")); assertEquals(1, 1); }
- 결과 :
assertEquals
가 아예 실행되지 않는다
5. 예외 체크
- 유효하지 않은 input이 있을 때 발생하는 예외를 체크할 수 있다.
- java-chess 프로젝트를 하면서 input 값에 말의 색깔이 흰색, 검정 색 외에 다른 색이 들어오면 예외가 발생하는 코드를 작성했다. 이 때, 예외체크 기능을 활용하여 pink라는 색을 입력했을 때 작성해둔 예외가 발생하는지 확인하는 test를 작성했던 기억이 난다.
- 테스트 : 예외가 발생하도록 작성한 코드
@DisplayName("Should throw exception") @Test void shouldThrowException() { assertThrows(RuntimeException.class, () -> { Integer.parseInt("hello"); }); }
- 결과 : 기대한 예외 발생하여 테스트 통과
- AssertJ 사용한 예외 체크 :
assertThatThrownBy
사용
@Test
@DisplayName("잘못된 위치에 node를 추가하면 예외가 발생해야한다")
void addNodeAtWrongPosition() {
assertThatThrownBy(() -> {
linkedList.add(head, new ListNode(1), 5);
}).isInstanceOf(IndexOutOfBoundsException.class);
}
- 기타 다른 AssertJ 사용한 예외 체크 : https://www.baeldung.com/assertj-exception-assertion
6. JUnit5 어노테이션
- https://junit.org/junit5/docs/current/user-guide/#annotations 이곳에 여러 어노테이션이 있지만 한번쯤 봤을 것들만 정리했다.
어노테이션 | 설명 | 자세한 설명 |
---|---|---|
@Test | 테스트메소드라는 것을 알려준다 | |
@ParameterizedTest | 파라미터라이즈 테스트라는 것을 알려준다. 매개변수 전달하여 테스트 | docs |
@RepeatedTest | 동일한 테스트를반복할 때 | docs baeldung |
@TestMethodOrder | 테스트 실행 순서 정하기. @TestInstance 를 사용할 때 순서 정할 때 사용, 순서를 정하는 방법에는 여러 옵션이 있다. | docs |
@TestInstance | 길어서 아래 설명 | docs |
@DisplayName | 테스트에 이름 붙이기 | |
@BeforeEach | 동일 클래스 내에서 각각의 @Test, @RepeatedTest, @ParameterizedTest, or @TestFactory 가 붙은 메소드 실행 전 실행 | |
@AfterEach | 동일 클래스 내에서 각각의 @Test, @RepeatedTest, @ParameterizedTest, or @TestFactory 가 붙은 메소드 실행 후 실행 | |
@BeforeAll | 모든 all @Test, @RepeatedTest, @ParameterizedTest, and @TestFactory 메소드 전에 한번만 실행 | |
@Nested | 테스트 묶어줄 때 사용 , @BeforeAll and @AfterAll는 @TestInstance 로 클래스 단위의 환경변수를 만들어주지 않는 한 사용 불가 | |
@Disabled | 이 테스트는 무시하세요 |
6.1 어노테이션 1 : @BeforeEach
와@BeforeAll
의 차이.
각각의 테스트 전에 한번씩 실행 된다는 것(@BeforeEach
)과 모든 테스트 전에 한 번씩 실행되는 결과(@BeforeAll
)의 차이를 보기 위해 코드를 작성했다.
- 테스트 :
@BeforeEach
@DisplayName("A stack")
public class BeforeEachTest {
Stack<Object> stack;
String anElement = "an element";
@BeforeEach
void createNewStack() {
stack = new Stack<>();
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertThat(stack.isEmpty()).isFalse();
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertThat(stack.pop()).isEqualTo(anElement);
assertThat(stack.isEmpty()).isTrue();
}
}
- 결과 : 요소가 1개 들어간 스택이 각각의 테스트 전에 만들어지므로 모든 테스트 통과
- 테스트 :
@BeforeAll
의 경우- 참고,
@BeforeAll
과@AfterAll
을 붙인 메소드는static
으로 선언되어한다. 그렇게 되면 클래스 내 지역변수를 사용하지 못하게 되므로 예시에서 인스턴스 변수를 사용할 수가 없다. 따라서@TestInstance(TestInstance.Lifecycle.PER_CLASS)
를 붙여줬다. 이에 대한 설명은 아래에. 이렇게 하면@BeforeAll
과@AfterAll
도 static 메소드가 아닌 곳에도 붙일 수 있다.
- 참고,
@DisplayName("A stack")
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class BeforeAllTest {
Stack<Object> stack = new Stack<>();
String anElement = "an element";
@BeforeAll
void createNewStack() {
stack.push(anElement);
}
@Test
@DisplayName("it is not empty")
void isNotEmpty() {
assertThat(stack.isEmpty()).isFalse();
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertThat(stack.pop()).isEqualTo(anElement);
assertThat(stack.isEmpty()).isTrue();
}
}
- 결과 : 첫번 째 실행된 테스트에서
pop
을 해주었기 때문에 빈 stack의 형태에서 다시 두 번째 테스트를 했기 때문에 테스트 실패
@AfterEach
@AfterAll
의 차이도 똑같다
6.2 어노테이션 2 : @TestInstance
-
모든 테스트가 하나의 테스트 환경 내에서 실행되고자 할 때사용한다
- (각각의 테스트 결과가 서로 영향을 준다)
-
JUnit은 기본적으로 각각의 test 메소드 실행 전에 test 클래스 인스턴스를 만든다. ("per-method" test instance lifecycle)
-
그래서 인스턴스 변수를 선언해두고 각각의 테스트에서 이 변수 값을 변경해도 다른 테스트에는 영향을 미치치 않는다.
- 테스트 :
@DisplayName("A stack") public class LifecycleTest { Stack<Object> stack = new Stack<>(); String anElement = "an element"; @Test @DisplayName("push an element and is no longer empty") void isNotEmpty() { stack.push(anElement); assertThat(stack.isEmpty()).isFalse(); } @Test @DisplayName("it is empty") void void isEmpty() { { stack.pop(); assertThat(stack.isEmpty()).isTrue(); } }
- 결과 : 실패. 한 테스트에서 스택에 값을 넣어도 다른 테스트에 전혀 영향을 주지 않기 때문에 스택은 항상 비어있다. 따라서 isEmpty테스트는 pop할 값이 없기 때문에 실패한다.
-
메소드끼리 영향을 주는 테스트를 만드려면 ( 하나의 공동의 테스트 환경 내에서 테스트를 실행하려면)
@TestInstance
를 사용한다. -
@TestInstance(Lifecycle.PER_CLASS)
과@TestInstance(Lifecycle.PER_METOD)
이렇게 두 가지로 사용할 수 있는데 후자는 디폴트이므로 굳이 써주지 않아도 된다. -
이렇게
@TestInstance(Lifecycle.PER_CLASS)
클래스 단위로 선언해주면 같은 테스트 환경을 메소드끼리 공유하게 된다.- 테스트 : 테스트를 통과하기 위해
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
로 테스트 순서를 지정해주었다.
@DisplayName("A stack") @TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class LifecycleTestPerClass { Stack<Object> stack = new Stack<>(); String anElement = "an element"; @Test @DisplayName("push an element and is no longer empty") @Order(1) void isNotEmpty() { stack.push(anElement); assertThat(stack.isEmpty()).isFalse(); } @Test @DisplayName("it is empty") @Order(2) void isEmpty() { stack.pop(); assertThat(stack.isEmpty()).isTrue(); } }
- 결과 : 같은 환경을 공유하므로 첫번째 테스트에서 요소를 하나 push 한 결과가 test2에도 반영되어 test2도 성공한다.
- 테스트 : 테스트를 통과하기 위해
6.3. 어노테이션3 : @Disabled
-
임시로 테스트를 비활성화 하게 해주는 어노테이션
- 테스트 :
public class ExampleTest { @Test @Disabled("잠시 테스트를 비활성화") void shouldShowSimpleWrongAssertion() { Assertions.assertEquals(1, 2); } @Test void shouldShowSimpleAssertion() { Assertions.assertEquals(1, 1); } }
- 결과 : 테스트 통과 ( 잘못된 테스트를 비활성화 해 두어서)
6.4. 어노테이션4 : @DisplayName
- 테스트에 설명을 붙일 수 있다.
- 테스트 :
@Test @DisplayName("Should demonstrate a simple assertion") void shouldShowSimpleAssertion() { Assertions.assertEquals(1, 1); }
- 결과 : 왼쪽에서 보일 수 있는 것과 같이 테스트 설명이 그대로 나온다(어떤 테스트 실패, 성공을 알아보기 쉽다)
6.5 어노테이션5 : Data Driven Tests - @ParameterizedTest
AssertAll
이 여러assertions
을 확인하는 방법이었다면- 같은 코드에 데이터를 여러개 바꿔가면서 테스트하고 싶다면
@ParameterizedTest
를 이용하면 된다
6.5.1. 환경설정
@ParameterizedTest
를 사용하고 싶다면 dependency에testCompile 'org.junit.jupiter:junit-jupiter-params:5.7.0'
을 추가해준다 (gradle의 경우)- 그리고 새로고침!
ctrl + shift + o
(dependecy 변경 사항 반영) - 출처 : Baeldung
6.5.2. 사용법과 예제
@ParameterizedTest
를 사용할 떄는@Test
를 사용하지 않는다@ValueSource
로 data를 array에 담아 전달할 수 있다. 그 외에 null과 빈 데이터소스를 전달하는 방식인@NullSource
,@EmptySource
,@NullAndEmptySource
가 있고Enum
을 활용해서 데이터를 전달하는 방식인@EnumSource
가 있다. 팩토리 메서드를 사용해서 데이터를 전달하는 방식인@MethodSource
가 있다. 그 외에도,
로 구분된 데이터 묶음을 전달하는@CsvSource
등이 있으니 자세한 사항은 출처를 참고@CsvSource
예시
@ParameterizedTest
@CsvSource({
"apple, 1",
"banana, 2",
"'lemon, lime', 0xF1"
})
void testWithCsvSource(String fruit, int rank) {
assertNotNull(fruit);
assertNotEquals(0, rank);
}
- 테스트 :
@ValueSource
로 값들이 홀수인지 확인하는 테스트를 해보았다.
@ParameterizedTest
@DisplayName("Should check all numbers are odd numbers")
@ValueSource(ints = {3, 4, 5, 8, 14})
void shouldCheckOddNumbers(int number) {
assertThat(number%2).isEqualTo(1);
}
- 결과 : 이렇게 에러와 함께 몇번째 데이터가 틀렸는지 알려준다.
6.6 어노테이션6 : @Nested
- 특정 test 를 보다 큰 클래스로 묶어줄 수 있다. 테스트를 구조화 하는데 도움이 된다.
- 테스트 : 참조자료에서 그대로 예시를 가져왔다. (조금 줄였다)
package bongf.junit5;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.util.Stack;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("A stack")
public class NestedTest {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
}
}
}
- 결과 : 구조화에 도움이 된다.
출처
- https://lucas.codesquad.kr/masters-2021/course/%EB%A7%88%EC%8A%A4%ED%84%B0%EC%A6%88-%EB%B0%B1%EC%97%94%EB%93%9C-Java-%ED%81%B4%EB%9E%98%EC%8A%A4-2021/Java-%EA%B0%95%EC%9D%98-%EB%AA%A8%EC%9D%8C/unit-test
- https://blog.jetbrains.com/idea/2020/09/writing-tests-with-junit-5/
- https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests
- 5.예외체크 https://www.baeldung.com/junit-assert-exception
- 6.어노테이션 https://junit.org/junit5/docs/current/user-guide/#annotations
- 6.2. @TestInstance
https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-instance-lifecycle - 6.5 @ParameterizedTest
- 6.6. @Nested https://junit.org/junit5/docs/current/user-guide/#writing-tests-nested
이 페이지를 링크해둔 곳
Author And Source
이 문제에 관하여(백기선님 자바스터디 4주차 : 제어문 2) 과제0. JUnit 5 학습), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@bongf/study-java-whiteship-javaStudy-week4-2-0저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)