티스토리 뷰

자바/테스트

[JUnit] JUnit5 Assertions

다음김 2023. 6. 20. 12:25

 

단위 테스트에서는 특정 메소드의 반환값이 예상값과 같은지, 메소드 호출 시 전달되는 인자에 따라 특정 예외가 발생하는지 등을 검증해야 한다. 이를 위해 실제 if문 또는 try-catch문 등을 사용하여 직접 검증할 수도 있지만 자바에서는 보다 편리하게 이를 수행할 수 있는 방법이 존재한다. 

 

이번 글에서는 조금 쉬어가는 타임으로 JUnit이 제공하는 검증을 위한 유틸리티 메소드인 Assertions에 대해 알아보고자 한다. 자바에서 단위 테스트를 한번이라도 해본적이 있다면 Assertions 클래스를 제공하는 2개의 패키지가 있다는 것을 알 것이다. 바로 JUnit과 AssertJ이다. 먼저 JUnit의 Assertions 메소드를 자주 사용하는 것들 위주로 살펴보자. 
 

JUnit의 Assertions와 AssertJ의 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());
}

fail 메소드로 인한 테스트 실패와 인자로 넘겨준 예외 발생

 
 

 

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
링크
«   2024/09   »
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
글 보관함