Fixture Monkey 적용기

JeongYong Park·2023년 7월 10일
3
post-thumbnail

프로젝트를 마친 후 테스트코드를 보던 중 지저분한 FixtureFactory 클래스를 정리하고 싶은 마음이 생겼습니다. 길어진 given 단계가 생겨 코드의 가독성이 떨어져 이를 위해 생성한 클래스였지만 클래스의 크기가 점점 커지고 읽기 어려워지는 문제가 있었습니다.

만약 클래스의 개수가 늘어나게 되면 fixture 생성 메서드는 계속 늘어날 것입니다.

테스트 데이터를 자동으로 생성해주는 도구

이런 테스트 데이터를 만드는 번거로운 작업을 줄이기 위해 사용할 수 있는 것이 Fixture Monkey 입니다.

Fixture Monkey

Fixture Monkey는 네이버페이 팀에서 만든 오픈소스 프로젝트입니다.

Fixture Monkey는 아래와 같은 두 가지 목표를 가지고 있습니다.

  1. 테스트를 작성하게 편하게 만들기
  2. 테스트를 읽기 편하게 만들기

기본적으로 Java/JUnit5를 지원합니다. 필요에 따라 서드파티 모듈을 추가하여 사용해야 하는 점을 유의합시다.

  • fixture-monkey-jackson
    • 테스트에서 jackson을 통한 직렬화/역직렬화를 지원
  • fixture-monkey-javax-validation
    • JSR380: Bean Validation 2.0 annotation을 활용해 객체의 값을 제어할 수 있도록 플러그인을 지원합니다.
  • fixture-monkey-kotlin : 코틀린 지원
  • fixture-monkey-mockito
    • 인터페이스와 추상 클래스를 Mockito 객체로 생성할 수 있도록 플러그인을 지원합니다.
  • fixture-monkey-jakarta-validation
    • Jakarta Bean Validation 3.0 annotation을 활용해 객체의 값을 제어할 수 있도록 플러그인을 지원합니다.

이것 말고도 아래와 같은 것들이 더 있습니다.

  • fixture-monkey-autoparams
  • fixture-monkey-engine

Fixture Monkey 시작

프로젝트에서는 Gradle 을 사용했기 때문에 아래와 같이 의존성을 추가합니다.

testImplementation 'com.navercorp.fixturemonkey:fixture-monkey-starter:0.5.0'

Java를 사용해서 개발을 하고 있기 때문에 아래와 같이 FixtureMonkey 인스턴스를 생성합니다.

FixtureMonkey sut = FixtureMonkey.builder()
			.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
			.build();

이를 사용하기 위해서는 아래 조건 중 하나만 만족하면 됩니다.

  • Java17 LTS 버전을 사용하고 있다면 record 타입입니다.
  • 생성자에 @ContructorProperties 가 있습니다.
  • lombok @Value을 사용하고 lombok.anyConstructor.addConstructorProperties=true 옵션을 추가합니다.

물론 간단하게 다음과 같이 생성할 수도 있습니다.

FixtureMonkey sut = FixtureMonkey.create();

이때 기본 생성 전략은 BeanArbitraryIntrospector 이기 때문에 아래와 같은 필요조건이 존재합니다.

  1. 클래스가 파라미터가 없는 빈 생성자를 가지고 있습니다.
  2. 클래스에 세터가 존재합니다.

2번 조건에서 테스트하려는 모든 클래스에 세터를 적용하는 것은 좋지 않다고 생각했습니다. 왜냐하면 테스트코드를 위한 도메인 변경은 적절치 않다고 생각하고 도메인의 모든 필드에 세터를 열어주는 것은 변경가능성이 많이 생겨버리기 때문입니다.

따라서 위의 ConstructorPropertiesArbitraryIntrospector.INSTANCE를 택하기로 했습니다.

Fixture Monkey 사용해보기

먼저 FixtureMonkey 인스턴스를 생성합니다.

private static final FixtureMonkey sut = FixtureMonkey.builder()
			.defaultNotNull(Boolean.TRUE)
			.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
			.build();

객체 하나 생성해보기

ItemDetailsResponse response = sut.giveMeOne(ItemDetailsResponse.class);

List 생성해보기

List<CategoryResponse> response = sut.giveMe(CategoryResponse.class, 3);

특별한 값을 갖는 객체 생성해보기

  • 임의의 필드 값 설정
OrderReceiptRequest orderReceiptRequest = sut.giveMeBuilder(OrderReceiptRequest.class)
				.set("orders", new OrdersRequest(1, 1))
				.sample();
  • 임의의 필드 모든 요소 값 설정
sut.giveMeBuilder(ItemDetails.class)
				.set("options[*]", Arbitraries.strings())
                .sample();
  • 임의의 필드 n번째 요소 값 설정
sut.giveMeBuilder(ItemDetails.class)
				.set("options[n]", Arbitraries.strings())
                .sample();

결론

FixtureMonkey를 적용해 따로 FixtureFactory 클래스를 두어 100라인에 달하던 fixture를 생성하는 코드를 모두 제거하게 되었습니다.
또한 클래스의 필드, 생성자 등이 변경되어도 메서드 하나하나를 수정할 필요가 없게 되어 테스트 로직에 집중할 수 있게 되었습니다.

위에서 소개해 드린 내용말고도 공식문서를 보면 유용한 내용이 많습니다.
그런데 약간 불친절한 것 같습니다..

profile
다음 단계를 고민하려고 노력하는 사람입니다

0개의 댓글