공부내용공유

java testFixture 는 어디에 둘까 (feat: package - private fixture) 본문

Server/Java

java testFixture 는 어디에 둘까 (feat: package - private fixture)

forfun 2024. 5. 25. 17:18

서론


 

테스트 코드를 작성하다 보면 특정 기능을 테스트 하기 위해 그를 위한 여러 객체들을 만들어야 한다, (entity, vo, req...) 그리고 이러한 객체들 중에는 값들을 굉장히 많이 가지고 있거나 반복적으로 사용되는 것들도 있을 것이다.  

 

 

이러한 테스트 비즈니스 로직과는 상관없는 부분의 노출을 최소화 하고자 TestFixture를 많이들 사용하고 있을거고 나도 잘 사용중이다, 다만 TestFixture들의 위치를 별 생각없이 두고 있었는데 위치에 관련해서 사수님이 의견을 남겨주셔서 해당 내용에 관해 정리할 예정이다.  

 

 

본론


 

목차는

  • TestFixture에 관한 간단한 설명
  • TestFixture 위치의 문제
  • TestFixture 위치 문제 해결
  • 문제 해결을 하면서 상기한 자바의 기초 내용

으로 구성될 예정이다.

 

 

TestFixture에 관한 간단한 설명

해당 내용은 이미 잘 정리된 글이 많으니 간단히만 설명하겠다. junit의 공식 문서에서는 Fixture에 대해 이렇게 얘기하고 있다.

This set of objects is called a test fixture.
When you are writing tests you will often find that you spend more time
writing the code to set up the fixture than you do in actually testing values.

To some extent, you can make writing the fixture code easier by paying carefulattention to the constructors you write.
However, a much bigger savings comes from sharing fixture code.Often, you will be able to use the same fixture for several different tests.
Each case will send slightly different messages or parameters to the fixture and will check for different results.

 

 

테스트를 위한 객체를 만들다보면 배보다 배꼽이 커질 수 있고 반복되어 사용되는 객체들도 많으니 Fixture를 사용하여 기능 테스트 그 본질에 더 집중하자는 말이다. 코드로 간단한 사용예시를 보자  

 

@Test
@DisPlayName("사용자가 주문을 하면 구매한 수가 올라간다.")
void add_purchase_count_success() {
    //given
    User user = new User.builder()
                    .name("최하식")
                    .age(27)
                    .address("일본 도쿄 맥도날드")
                    .level("vvvip")

    Shop shop = new Shop.builder()
                        .name("핑크 공주님만 오는 네일아트 샵")
                        .address("홍대입구역")
                        .owner(owner)
                        .rank(Rank.HIGH)

    Item item = new Item.builder()
                        .name("네일아트 도구")
                        .price(50000)
                        .purchaseCount(0)  
                        .shop(shop)

    .....
    //when
    user.buy(item);

    //then
    asserThat(item.purchaseCount).equal(1);
}

 

 

정작 검증해야하는 비즈니스 로직은 1줄인데 이를 위해 너무 많은 객체를 만들어야한다. TestFixture를 사용하면
어떻게 될까?

 

 

@Test
@DisPlayName("사용자가 주문을 하면 구매한 수가 올라간다.")
void add_purchase_count_success() {
    //given
    User user = UserFixture.getUser();
    Shop shop = ShopFixture.getShop();
    Item item = ItemFixture.getItem();

    .....
    //when
    user.buy(item);

    //then
    asserThat(item.purchaseCount).equal(1);
}  

 

 

훨씬 더 간결해졌고 해당 Fixture를 다른 테스트에서 재사용할 수 있다. 그래도 어쨋든 Fixture를 구성하는 코드는 있어야 한다, 어디에 두는게 좋을까??

  • 해당 테스트 클래스안에 private 메서드로?
  • fixture 패키지를 따로 만들까?
  • @BeforEach를 사용한 setUp 메서드 내부에 모두 포함하기?

 

 

당연히 정답은 없고 각자만의 장,단점이 있다. 다만 나는 fixture만 모아놓는 패키지를 따로 만들었는데 사수님께서
문제가 있다고 얘기해주셨다.

 

 

 

TestFixture 위치와 문제

내가 사용하던 테스트 패키지의 구조는 프로던션 코드 패키지와 일치하였고 그 외에 fixture들을 모아놓는
testFixture 패키지가 있었다.

  • test
    • java
      • com
        • company
          • domain
            • item
            • user
            • delivery
            • testFixture

이렇게 구성을하면 무슨 문제가 생길까?

 

 

value object를 사용하다 보면 상황에 맞춰서 public으로 열어야 할 때가 있고 package private으로 접근을 제한시켜야 하는 때가 있을 것이다.

 

 

만약 어떠한 테스트를 할 때 필요한 value object가 package private으로 선언되어져 있다면 testFixture 패키지에서 fixture를 만들 수 없게 된다.  그렇다면 testFixture를 어디서 관리해야할까? 방법은 2가지 정도가 있다고 생각한다.

 

 

 

TestFixture 위치와 문제 해결

1. 각 도메인별 패키지 안에다 두기

어쨋든 vo는 최대 package private의 접근 제어자를 가질 것 이므로 각 도메인 객체들 (vo, entity)들이
있는 패키지에 fixture 클래스를 만들 수 있다.

  • domain
    • item
      • item
      • price
      • address
      • rank
      • itemFixture
      • rankFixture
      • addressFixture

 

이런식으로 item이 entity이고 price, address, rank등이 제법 크고 복잡한 vo라고 하면 해당 패키지에 각각의 fixture를 만들거나 itemFixture에 다른 fixture들도 보관할 수 있겠다.

 

 

2. 새로운 root package 만들기

spring initializer를 사용해서 프로젝트를 만들면 기본적으로

  • src
    • main
    • test
    • build.gradle
    • readme

등으로 만들어진다. 여기에 testFixture만을 위한 새로운 root directory를 만드는 것이다.

  • src
    • main
    • test
    • testFixture
    • build.gradle
    • readme

 

새로운 testFixture 디렉토리에 프로덕션 코드와 같은 구성으로 만들고 각 class 위치별로 fixture를 만들 수 있다.

 

 

그런데 내부 패키지 구조가 같다지만 다른 root directory에 있는데 package private을 사용하는걸 왜 그런지 명확하게 설명할 수 없어서 이에 대해 조사를 하였다.

 

 

문제 해결을 하면서 상기한 자바의 기초 내용

위에서 기본으로 만들어진 src, test나 우리가 만든 testFixture와 같은 디렉토리는 말 그대로 디렉토리이다. 우리는 그 안에 컴파일러의 컴파일 대상인 Source root 디렉토리를 선언한다. (보통은 java라는 네이밍으로)

 

 

그리고 그 아래부터 path에 사용되는 package의 일부가 된다. java 진영에서는 패키지의 중복, 혼동을 막기 위해 해당 프로젝트의 도메인명을 거꾸로 사용해서 상위 패키지를 만들 것을 권장한다. 예를 들어서 item-delivery.company.com 이런식이면 상위 패키지는

  • com
    • company
      • item-delivery

이런식으로 구성하고 그 아래에는 어플리케이션 아키텍쳐에 따라 패키지 구성하는 식이다.

 

 

어쨋든 컴파일러는 각 클래스에 선언되어져있는 package 를 기반으로 패키지를 인식하고 jvm은 런타임 중에 해당 정보를 기반으로 접근 제어를 통제한다.

 

 

package는 source root 디렉토리 아래부터 작성되므로 다른 source root에 있더라도 package private class에 접근을 할 수 있게된다.

 

 

결론


음 뭔가 블로그 글을 쓰면서도 너무 당연한 내용으로 시작해서 당연한 결과를 썼나라는  생각도 들었으나 일단 내가 명확하게 설명을 못했기에 조금 부끄러워도 글을 써서 남겼다.

 

 

개인적인 욕심으로는  docs를 뒤져가면서 jvm 혹은 컴파일러가 어떻게 작동하는지를 알고 이러이러 해서 이렇다 라고 글을 쓰고 싶으나 그 시간에 현재 내가 하고 있는 일 관련된 공부나 하는게 맞는거 같다..

 

 

그래도 해당 내용을 다시 찾아보면서 잊고 있었던 패키지 구조 개념, 컨벤션을 상기할 수 있었다.