티스토리 뷰
단위 테스트에서는 특정 메소드의 반환값이 예상값과 같은지, 메소드 호출 시 전달되는 인자에 따라 특정 예외가 발생하는지 등을 검증해야 한다. 이를 위해 실제 if문 또는 try-catch문 등을 사용하여 직접 검증할 수도 있지만 자바에서는 보다 편리하게 이를 수행할 수 있는 방법이 존재한다.
이번 글에서는 조금 쉬어가는 타임으로 JUnit이 제공하는 검증을 위한 유틸리티 메소드인 Assertions에 대해 알아보고자 한다. 자바에서 단위 테스트를 한번이라도 해본적이 있다면 Assertions 클래스를 제공하는 2개의 패키지가 있다는 것을 알 것이다. 바로 JUnit과 AssertJ이다. 먼저 JUnit의 Assertions 메소드를 자주 사용하는 것들 위주로 살펴보자.
assertEquals & assertNotEquals
assertEquals
메소드는 expected value와 actual value가 같은지 비교하고 같다면 테스트 통과, 다르다면 테스트를 실패시킨다. expected
부분에는 정답 값을, actual
부분에는 검사하고자 하는 값을 작성해주면된다. 테스트 실패 시 콘솔창에 메시지를 띄우고 싶다면 message
파라미터를 전달한다.
@Test
void testEquals() {
assertEquals("hi", "hi", "테스트 실패!"); //expected, actual, message
}
class UnitTest {
static class A {
int a;
public A(int a) {
this.a = a;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof A) return a == ((A) obj).a;
return false;
}
}
@Test
void testEquals() {
A expected = new A(1);
A actual = new A(1);
assertEquals(expected, actual, "테스트 실패!");
}
}
해당 메소드는 기본적으로 동등성(equality) 비교를 수행하는데, 이때 동등성 비교는 객체 자체가 아닌 객체에 대한 정보를 비교하는 것으로 equals()
를 호출하며 이와 반대되는 개념인 동일성(identity) 비교(또는 == 비교)는 객체 참조 자체를 비교하는 것으로 hashCode()
를 호출한다. 따라서 만약 assertEquals
메소드에서 비교 대상 클래스의 equals()
메소드를 오버라이딩 하지 않은 경우에는 Object
클래스의 equals()
에 의해 해시값이 비교되기 때문에 동일성 비교를 수행하게 된다.
@Test
void testEquals() {
assertEquals(2, Math.abs(5 - 3));
assertEquals(3, 5, 2); //expected, actual, delta
assertEquals(5, 3, 2);
}
또한 exptected, actual value가 숫자인 경우 두 값의 차이를 테스트하기 위해서는 | expected - actual | 과 같은 절댓값 연산이 필요한데, assertEquals
에서는 delta
파라미터 하나만 추가해줌으로써 이 같은 동작을 수행할 수 있다. (float, double형만 가능하기 때문에 int형 전달 시 자동 형변환 발생)
asserSame & assertNotSame
그렇다면 객체간 동일성 비교를 수행하고 싶다면 어떻게 해야 할까? 바로 assertSame
메소드를 사용하면 된다. 이 경우 인자로 전달된 expected, actual value 간 == 비교(또는 hashCode()
호출)를 수행한다.
@Test
void testNotSame() {
A unexpected = new A(1);
A actual = new A(1);
assertNotSame(unexpected, actual);
}
assertArrayEquals
위 assertEquals
메소드와 비슷하게 동작하지만 assertArrayEquals
메소드는 expected array와 actual array를 비교한다. 모든 primitive 타입, 클래스 타입 array 간 비교가 가능하며 이 또한 마찬가지로 동등성 비교(equals()
호출)를 수행한다.
@Test
void testPrimitiveArrayEquals() {
char[] expected = {'h', 'a', 'p', 'p', 'y'};
char[] actual = "happy".toCharArray();
assertArrayEquals(expected, actual);
}
@Test
void testClassArrayEquals() {
A[] expected = {new A(1), new A(2), new A(3)};
A[] actual = {new A(1), new A(2), new A(3)};
assertArrayEquals(expected, actual);
}
assertIterableEquals
assertIterableEquals
메소드는 expected iterable과 actual iterable을 깊게 비교한다. 즉 오직 내부 값만을 비교하기 때문에 두 Iterable의 타입이 서로 같을 필요는 없다. 대신 iterable의 값을 순서대로 비교하기 때문에 두 iterable은 동일한 순서로 equal elements를 반환해야 한다. 메소드 이름에서 알 수 있듯이 이 또한 equals()
메소드를 호출하는 동등성 비교를 수행한다. 추가로 assertEquals
, assertSame
, assertArrayEquals
메소드도 마찬가지지만 expected, actual 값이 모두 null이면 두 값이 같은 것으로 판단한다.
@Test
void testIterableEquals() {
Iterable<String> al = new ArrayList<>(asList("Java", "Junit", "Test"));
Iterable<String> ll = new LinkedList<>(asList("Java", "Junit", "Test"));
Iterable<A> expected = new ArrayList<>(asList(new A(1), new A(2), new A(3)));
Iterable<A> actual = new ArrayList<>(asList(new A(1), new A(2), new A(3)));
assertIterableEquals(al, ll);
assertIterableEquals(expected, actual);
}
assertTrue and assertFalse
말그대로 주어진 condition의 값이 true인지, false인지를 검증하는 메소드이다. condition
파라미터 부분에는 boolean condition 뿐만 아니라 BooleanSupplier
도 전달할 수 있다.
@Test
void testBoolean() {
assertTrue(1 + 1 == 2);
BooleanSupplier condition = () -> 1 + 1 == 3;
assertFalse(condition);
}
assertNull & assertNotNull
이 또한 말그대로 주어진 객체가 null인지 null이 아닌지를 검증하는 메소드이다. null 체크이기 때문에 actual
파라미터로는 당연히 클래스 타입만 가능하다.
@Test
void testNull() {
assertNull(null);
assertNotNull(new A(1));
}
fail
해당 메소드를 호출하면 항상 테스트 fail이 발생한다. 파라미터로는 String
타입의 fail 메시지와 Thrawable
(Exception
의 부모 클래) 객체를 전달하며, 이때 해당 예외를 throw하면서 테스트를 실패시킨다. fail 메소드는 특히 작성이 완료되지 않은 테스트를 표시하는데 유용하게 사용할 수 있다.
@Test
void testFail() {
fail("테스트 실패!", new IllegalArgumentException());
}
assertAll
JUnit5에 새롭게 추가된 assertion으로, 앞서 소개했던 assert 메소드들을 그룹화하는 기능을한다. 해당 그룹화된 assertions는 함께 수행되고 테스트 결과 또한 함께 보고되며 assertions 중 하나라도 테스트에 실패하면 MultipleFailureError
가 발생한다.
assertAll
메소드의 첫번째 인자로 넘겨주는 String
타입의 heading
파라미터로 MutipleFailureError
의 예외 메시지 prefix를 지정할 수 있으며, 다음으로 Stream of Executable
타입의 assert 메소드들을 넘겨주어 이들을 그룹화할 수 있다. 그룹화된 assertions은 작성된 순서대로 하나씩 실행되며 이 중 하나가 OutOfMemory와 같은 하드웨어 관련 Exception을 날리지 않는 이상 테스트에 실패해도 중단되지 않고 모두 실행된다.
@Test
void testAll() {
assertAll(
"그룹 assertions",
() -> assertEquals(1, 1),
() -> assertNull(null),
() -> assertTrue(true)
);
}
assertThrows
주어진 코드가 특정 예외를 throw하는지 검증하는 메소드이다. 두번째 인자인 Executable
실행 시 아예 예외가 발생하지 않거나 첫번째 인자인 예외 클래스와 다른 예외가 throw되면 테스트에 실패하게 된다. 실제 Executable
이 throw한 예외 객체를 반환하기 때문에 해당 예외에 대한 추가 검증 또한 가능하다.
@Test
void testThrows() {
Throwable exception = assertThrows(
IllegalArgumentException.class,
() -> {
throw new IllegalArgumentException("Exception message");
}
);
assertEquals("Exception message", exception.getMessage());
}
마지막으로 assert-
메소드 인자로 넘기는 테스트 실패 메시지는 String
타입이 아닌 Supplier<String>
타입으로 넘겨주는 것이 좋다. 후자는 lazy 방식으로 메시지를 생성하는데, 예를 들어 테스트 실패 메시지에 "test " + "fail"과 같은 더하기 연산이 포함되어 있을 때, String
타입은 테스트 실패 여부와 상관 없이 항상 문자열 더하기 연산을 수행하는 반면, Supplier<String>
타입은 테스트 실패 시까지 더하기 연산에 대한 실행을 미룬다.
잘 와닿지 않을 수 있는데 이는 String
을 반환하는 메소드를 assert-
메소드에서 호출해보면 직접 확인할 수 있다. 실제 아래 테스트 메소드의 assertions을 하나씩 주석 처리하면서 실행시켜 보면, 첫번째 assertion는 테스트 성공 시에도 getString
메소드를 호출하는 반면 두번째 assertion는 오직 테스트 실패 시에만 해당 메소드를 호출하는 것을 알 수 있다.
@Test
void test() {
assertEquals(1, 1, getString()); //String 타입 메시지
assertEquals(1, 1, this::getString); //Supplier<String> 타입 메시지
}
String getString() {
System.out.println("getString 메소드 호출");
return "테스트 실패!";
}
이것저것 부연설명하느라 글이 길어진 감이 없지 않아 있는데 각설하고, 다음 글에서는 또 다른 검증 라이브러리인 AssertJ Assertions의 검증 API들과 이것이 어떤 부분에서 JUnit Assertions 보다 메리트가 있는지 간략하게 알아보자.
끝.
참고
https://www.baeldung.com/junit-assertions
'자바 > 테스트' 카테고리의 다른 글
[Mockito] Mocking Framework with JUnit5 (1) | 2023.10.07 |
---|---|
[JUnit] Parameterized Test (0) | 2023.06.24 |
[JUnit] AssertJ Assertions (0) | 2023.06.23 |
[JUnit] 테스트 Tagging과 Filtering (0) | 2023.06.02 |
[JUnit] 자바 단위 테스트 프레임워크 (0) | 2023.05.31 |
- Total
- Today
- Yesterday
- spring boot
- SSE
- spring
- JPA
- Gitflow
- Git
- 단위 테스트
- Java
- rest api
- 전략 패턴
- github
- Spring Security
- spring aop
- junit5
- ParameterizedTest
- Assertions
- Linux
- facade 패턴
- QueryDSL
- Front Controller
- FrontController
- vscode
- 디자인 패턴
- Transaction
- 서블릿 컨테이너
- mockito
- C++
- 모두의 리눅스
- 템플릿 콜백 패턴
- servlet filter
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |