개발

[Server] FixtureMonkey를 사용해보자

2023년 12월 25일

안녕하세요 팀 펫모리의 백엔드 개발자 장세은입니다.

오늘은 테스트 코드 작성할 때 편리한 라이브러리를 소개해보려 합니다. 바로 FixtureMonkey라는 라이브러리인데요! 어떤 라이브러리인지 한번 알아볼까요?

FixureMonkey는 네이버에서 만든 PBT (Property Based Testing) 라이브러리로 Java와 Kotlin을 지원합니다.

PBT : 정의된 속성을 기반으로 테스트 케이스를 만드는 기법


FixtureMonkey의 장점

FixeturMonkey를 사용하면 다음과 같은 장점이 있습니다.

1. 단순함

Product actual = fixtureMonkey.giveMeOne(Product.class);

FixtureMonkey를 사용하면 단 한 줄의 코드로 원하는 어떤 종류의 테스트 객체든 손쉽게 생성할 수 있습니다. 이를 통해 테스트를 더 빠르고 쉽게 작성할 수 있게 해줍니다. 또한 실제 코드에 대한 의존성 없이 생성할 수 있기 때문에 수정사항에 따라 프로덕션 코드나 테스트 환경을 변경할 필요가 없습니다.

2. 재사용성

ArbitraryBuilder<Product> actual = fixtureMonkey.giveMeBuilder(Product.class)
    .set("id", 1000L)
    .set("productName", "Book");

Fixture Monkey를 사용하면 여러 테스트에서 여러 인스턴스의 구성을 재사용할 수 있어 시간과 노력을 절약할 수 있습니다. 복잡한 사양은 위에 코드처럼 빌더 내에서 정의할 수 있습니다.

3. 무작위성

ArbitraryBuilder<Product> actual = fixtureMonkey.giveMeBuilder(Product.class);

then(actual.sample()).isNotEqualTo(actual.sample());

Fixture Monkey는 테스트 객체를 무작위 값으로 생성하여 테스트를 더 동적으로 만들어줍니다. 랜덤값이기 때문에 정적 데이터를 사용할 땐 찾을 수 없던 케이스를 발견할 수 있어 더 안전한 테스트 코드를 작성할 수 있습니다.

4. 다양성

// inheritance
class Foo {
  String foo;
}

class Bar extends Foo {
    String bar;
}

Foo foo = FixtureMonkey.create().giveMeOne(Foo.class);
Bar bar = FixtureMonkey.create().giveMeone(Bar.class);

// circular-reference
class Foo {
    String value;

    Foo foo;
}

Foo foo = FixtureMonkey.create().giveMeOne(Foo.class);

// anonymous objects
interface Foo {
    Bar getBar();
}

class Bar {
    String value;
}

Foo foo = FixtureMonkey.create().giveMeOne(Foo.class);

List, nested collections, enums 그리고 generic을 포함한 어떠한 객체도 지원합니다. 또한 상속 관계, 순환 참조에도 사용할 수 있어 다양한 시나리오 작성이 가능합니다.

시작하기

FixtureMonkey를 사용하려면 다음과 같은 준비물이 필요합니다.

  1. JDK 1.8 이상 또는 Kotlin 1.8 이상

  2. JUnit 5

  3. jqwik 1.7.3

그리고 다음과 같이 의존성만 추가해주면 됩니다.

//gradle 
testImplementation("com.navercorp.fixturemonkey:fixture-monkey-starter:1.0.0")
testFixturesImplementation("com.navercorp.fixturemonkey:fixture-monkey-starter:1.0.0")

사용방법

FixtureMonkey에서는 테스트 객체를 생성할 때 두 가지 방식을 지원합니다.

Lombok이 있는 경우

@Value
@AllArgsConstructor
public class Product {
    long id;

    String productName;

    long price;

    List<String> options;

    Instant createdAt;

    ProductType productType;

    Map<Integer, String> merchantInfo;
}

위에 있는 클래스에 대한 테스트 객체는 FixtureMonkey를 사용해 다음과 같이 생성할 수 있습니다.

@Test
void test() {
    // given
    FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
        .objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
        .build();

    // when
    Product actual = fixtureMonkey.giveMeOne(Product.class);

    // then
    then(actual).isNotNull();
}

Fixture Monkey는 자바 빈즈 규약을 사용하도록 되어있기 때문에 롬복으로 생성자를 만들 경우 ConstructorPropertiesArbitraryIntrospector을 사용해야 합니다.

Lombok이 없는 경우

public class Product {
    private long id;

    private String productName;

    private long price;

    private List<String> options;

    private Instant createdAt;

    private ProductType productType;

    private Map<Integer, String> merchantInfo;

    public Product() {

    }

		//getter
		//setter
}

Lombok이 없는 경우엔 다음과 같이 코드들 작성하면 됩니다.

FixtureMonkey는 BeanArbitraryIntrospector을 기본값으로 사용하는데, 자바 빈즈 규약을 따릅니다. 따라서, 기본 생성자가 필요하고 그에 따른 getter 및 setter가 필요합니다.


위의 코드를 보면 알 수 있듯이 FixtureMonkey를 활용하면 테스트 객체를 매우 간단하게 생성할 수 있습니다. 더 다양한 기능에 대한 자세한 정보는 공식 문서GitHub에서 확인할 수 있습니다. 여러분도 FixtureMonkey를 사용하여 더 간단하고 안전한 테스트 코드를 작성해보는 것은 어떨까요?