JAVA
AssertJ 주요 기능 공부
Stranger_s
2020. 2. 19. 14:37
AssertJ
- 테스트 코드 작성 시 가독성이 뛰어나고 상세한 에러 메시지를 제공해주는 라이브러리입니다.
- 지원해주는 대표적인 기능들을 테스트해보면서 살펴보겠습니다.
살펴본 기능에 대한 모든 것은 AssertJ Core features highlight를 참조하면서 정리하였습니다.
의존성 추가
// build.gralde
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.0')
testImplementation('org.assertj:assertj-core:3.11.1')
compileOnly 'org.projectlombok:lombok:1.18.12'
annotationProcessor 'org.projectlombok:lombok:1.18.12'
}
test {
useJUnitPlatform()
}
- 테스트 작성을 위해 그래들 프로젝트에 junit5 jupiter, assertj-core, lombok을 추가하였습니다.
1. Test Fail Message
- AssertJ에서는 테스트 실패 시 나타낼 메시지를 as로 표현할 수 있습니다.
- as는 검증 문 앞에 작성해야 하며 뒤에 작성 시 호출되지 않습니다.
@Test
void test() throws Exception {
String str = "name";
assertThat(str).as("값을 확인해주세요. 현재 값: %s", str)
.isEqualTo("name2");
}
2. Collection Filtering
- AssertJ를 사용하면 간단하게 테스트할 데이터들을 필터링할 수 있습니다.
- 간단한 예를 통해 알아보겠습니다.
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class Member {
private String name;
private int age;
private MemberType type;
}
enum MemberType{
ADMIN, USER
}
- 우선 테스트할 대상인 Member를 정의하였습니다.
- Member는 이름, 나이, 타입을 가지고 있습니다.
- 아래에서 해당 데이터로 테스트를 진행해 보겠습니다.
import org.junit.jupiter.api.Test;
import java.util.List;
import static assertj.MemberType.ADMIN;
import static assertj.MemberType.USER;
import static org.assertj.core.api.Assertions.*;
import static org.assertj.core.util.Lists.newArrayList;
class MemberTest {
private Member dexter = new Member("dexter", 12, ADMIN);
private Member james = new Member("james", 30, ADMIN);
private Member park = new Member("park", 23, USER);
private Member lee = new Member("lee", 33, USER);
private List<Member> members = newArrayList(dexter, james, park, lee);
@Test
void sample() throws Exception {
// 1
assertThat(members)
.filteredOn("type", ADMIN)
.containsOnly(dexter, james);
// 2
assertThat(members)
.filteredOn(m -> m.getType() == USER)
.containsOnly(park, lee);
// 3
assertThat(members).
filteredOn("type", in(ADMIN, USER))
.containsOnly(dexter, james, park, lee);
// 4
assertThat(members)
.filteredOn("type", not(ADMIN))
.containsOnly(park, lee);
// 5
assertThat(members)
.filteredOn("type", ADMIN)
.filteredOn(m -> m.getAge() > 20)
.containsOnly(james);
}
}
- ADMIN, USER Member를 각 두 명씩 생성하였습니다. 해당 값들을 List에 넣어 테스트해보겠습니다.
- 해당 데이터는 아래의 테스트에서 계속 사용됩니다.
- filteredOn을 이용하여 간단하게 필터링할 수 있습니다.
- 첫 번째 아규먼트에는 해당 데이터의 필드 파라미터명을 입력하면 됩니다.
- 두 번째 아규먼트에는 필터 할 상태를 정의합니다.
- 1. type 파라미터에서 ADMIN Member만 필터링합니다.
- 2. 람다식을 활용하여 필터링이 가능합니다.
- 3, 4. assertj에서 지원해주는 메서드를 통해 다양한 조건을 넣을 수 있습니다.
- 5. 필터링은 연쇄적으로 진행할 수 있습니다.
3. Collection Data Extracting
- 만약 Member List에서 해당 Member들의 이름을 테스트하려면 어떻게 해야 할까요?
@Test
void no_extracting() throws Exception{
List<String> names = new ArrayList<>();
for (Member member : members) {
names.add(member.getName());
}
assertThat(names).containsOnly("dexter", "james", "park", "lee");
}
- 따로 Name List를 만들고 for each를 통해 테스트할 수 있습니다.
- 하지만 assertj에서는 extracting을 지원해주며 해당 메서드를 통해 간결하게 테스트할 수 있습니다.
@Test
void extracting() throws Exception{
assertThat(members)
.extracting("name")
.containsOnly("dexter", "james", "park", "lee");
}
- 위의 테스트보다 훨씬 간결해진 것을 확인할 수 있습니다.
- 추가적으로 extracting에서 활용할 수 있는 기능 몇 개에 대해 알아보겠습니다.
@Test
void extracting_more() throws Exception {
// 1
assertThat(members)
.extracting("name", String.class)
.contains("dexter", "james", "park", "lee");
// 2
assertThat(members)
.extracting("name", "age")
.contains(
tuple("dexter", 12),
tuple("james", 30),
tuple("park", 23),
tuple("lee", 33)
);
}
- 1. 추출할 데이터가 하나라면 타입 지정이 가능합니다.
- 2. 여러 데이터를 추출한 후 assertj에서 지원해주는 tuple로 테스트를 할 수 있습니다.
4. Soft Assertions
- 보통 테스트를 진행할 때 앞의 assertThat이 실패하면 해당 테스트는 중지됩니다.
- Soft Assertions을 이용하면 우선 모든 assertThat을 실행하고 해당 실패 내역을 확인할 수 있습니다.
class MemberTest {
private Member dexter = new Member("dexter", 12, ADMIN);
private Member james = new Member("james", 30, ADMIN);
private Member park = new Member("park", 23, USER);
private Member lee = new Member("lee", 33, USER);
private List<Member> members = newArrayList(dexter, james, park, lee);
@Test
void no_softAssertion() throws Exception{
assertThat(dexter.getAge()).as("Dexter Age Test").isEqualTo(11);
assertThat(james.getAge()).as("James Age Test").isEqualTo(31);
assertThat(park.getAge()).as("Park Age Test").isEqualTo(24);
assertThat(lee.getAge()).as("Lee Age Test").isEqualTo(32);
}
}
// 출력
[Dexter Age Test]
Expecting:
<12>
to be equal to:
<11>
but was not.
- 해당 테스트는 첫 번째 assertThat이 실패하므로 Dexter Age Test에서 테스트가 중지됩니다.
- soft assertThat을 이용하여 테스트를 작성해 보겠습니다.
@Test
void softAssertion() throws Exception{
SoftAssertions softAssertions = new SoftAssertions();
softAssertions.assertThat(dexter.getAge()).as("Dexter Age Test").isEqualTo(11);
softAssertions.assertThat(james.getAge()).as("James Age Test").isEqualTo(31);
softAssertions.assertThat(park.getAge()).as("Park Age Test").isEqualTo(24);
softAssertions.assertThat(lee.getAge()).as("Lee Age Test").isEqualTo(32);
softAssertions.assertAll();
}
// 출력
Multiple Failures (4 failures)
org.opentest4j.AssertionFailedError: [Dexter Age Test]
Expecting:
<12>
to be equal to:
<11>
but was not.
at MemberTest.softAssertion(MemberTest.java:35)
org.opentest4j.AssertionFailedError: [James Age Test]
Expecting:
<30>
to be equal to:
<31>
but was not.
at MemberTest.softAssertion(MemberTest.java:36)
org.opentest4j.AssertionFailedError: [Park Age Test]
Expecting:
<23>
to be equal to:
<24>
but was not.
at MemberTest.softAssertion(MemberTest.java:37)
org.opentest4j.AssertionFailedError: [Lee Age Test]
Expecting:
<33>
to be equal to:
<32>
but was not.
- 사용법은 간단합니다. Assertj의 SoftAssertions 인스턴스를 만듭니다.
- assertThat앞에 해당 인스턴스를 추가합니다.
- 마지막에 softAssertions.assertAll()을 호출하면 테스트가 진행됩니다.
- 해당 테스트의 출력을 보시면 모든 assertThat이 진행된 것을 확인할 수 있습니다.
@Test
void softAssertion_JUnitSoft() throws Exception{
SoftAssertions.assertSoftly(softAssertions -> {
softAssertions.assertThat(dexter.getAge()).as("Dexter Age Test").isEqualTo(11);
softAssertions.assertThat(james.getAge()).as("James Age Test").isEqualTo(31);
softAssertions.assertThat(park.getAge()).as("Park Age Test").isEqualTo(24);
softAssertions.assertThat(lee.getAge()).as("Lee Age Test").isEqualTo(32);
});
}
- assertSoftly를 사용하면 따로 assertAll을 호출할 필요 없이 사용할 수 있습니다.
5. File Assertions
- AssertJ에서는 파일에 대한 테스트를 제공합니다.
- 해당 데이터가 파일인지, 존재하는 지등에 대한 테스트를 진행할 수 있고 해당 파일 내용을 읽어 테스트를 진행할 수 있습니다.
@Test
void file() throws Exception{
File file = writeFile("Temp", "You Know Nothing Jon Snow");
// 1
assertThat(file).exists().isFile().isRelative();
// 2
assertThat(contentOf(file))
.startsWith("You")
.contains("Know Nothing")
.endsWith("Jon Snow");
}
private File writeFile(String fileName, String fileContent) throws Exception {
File file = new File(fileName);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), Charset.defaultCharset()));
writer.write(fileContent);
writer.close();
return file;
}
- 1. 해당 파일이 존재하는지, 파일이 맞는지, 상대 경로인지 테스트합니다.
- 2. 해당 파일의 내용을 contentOf로 가져와 테스트를 진행합니다.
- 해당 값의 시작, 끝 값과 포함하는 값 등을 테스트할 수 있습니다.
6. Exception Assertions
- 예외를 테스트하는 방법에 대해 알아보겠습니다.
// 1
@Test
void exception() throws Exception {
assertThatThrownBy(() -> throwException())
.isInstanceOf(Exception.class)
.hasMessage("예외 던지기!")
.hasStackTraceContaining("Exception");
}
// 2
@Test
void thrownTest() throws Exception{
Throwable throwable = catchThrowable(() -> throwException());
assertThat(throwable).isInstanceOf(Exception.class).hasMessage("예외 던지기!");
}
private void throwException() throws Exception {
throw new Exception("예외 던지기!");
}
- AssertJ로 예외를 테스트하는 방법은 크게 두 가지가 존재합니다.
- 1. assertThatThrownBy에 해당 예외가 발생하는 코드를 작성합니다.
- 예외 종류, 메시지, 스택 트레이스등에 대한 정보를 테스트할 수 있습니다.
- 2. catchThrowable에서 예외가 발생하는 코드를 작성하고 해당 throwable을 위와 같은 방법으로 테스트할 수 있습니다.
7. Custom Comparison Assertions
- isEqualTo로 비교 시 해당 객체의 참조가 같을 경우에만 테스트가 성공합니다.
- 따로 Comparison을 커스텀하여 테스트를 진행할 수도 있습니다.
@Getter
@AllArgsConstructor
public class Item {
String name;
}
- 테스트를 위해 Item Class를 정의합니다.
@Test
void equalTest() throws Exception{
Item item1 = new Item("item1");
Item item2 = new Item("item1");
// 테스트 실패
// assertThat(item1).isEqualTo(item2);
assertThat(item1).isNotEqualTo(item2);
assertThat(item1)
.usingComparator(new Comparator<Item>() {
@Override
public int compare(Item o1, Item o2) {
return o1.getName().compareTo(o2.getName());
}
})
.isEqualTo(item2);
assertThat(item1)
.usingComparator((a, b) -> a.getName().compareTo(b.getName()))
.isEqualTo(item2);
}
- item1, 2는 서로 다른 참조를 가지고 있으므로 isNotEqualTo입니다.
- 이럴 때 usingComparator를 이용하여 원하는 Comparator를 설정하면 해당 Comparator에 맞게 isEqualTo를 사용할 수 있습니다.
@Test
void equalTest2() throws Exception{
Item item1 = new Item("item1");
Item item2 = new Item("item1");
Item item3 = new Item("item1");
List<Item> itemList = new ArrayList<>();
itemList.add(item1);
itemList.add(item2);
assertThat(itemList).contains(item1, item2).doesNotContain(item3);
assertThat(itemList)
.usingElementComparator((a, b) -> a.getName().compareTo(b.getName()))
.contains(item1, item2, item3);
}
- 컬렉션의 데이터들도 usingElementComparator를 통해 데이터를 비교할 수 있게 됩니다.
- 그렇기 때문에 itemList에는 해당 데이터가 없지만 comparator로 비교 시 같은 값을 가지므로 테스트는 통과합니다.
8. Field Comparisons
- 위와 같이 Comparator를 커스텀할 수도 있지만 기본적인 필드 비교는 AssertJ에서 지원해줍니다.
@Getter
@AllArgsConstructor
public class Item {
String name;
int price;
}
- Item Class에 price 필드를 추가해 줍니다.
@Test
void comparison() throws Exception{
Item item1 = new Item("item1", 1000);
Item item2 = new Item("item1", 1000);
Item item3 = new Item("item1", 3000);
// 1
assertThat(item1).isEqualToComparingFieldByField(item2);
// price가 다르므로 실
//assertThat(item1).isEqualToComparingFieldByField(item3)
// 2
assertThat(item1).isEqualToComparingOnlyGivenFields(item2, "name");
assertThat(item1).isEqualToComparingOnlyGivenFields(item3, "name");
// 3
assertThat(item1).isEqualToIgnoringGivenFields(item2, "price");
assertThat(item1).isEqualToIgnoringGivenFields(item3, "price");
// 4
Item item4 = new Item(null, 1000);
assertThat(item1).isEqualToIgnoringNullFields(item4);
}
- 1. isEqualToComparingFieldByField를 사용할 경우 해당 객체의 필드 값이 같은지 비교해 줍니다.
- 2. isEqualToComparingOnlyGivenFields를 사용할 경우 지정한 데이터의 값이 같은지 비교해줍니다.
- 3. isEqualToIgnoringGivenFields를 사용할 경우 지정한 데이터를 제외하고 값이 같은지 비교해줍니다.
- 4. isEqualToIgnoringNullFields를 사용할 경우 null필드 데이터를 제외하고 값이 같은지 비교해줍니다.
- isEqualToComparingFieldByField를 비교할 때 객체 안의 데이터의 참조값이 다를 경우 해당 테스트는 실패합니다. 이럴 때 사용할 수 있는 방법에 대해 알아보겠습니다.
@Getter
@AllArgsConstructor
public class Item {
String name;
int price;
Person producer;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
public Item(String name) {
this.name = name;
}
}
@Getter
@AllArgsConstructor
class Person{
String name;
}
- Person Class를 만들고 Item Class에 producer 필드를 하나 추가해줍니다.
@Test
void recursively() throws Exception{
Person person1 = new Person("Dexter");
Person person2 = new Person("Dexter");
Item item1 = new Item("item1", 1000, person1);
Item item2 = new Item("item1", 1000, person2);
// 테스트 실패
// assertThat(item1).isEqualToComparingFieldByField(item2);
assertThat(item1).isEqualToComparingFieldByFieldRecursively(item2);
}
- item1, 2의 name, price는 같지만 person은 서로 다른 참조를 가집니다.
- 하지만 각 person의 필드 값도 같은 값을 가지고 있습니다.
- 이럴 때 isEqualToComparingFieldByField를 사용하면 테스트는 실패합니다.
- 하지만 isEqualToComparingFieldByFieldRecursively를 사용하면 테스트를 진행할 수 있습니다.