본문 바로가기
Engineering

[테스트 주도 개발 시작하기] 11장 ~ 부록

by unknownomad 2023. 6. 22.

<11장> 마치며

 

테스트 코드

  • 회귀 테스트로 사용 가능
  • 안전장치가 되어, 변경한 코드로 인해 소프트웨어가 비정상적으로 동작하는 것을 사전에 방지해줌
  • 버그 수정도 쉬워짐

 

회귀 테스트

  • Regression test
  • 개발하고 테스트한 소프트웨어의 기존 코드가 이후에 수정돼도 올바르게 동작하는지 확인하기 위함

 

레거시 코드에 대한 테스트 추가

  • 약간의 위험을 감수하더라도 테스트 코드를 만들 수 있게 리팩토링 감행하거나
  • 테스트 코드 만들고 싶은 코드를 별도 클래스로 분리 ➡️ 테스트할 코드의 범위 줄어듦

 


 

<부록 A> JUnit 5 추가 내용

 

조건에 따른 테스트

@EnabledOnOs: 특정 운영체제에서만 동작

@DisabledOnOs: 실행하지 않을 운영체제 지정

@Test
@EnabledOnOs(OS.WINDOWS)

...

@Test
@EnabledOnOs(OS.LINUX)

...

 

@EnabledOnJre, @DisabledOnJre: 자바 버전에 따라 테스트

@Test
@EnabledOnJre({JRE.JAVA_8, JRE.JAVA_9, JRE.JAVA_10, JRE.JAVA_11})

 

@EnabledIfSystemProperty, @DisabledIfSystemProperty: 시스템 프로퍼티 값을 비교하여 테스트 실행 여부 결정@EnabledIfSystemProperty

  • named 속성 : 시스템 프로퍼티 이름 지정
  • matches 속성 : 값의 일치 여부 검사 시 사용할 정규 표현식 지정
@Test
@EnabledIfSystemProperty(named = "java.vm.name", matches = ".*OpenJDK.*")

 

@EnabledIfEnvironmentVariable, @DisabledIfEnvironmentVariable

@EnabledIfEnvironmentVariable

  • named 속성, matches 속성 사용
  • named 속성에 환경변수 이름을 사용한다는 차이점이 있음

 

태깅과 필터링

@Tag

  • 테스트에 태그 달 때 사용
  • 클래스와 테스트 메서드에 적용 가능
@Tag("integration")
public class TagTest {
    
    @Tag("very-slow")
    @Test
    void verySlow() {
        int result = someVerySlowOp();
        assertEquals(result, 0);
    }
}

 

태그 이름 규칙

  • null 이나 공백이면 안 됨
  • 좌우 공백 제거 후 공백 포함하면 안 됨
  • ISO 제어 문자를 포함하면 안 됨
  • 다음 글자를 포함하면 안 됨 : , ( ) & | !

 

@Tag 애노테이션 사용 시 메이븐이나 그레이들에서 실행할 테스트 선택 가능

e.g. maven

<plugin>
    <artifactId>maven-surfire-plugin</artifactId>
    <version>2.22.1</version>
    <configuration>
        // 제외 대상이 우선함
        // <groups>: 실행에 포함할 태그 지정
        <groups>integration</groups>
        // <excludedGroups>: 실행에서 제외할 태그 지정
        <excludedGroups>slow | very-slow</excludedGroups>
    </configuration>
</plugin>

e.g. gradle

test {
    useJUnitPlatform {
        includeTags 'integration'
        excludeTags 'slow | very-slow'
    }
}

 

테스트 포함 대상이나 제외 대상 지정 시 사용하는 태그 식의 연산

다양한 태그 조합 사용해 테스트 대상 구분 가능

연산 기호 설명 예시
! NOT 연산 !integration
& AND 연산 slow & mock-server
| OR 연산 slow | very-slow

 

중첩 구성

@Nested

중첩 클래스에 테스트 메서드 추가 가능

public class Outer {

    @BeforeEach void outerBefore() {}
    
    @Test void outer() {}
    
    @AfterEach void outerAfter() {}
    
    @Nested
    class NestedA {
    
        @BeforeEach void nestedBefore() {}
        
        @Test void nested1() {}
        
        @AfterEach void nestedAfter() {}
    }
}

 

nested1() 실행 순서

  1. Outer 객체 생성
  2. NestedA 객체 생성
  3. outerBefore() 메서드 실행
  4. nestedBefore() 메서드 실행
  5. nested1() 테스트 실행
  6. nestedAfter() 메서드 실행
  7. outerAfter() 메서드 실행

 

중첩된 클래스는 내부 클래스이기에 외부 클래스의 멤버가 접근할 수 있음

public class UserServiceTest {
    
    private MemoryUserRepository memoryRepo;
    private UserService userService;
    
    @BeforeEach
    void setup() {
        memoryRepo = new MemoryUserRepository();
        userService = new UserService(memoryRepo);
    }
    
    @Nested
    class GivenUser {
        
        @BeforeEach
        void givenUser() {
            memoryRepo.save(new User("user", "name"));
        }
        
        @Test
        void dupId() { ... }
    }
    
    @Nested
    class GivenNoDupId {
        ...
    }
}

 

테스트 메시지

설명 문자열을 사용해 어디에서 실패했는지 쉽게 확인할 수 있도록

List<Integer> ret = getResults();
List<Integer> expected = Arrays.asList(1, 2, 3);

for(int i = 0; i < expected.size(); i++) {
    assertEquals(expected.get(i), ret.get(i), "ret[" + i + "]");
}

 

@TempDir 애노테이션 이용한 임시 폴더 생성

Case 1) 파일 관련 테스트 코드 작성 시 임시로 사용할 폴더가 필요한 경우 있음

  1. @TempDir 를 필드 또는 라이프사이클 관련 메서드나 테스트 메서드의 파라미터로 사용하면
  2. JUnit 이 임시 폴더 생성하고
  3. @TempDir 붙인 필드나 파라미터에 임시 폴더 경로 전달함

 

e.g. @TempDir 는 File 타입이나 Path 타입에 적용 가능

public class TempDirTest {
    
    // 테스트 메서드 실행 전, 임시 폴더 생성
    // + 그 폴더 정보를 tempFolder 필드에 할당
    // 필드에 적용 시 각 테스트 메서드 실행 때마다 임시 폴더 생성
    @TempDir
    File tempFolder;
    
    @Test
    void fileTest() {
        // tempFolder 에 파일 생성 등 작업
    }
}
@Test
// 특정 테스트 메서드에서만 임시 폴더 생성해 사용하고 싶을 때
// 테스트 메서드의 파라미터에 @TempDir 붙이기
void fileTest(@TempDir Path tempFolder) {
    ... test code
}
  • 테스트 실행 후, 생성한 임시 폴더 삭제 + 임시 폴더에 작성한 파일도 함께 삭제

 

Case 2) 특정 테스트 클래스 단위로 임시 폴더 생성하려면 정적 필드에 @TempDir 붙이기

  • 정적 필드에 @TempDir 적용 시, 각 테스트 메서드마다 임시 폴더 생성하지 않음
  • 테스트 클래스의 모든 테스트 메서드 실행하기 전,
  • 임시 폴더 한 번 생성 후,
  • 모든 테스트 메서드 실행 끝난 뒤 임시 폴더 삭제함
public class TempDirTest {

    @TempDir
    static File tempFolderPerClass;
    
    ...
}

 

정적 필드 대신, @BeforeAll 메서드의 파라미터에 @TempDir 적용해도 됨

public class TempDirTest {

    @BeforeAll
    static void setup(@TempDir File tempFolder) {
        ...
    }
}

 

@Timeout 애노테이션을 이용한 테스트 실행 시간 검증

  • JUnit 5.5 버전부터 지원됨
  • 테스트가 일정 시간 내에 실행되는지 검증 가능
public class TimeoutTest {
    
    @Test
    @Timeout(1)
    void sleep2seconds() throws InterruptedException {
        // 2초 동안 실행 멈춤 ➡️ 1초 초과했으니 테스트 실패
        Thread.sleep(2000);
    }
    
    @Test
    // 초가 아닌 다른 시간 단위 사용하려면 unit 속성에 TimeUnit 값 지정
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    void sleep40Mills() throws InterruptedException {
        Thread.sleep(40);
    }
}

 


 

<부록 B> JUnit 4 기초

 

JUnit 4 JUnit 5
@Before @BeforeEach
@Test @Test
@After @AfterEach
  • JUnit 5 와 달리, JUnit 4 의 테스트 메서드는 public 이어야 함
  • @Before, @After 도 public 메서드에 붙여야 함

 

단언 메서드

메서드 설명
assertEquals(expected, actual) 실제 값(actual)이 기대하는 값(expected)과 같은지 검사
assertNotEquals(unexpected, actual) 실제 값이 특정 값(unexpected)과 같지 않은지 검사
assertSame(Object expected, Object actual) 두 객체가 동일한 객체인지 검사
assertNotSame(Object unexpected, Object actual) 두 객체가 동일하지 않은 객체인지 검사
assertTrue(boolean condition) 값이 true 인지 검사
assertFalse(boolean condition) 값이 false 인지 검사
assertNull(Object actual) 값이 null 인지 검사
assertNotNull(Object actual) 값이 null 이 아닌지 검사
fail() 테스트를 실패 처리함
  • JUnit 4 는 assertAll() / assertThrows() 미제공

 

JUnit 4 에서 익셉션 발생 여부 테스트 시 @Test 의 expected 속성 사용

@Test(expected = ArithmeticException.class)
public void throwEx() {
    divide(1, 0);
}

발생한 익셉션 객체를 사용해 추가 검증해야 할 때는 expected 속성 사용 불가

아래처럼 try-catch 사용해 직접 검증 처리해야 함

ArithmeticException thrown = null
try {
    divide(1, 0);
} catch(ArithmeticException ex) {
    thrown = ex;
}
assertNotNull(thrown);
assertTrue(thrown.getMessage().contains("zero"));

 


 

<부록 C> Mockito 기초 사용법

 

Mockito

모의 객체 생성, 검증, 스텁 지원하는 프레임워크

 

모의 객체 생성

Mockito.mock() 통해 클래스, 인터페이스, 추상 클래스에 대한 모의 객체 생성 가능

public interface GameNumGen {
    String generate(GameLevel level);
}

public class GameGenMockTest {
    @Test
    void mockTest() {
        GameNumGen genMock = mock(GameNumGen.class);
    }
}

 

스텁 설정

Case 1) BDDMockito.given() 이용한 스텁 설정

  • 모의 객체 생성 뒤, BDDMockito 클래스 통해 모의 객체에 스텁 구성할 수 있음
public class GameGenMockTest {
    
    @Test
    void mockTest() {
    
        // 1) 모의 객체 생성
        GameNumGen genMock = mock(GameNumGen.class);
        // 2) 스텁 설정
        given(genMock.generate(GameLevel.EASY)).willReturn("123");
        // 3) 스텁 설정에 매칭되는 메서드 실행
        String num = genMock.generate(GameLevel.EASY);
        
        assertEquals("123", num);
    }
}

 

Case 2) 특정 타입의 익셉션 발생하도록 스텁 설정

BDDMockito.willThrow(): 리턴 타입이 void 인 메서드에 대헤 익셉션 발생시킴

@Test
void mockThrowTest() {
    GameNumGen genMock = mock(GameNumGen.class);
    given(genMock.generate(null)).willThrow(IllegalArgumentException.class);

    assertThrows(
        IllegalArgumentException.class,
        () -> genMock.generate(null));
}

 

Case 3) BDDMockito.given() 이용한 스텁 설정

  • BDDMockito.willThrow(): 발생할 익셉션 타입이나 익셉션 객체를 인자로 받음
  • BDDMockito.given()
    • 모의 객체를 전달받음(메서드 실행 x)
    • ➡️ 모의 객체 자신을 리턴 + 익셉션 발생할 메서드 호출
    • 실제 모의 객체의 메서드 호출이 아닌, 익셉션 발생할 모의 객체를 설정함
public class VoidMethodStubTest {

    @Test
    void voidMethodWillThrowTest() {
        List<String> mockList = mock(List.class);
        willThrow(UnsupportedOperationException.class)
            .given(mockList) // 모의 객체 전달 받음
            .clear();
            
        assertThrows(
            UnsupportedOperationException.class,
            () -> mockList.clear());
    }
}

 

인자 매칭 처리

given(genMock.generate(GameLevel.EASY)).willReturn("123");

String num = genMock.generate(GameLevel.NORMAL);
  • 위 코드는 스텁 설정 시 generate() 의 인자로 EASY 전달함
  • 근데 실제로 generate() 호출 시 NORMAL 을 인자로 전달함
  • ➡️ genMock.generate(GameLevel.NORMAL) 코드는 스텁 설정 시 사용한 인자와 불일치하기에 null 리턴

Why?

  • Mockito 는 일치하는 스텁 설정이 없으면 리턴 타입의 기본 값을 리턴함
  • 기본 데이터 타입이 아닌 String 이나 List 같은 참조 타입이면 null 리턴

 

ArgumentMatchers.any() 메서드로 (임의의) 인자 값 매칭 처리

public class AnyMatcherTest {

    @Test
    void anyMatchTest() {
        GameNumGen genMock = mock(GameNumGen.class);
        given(genMock.generate(any())).willReturn("456");
        
        String num = genMock.generate(GameLevel.EASY);
        assertEquals("456", num);
        
        String num2 = genMock.generate(GameLevel.NORMAL);
        assertEquals("456", num2);
    }
}

 

관계도

상속 관계이기에, ArgumentMatchers.any() 대신, Mockito.any() 나 BDDMockito.any() 사용 가능

 

ArgumentMatchers 클래스가 제공하는 메서드

메서드 설명
anyInt(), anyShort(), anyLong(), anyByte(),
anyChar(), anyDouble(), anyFloat(), anyBoolean()
기본 데이터 타입에 대한 임의 값 일치
anyString() 문자열에 대한 임의 값 일치
any() 임의 타입에 대한 일치
anyList(), anySet(), anyMap(), anyCollection() 임의 콜렉션에 대한 일치
matches(String), matches(Pattern) 정규표현식을 이용한 String 값 일치 여부
eq(값) 특정 값과 일치 여부

 

스텁 설정할 메서드의 인자가 2개 이상인 경우 주의할 점

List<String> mockList = mock(List.class);

// mockList.set() 의 스텁 설정 시
// 첫 인자는 anyInt() 이용해 임의의 int 값에 일치하도록,
// 다음 인자는 "123" 으로 정확한 값에 일치하도록 설정하면
given(mockList.set(anyInt(), "123")).willReturn("456");

// 익셉션 발생
String old = mockList.set(5, "123);
  • ArgumentMatchers 의 anyInt() 나 any() 등 메서드는 내부적으로 인자의 일치 여부 판단 위해 ArgumentMatcher 등록함
  • Mockito 는 한 인자라도 ArgumentMatcher 사용해 설정하면 모든 인자를 ArgumentMatcher  이용해 설정하도록 함

 

ArgumentMatchers.eq()

임의의 값과 일치하는 인자가 정확하게 일치하는 인자를 함께 사용하고 싶을 때 사용

@Test
void mixAnyAndEq() {
    List<String> mockList = mock(List.class);
    
    given(mockList.set(anyInt(), eq("123"))).willReturn("456");
    
    String old = mockList.set(5, "123");
    assertEquals("456", old);
}

 

행위 검증

모의 객체의 역할 중 하나는 실제로 모의 객체가 불렸는지 검증하는 것

public class GameTest {

    @Test
    void init() {
        GameNumGen genMock = mock(GameNumGen.class);
        Game game = new Game(genMock);
        game.init(GameLevel.EASY);
        
        then(genMock).should().generate(GameLevel.EASY);
    }
}
  1. BDDMockito.then(): 메서드 호출 여부 검증할 모의 객체 전달 받음
  2. .should(): 모의 객체의 메서드가 불려야 한다는 것을 설정
  3. 그 다음 실제 불려야 할 메서드 지정

 

정확한 값이 아닌 메서드가 불렸는지의 여부가 중요하다면 any(), anyInt() 등 사용해 인자 지정하면 됨

then(genMock).should().generate(any());

 

정확하게 한 번만 호출된 것을 검증하고 싶다면 .should().Mockito.only() 를 인자로 전달

then(genMock).should(only()).generate(any());

 

메서드 호출 횟수 검증 위해 Mockito 클래스가 제공하는 메서드

메서드 설명
only() 한 번만 호출
times(int) 지정한 횟수만큼 호출
never() 호출하지 않음
atLeast(int) 적어도 지정한 횟수만큼 호출
atLeastOnce() atLeast(1) 과 동일
atMost(int) 최대 지정한 횟수만큼 호출

 

인자 캡쳐

  • 단위 테스트 실행하다 보면 모의 객체 호출 시 사용한 인자를 검증해야 할 때가 있는데, 이때 이용함
  • Mockito 의 ArgumentCaptor 사용하면 메서드 호출 여부 검증 과정에서 실제 호출할 때 전달한 인자 보관 가능
public class UserRegisterMockTest {

    private UserRegister userRegister;
    private EmailNotifier mockEmailNotifier = mock(EmailNotifier.class);
    
    ...
    
    @DisplayName("가입하면 메일을 전송함")
    @Test
    void whenRegisterThenSendMail() {
        userRegister.register("id", "pw", "email@email.com");
        
        // String 타입의 인자 보관할 수 있는 ArgumentCaptor 생성
        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
        then(mockEmailNotifier)
            .should()
            .sendRegisterEmail(captor.capture()); // 모의 객체 호출 여부 검증 코드에서 인자로 전달
            
        String realEmail = captor.gerValue();
        assertEquals("email@email.com", realEmail);
    }
}

 

JUnit 5 확장 설정

  • Mockito 의 JUnit 5 의 확장 기능 사용하면 애노테이션 이용해 모의 객체 생성 가능
  • mockito-junit-jupiter 의존 추가 필요
  • 의존 추가 후 MockitoExtension 확장 사용 가능 ➡️ @Mock 애노테이션 붙인 필드에 대해 자동으로 모의 객체 생성해줌
@ExtendWith(MockitoExtension.class)
public class JUnit5ExtensionTest {

    @Mock
    private GameNumGen genMock;
    
    ...
}

 


 

<부록 D> AssertJ 소개

 

  • JUnit 은 테스트 실행 위한 프레임워크를 제공하나, 단언에 대한 표현력이 부족함

 

JUnit version

assertTrue(id.contains("a"));

AssertJ version

assertThat(id).contains("a");

 

AssertJ 장점

  • 테스트 코드의 표현력이 높아짐
  • 개발 도구의 자동 완성 기능 활용 가능

 

AssertJ 기본 사용법

assertThat(실제값).검증메서드(기대값)

 

기본 검증 메서드

거의 모든 타입에 사용할 수 있는 검증 메서드

메서드 설명  
isEqualTo(값) 값과 같은지 검증  
isNotEqualTo(값) 값과 같지 않은지 검증  
isNull() null 인지 검증  
isNotNull() null 이 아닌지 검증  
isIn(값 목록) 값 목록에 포함되어 있는지 검증 값 목록 : 가변 인자로 주거나 List 와 타입(Iterable 구현하는 타입) 이용해 전달
isNotIn(값 목록) 값 목록에 포함되어 있지 않은지 검증

 

Comparable 인터페이스를 구현한 타입이나 int, double 같은 숫자 타입의 값을 검증하는 메서드

메서드 설명
isLessThan(값) 값보다 작은지 검증
isLessThanOrEqualTo(값) 값보다 작거나 같은지 검증
isGreaterThan(값) 값보다 큰지 검증
isGreaterThanOrEqualTo(값) 값보다 크거나 같은지 검증
isBetween(값1, 값2) 값1과 값2 사이에 포함되는지 검증

 

boolean, Boolean 타입 위한 검증 메서드

메서드 설명
isTrue() 값이 true 인지 검증
isFalse() 값이 false 인지 검증

 

String 에 대한 추가 검증 메서드

특정 값 포함하는지 검사하는 메서드

메서드 설명
contains(CharSequence... values) 인자로 지정한 문자열들을 모두 포함하고 있는지 검증
containsOnlyOnce(CharSequence sequence) 해당 문자열을 딱 한 번만 포함하는지 검증
containsOnlyDigits() 숫자만 포함하는지 검증
containsWhitespaces() 공백 문자를 포함하고 있는지 검증
containsOnlyWhitespaces() 공백 문자만 포함하는지 검증
공백 문자 여부는 Character#isWhitespace() 따름
containsPattern(CharSequence regex) 지정한 정규 표현식에 일치하는 문자를 포함하는지 검증
containsPattern(Pattern pattern) 지정한 정규 표현식에 일치하는 문자를 포함하는지 검증

 

포함하지 않는지 여부 확인하는 메서드

메서드 설명
doesNotContain(CharSequence... values) 인자로 지정한 문자열들을 모두 포함하고 있지 않은지 검증
doesNotContainAnyWhitespaces() 공백 문자를 포함하고 있지 않은지 검증
doesNotContainOnlyWhitespaces() 공백 문자만 포함하고 있지 않은지 검증
doesNotContainPattern(Pattern pattern) 정규 표현식에 일치하는 문자를 포함하고 있지 않은지 검증
doesNotContainPattern(CharSequence pattern) 정규 표현식에 일치하는 문자를 포함하고 있지 않은지 검증

 

특정 문자열로 시작하거나 끝나는지 검증하는 메서드

메서드 설명
startsWith(CharSequence prefix) 지정한 문자열로 시작하는지를 검증
doesNotStartWith(CharSequence prefix) 지정한 문자열로 시작하지 않는지를 검증
endsWith(CharSequence suffix) 지정한 문자열로 끝나는지를 검증
doesNotEndWith(CharSequence suffix) 지정한 문자열로 끝나지 않는지를 검증

 

숫자에 대한 추가 검증 메서드

메서드 설명
isZero() / isNotZero() 0 인지 또는 0 이 아닌지를 검증
isOne() 1 인지를 검증
isPositive() / isNotPositive() 양수인지 또는 양수가 아닌지를 검증
isNegative() / isNotNegative() 음수인지 또는 음수가 아닌지를 검증

 

날짜 / 시간에 대한 검증 메서드

메서드 설명
isBefore(비교할 값) 비교할 값보다 이전인지 검증
isBeforeOrEqualTo(비교할 값) 비교할 값보다 이전이거나 같은지 검증
isAfter(비교할 값) 비교할 값보다 이후인지 검증
isAfterOrEqualTo(비교할 값) 비교할 값보다 이후이거나 같은지 검증

 

LocalDateTime, OffsetDateTime, ZonedDateTime 타입 검증 메서드

메서드 설명
isEqualToIgnoringNanos(비교할 값) 나노 시간을 제외한 나머지 값이 같은지 검증
= 초 단위까지 값이 같은지 검증
isEqualToIgnoringSeconds(비교할 값) 초 이하 시간을 제외한 나머지 값이 같은지 검증
= 분 단위까지 값이 같은지 검증
isEqualToIgnoringMinutes(비교할 값) 분 이하 시간을 제외한 나머지 값이 같은지 검증
= 시 단위까지 값이 같은지 검증
isEqualToIgnoringHours(비교할 값) 시 이하 시간을 제외한 나머지 값이 같은지 검증
= 일 단위까지 값이 같은지 검증

 

콜렉션에 대한 검증 메서드

List, Set 등 콜렉션에 대한 주요 검증 메서드

메서드 설명
hasSize(int expected) 콜렉션의 크기가 지정한 값과 같은지 검증
contains(E ... values) 콜렉션이 지정한 값을 포함하는지 검증
containsOnly(E ... values) 콜렉션이 지정한 값만 포함하는지 검증
containsAnyOf(E ... values) 콜렉션이 지정한 값 중 일부를 포함하는지 검증
containsOnlyOnce(E ... values) 콜렉션이 지정한 값을 한 번만 포함하는지 검증

 

Map 위한 주요 검증 메서드

메서드 설명
containsKey(K key) Map 이 지정한 키를 포함하는지 검증
containsKeys(K... keys) Map 이 지정한 키들을 포함하는지 검증
containsOnlyKeys(K... keys) Map 이 지정한 키만 포함하는지 검증
doesNotContainKeys(K... keys) Map 이 지정한 키들을 포함하지 않는지 검증
containsValues(VALUE... values) Map 이 지정한 값들을 포함하는지 검증
contains(Entry<K,V>... values) Map 이 지정한 Entry<K,V> 를 포함하는지 검증

 

익셉션 관련 검증 메서드

익셉션 발생 여부 검증

  • assertThatThrownBy(): 인자로 받은 람다에서 익셉션 발생하는지 검증
assertThatThrownBy(() -> readFile(new File("nofile.txt")));

 

isInstanceOf(): 발생한 익셉션의 타입을 추가 검증하고 싶을 때

assertThatThrownBy(() -> readFile(new File("nofile.txt")))
    .isInstanceOf(IOException.class);

 

특정 타입의 익셉션이 발생하는지 검증하는 다른 방법

assertThatExceptionOfType(): 발생할 익셉션의 타입 지정하고, isThrownBy() 이용해 익셉션이 발생할 코드 블록 지정

// Case 1
assertThatExceptionOfType(IOException.class)
    .isThrownBy(() -> {
        readFile(new File("nofile.txt"));
    });
    
// Case 2
assertThatIOException()
    .isThrownBy(() -> {
        readFile(new File("nofile.txt"));
    });
  • assertThatIOException()
  • assertThatNullPointerException()
  • assertThatIllegalArgumentException()
  • assertThatIllegalStateException()

 

doesNotThrowAnyException(): 익셉션 발생하지 않는 경우 검증 시

assertThatCode(() -> {
    readFile(new File("pom.xml"));
}).doesNotThrowAnyException();

 

SoftAssertions 로 모아서 검증하기

SoftAssertions

  • JUnit 5 의 assertAll() 과 유사함
  • 여러 검증을 한 번에 수행하고 싶을 때 사용
SoftAssertions soft = new SoftAssertions();
soft.assertThat(1).isBetween(0, 2);
soft.assertThat(1).isGreaterThan(2);
soft.assertThat(1).isLessThan(0);
soft.assertAll();
  • SoftAssertions 객체가 제공하는 assertThat() 은 해당 시점에 바로 검증 수행하지 않음
  • 실제 검증은 assertAll() 실행할 때 진행함

 

SoftAssertions.assertSoftly() 정적 메서드

assertAll() 직접 호출하지 않아도 됨

SoftAssertions.assertSoftly(soft -> {
    soft.assertThat(1).isBetween(0, 2);
    soft.assertThat(1).isGreaterThan(2);
    soft.assertThat(1).isLessThan(0);
});

 

as() 와 describedAs() 로 설명 달기

as() = describedAs()

assertThat(id).as("ID 검사").isEqualTo("abc");
assertThat(id).as("ID 검사: %s", "abc").isEqualTo("abc");

 

한 테스트 메서드에서 다수의 검증 메서드 실행 시 as() 유용함

List<Integer> ret = getResults();

List<Integer> expected = Arrays.asList(1, 2, 3);
SoftAssertions soft = new SoftAssertions();
for(int i = 0; i < expected.size(); i++) {
    soft.assertThat(ret.get(i)).as("ret[%d]", i).isEqualTo(expected.get(i));
}
soft.assertAll();

댓글