<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() 실행 순서
- Outer 객체 생성
- NestedA 객체 생성
- outerBefore() 메서드 실행
- nestedBefore() 메서드 실행
- nested1() 테스트 실행
- nestedAfter() 메서드 실행
- 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) 파일 관련 테스트 코드 작성 시 임시로 사용할 폴더가 필요한 경우 있음
- @TempDir 를 필드 또는 라이프사이클 관련 메서드나 테스트 메서드의 파라미터로 사용하면
- JUnit 이 임시 폴더 생성하고
- @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);
}
}
- BDDMockito.then(): 메서드 호출 여부 검증할 모의 객체 전달 받음
- .should(): 모의 객체의 메서드가 불려야 한다는 것을 설정
- 그 다음 실제 불려야 할 메서드 지정
정확한 값이 아닌 메서드가 불렸는지의 여부가 중요하다면 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();
'Engineering' 카테고리의 다른 글
애자일 선언(Agile Manifesto) (0) | 2025.02.09 |
---|---|
[테스트 주도 개발 시작하기] 9 ~ 10장 (0) | 2023.06.22 |
[테스트 주도 개발 시작하기] 7 ~ 8장 (0) | 2023.06.15 |
[테스트 주도 개발 시작하기] 5 ~ 6장 (0) | 2023.06.14 |
[테스트 주도 개발 시작하기] 3 ~ 4장 (0) | 2023.06.13 |
댓글