소프트웨어의 특정 모듈이나 컴포넌트를 독립적으로 테스트 하는 방법을 말한다. 단위 테스트는 코드의 작은 파트를 테스트하여 특정 기능이나 메서드가 잘 작동하는지 확인한다.
@Test : 테스트 메서드를 나타낸다.@BeforeEach : 각 테스트 전에 실행되는 메서드를 지정@AfterEach : 각 테스트 후에 실행되는 메서드를 지정한다.@BeforeAll : 클래스의 모든 테스트 전에 실행되는 메서드 지정@AfterAll : 클래스의 모든 테스트 후에 한 번 실행되는 메서드를 지정@DisplayName : 테스트 클래스나 메서드의 이름을 지정
@RepeatedTest(int count) : 동일한 테스트를 여러 번 반복 실행할 때 사용예시들과 함께 더 자세히 알아볼 것이다.
테스트 코드를 작성할 때, 이 주요 세 가지 파트로 나뉘어지게 된다. Given, when, then 이라는 워딩으로 나뉘는데, 각 단어의 뜻을 간단하게 풀이해보면
/*given*/준비 단계
테스트를 위한 초기 상태(조건, 입력, Mock 등)를 세팅한다.
/*when*/실행 단계
테스트 대상 메서드나 행위를 실제로 수행한다.
/*Then*/검증 단계
그 결과가 기대한 대로 나왔는지 단언(assert)한다.
1. assertEquals : 예상 값과 실제 값의 일치 여부를 동일성으로 판단한다.
void testAssertEquals() {
/* given: */
int firstNum = 10;
int secondNum = 20;
int expected = 34;
/* when 어떤 것을 하는가?*/
Calculator calculator = new Calculator();
int result = calculator.plus(firstNum, secondNum);
/* then */
Assertions.assertEquals(expected, result, delta:5); // 두 수를 더하고 결과값과 확인해보겠다
// delta = 근사치(오차범위) 허용
Assertions.assertNotEquals(firstNum, secondNum); // 부정 단정 -> 틀리면 성공하는것
2. assertNull Null 여부 테스트
@Test
@DisplayName("Null 여부 테스트")
void testAssertNulls() {
/*given*/
String str = null;
/*when*/
/*then*/
//Assertions.assertNotNull(str);//null인가?
Assertions.assertNull(str);
}
3. assertAll : 한 번에 여러 검증을 수행할 수 있는 메소드
@Test
@DisplayName("동시에 여러 값에 대한 검증 테스트")
void testAssertAll() {
/*given*/
String firstName = "길동";
String lastName = "홍";
Person person = new Person(firstName, lastName);
/*when*/
/*then*/
Assertions.assertAll(
"그룹화 된 테스트의 이름(테스트 실패 시 보여지는 부분)",
//() -> Assertions.assertEquals("", person.getFirstName()),//여기서 틀렸음-> header 부분이 출력된다.
() -> Assertions.assertEquals(firstName, person.getFirstName()),
() -> Assertions.assertEquals(lastName, person.getLastName()
);
틀린 화면 : 
맞은 화면 : 
4. assertThrows/assertInstanceOf : 이게 뭐지
@Test
@DisplayName("예외발생 테스트")
void testAssertThrow() {
/*given*/
int firstNum = 10;
int secondNum = 0;
/*when*/
NumberValidation validation = new NumberValidation();
// Exception 클래스 가져오기
Exception exception = Assertions.assertThrows(
IllegalArgumentException.class,
() -> validation.checkDividableNumbers(firstNum,secondNum)
);
/*then*/
Assertions.assertAll(
// 예외가 실제로 일어났는가?
() -> Assertions.assertInstanceOf(
IllegalArgumentException.class,
exception
),
// 예외 메시지가 동일한가?
() -> Assertions.assertEquals(
"0으로 나눌 수 없습니다.",
exception.getMessage()
)
);
@Test
@DisplayName("문자열 테스팅")
void testStringValidation() {
/*메소드 체인으로 하나하나 확인해볼 수 있다.*/
/*given*/
String expected = "hello world";
/*when*/
String actual = new String(expected);
/*then*/
Assertions.assertThat(actual)
.isNotEmpty()
.isNotBlank()
.contains("hello")
.doesNotContain("hahahahha")
.startsWith("h")
.endsWith("d")
.isEqualTo(expected);
}
@Test
@DisplayName("숫자 테스트")
void testIntegerValidation() {
/*given*/
double pi = Math.PI;
Double actual = Double.valueOf(pi);
/*when*/
/*then*/
Assertions.assertThat(actual)
.isPositive() //양수인가
.isGreaterThan(3)
.isLessThan(5)
.isEqualTo(Math.PI);
}
@Test
@DisplayName("날짜 테스트 하기")
void testLocalDateTimeValidation() {
/*given*/
String birthDay = "1999-02-12T02:03:33.135";
LocalDateTime bDay = LocalDateTime.parse(birthDay); //Parse -> 문자열to날짜형
Assertions.assertThat(bDay)
.hasYear(1999)
.hasMonthValue(2)
.hasDayOfMonth(12)
.isBetween("1999-02-01T02:03:33.135","1999-12-12T02:03:33.135")
.isBefore(LocalDateTime.now())
.isAfter("1999-02-01T02:03:33.135");
}
@Test
@DisplayName("예외테스트")
void testExceptionValidation() {
Throwable thrown = AssertionsForClassTypes.catchThrowable(() -> {
throw new IllegalArgumentException("잘못된 파라미터를 입력하였습니다.");
});
Assertions.assertThat(thrown)
.isInstanceOf(IllegalArgumentException.class)//클래스가 해당 예외 매개
.hasMessageContaining("파라미터");
}
@Test
@DisplayName("filtering assertions 테스트")
void testFilteringAssertions() {
/*given*/
Member member1 = new Member(1,"user01","홍길동",16);
Member member2 = new Member(2,"user02","이순신",26);
Member member3 = new Member(3,"user03","신사임당",36);
List<Member> members = Arrays.asList(member1, member2, member3); //각 멤버 객체를 List로
/*when*/
/*then*/
Assertions.assertThat(members)
.filteredOn(member->member.getAge()>19)
.containsOnly(member2, member3) // 위 필터 결과에 member들이 포함되어 있는가? -> member1은 실패 뜸(19세미만)
.filteredOn("age", 26) // age==26이 존재하는가?
.containsOnly(member2); //위 모든 조건을 member2 가 통과하는가?
}
@BeforeAll
@afterAll
각 어노테이션 확인 - 자원과 체크 타이밍 확인(생명주기 확인 어노테이션)
public class LifeCycleAnnotationTests {
/*테스트 메소드 확인 전 어노테이션 먼저 확인해보자*/
/*before all*/
static void beforeAll() {
System.out.println("before all 동작");//클래스의 모든 테스트 전에 한 번 실행되는 메서드를 지정한다
}
/*before each*/
void beforeEach() {
System.out.println("before each 동작");
}
@BeforeAll
static void before() {
System.out.println("beforeall");
}
@BeforeEach
void beforeEach1() {
System.out.println("beforeEach1");
}
@AfterAll
static void afterAll() {
System.out.println("afterAll");
}
@AfterEach
void afterEach1() {
System.out.println("afterEach1");
}
@Test
void test1() {
System.out.println("테스트 코드1");
}
@Test
void test2() {
System.out.println("테스트 코드1");
}
}
결과
beforeall
beforeEach1
테스트 코드1
afterEach1
beforeEach1
테스트 코드1
afterEach1
afterAll
@Disabled : 테스트 무시 → 테스트가 진행되지 않고 무시된다.
@Timeout(value = 시간) : 제한시간 지나면 오류
@Test
@Timeout(value = 1)
void testTimeOut() {
try {
Thread.sleep(9900);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("타임아웃 테스트");
}
Order(순서int) : 실행 순서 설정
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class AdditionalAnnotationTests {
@Test
@Order(1)
void testFirst() {
System.out.println("first");
}
@Test
@Order(3)
void testSecond() {
System.out.println("second");
}
@Test
@Order(2)
void testThird() {
System.out.println("third");
}
# 결과
first
third
second
@RepeatedTest(반복횟수) : 테스트를 반복한다.
@RepeatedTest(10)
void testRepeated() {
System.out.println("반복 테스트");
}
# 결과
반복 테스트
반복 테스트
반복 테스트
반복 테스트
반복 테스트
반복 테스트
반복 테스트
반복 테스트
반복 테스트
반복 테스트