hmk run dev

테스트를 짜는 데 데이터가 너무 크다? 본문

TypeScript

테스트를 짜는 데 데이터가 너무 크다?

hmk run dev 2023. 12. 2. 15:08

 

얼마 전 회사에서 테스트 코드를 짜다가 아래와 같은 피드백을 받았습니다.

TC를  통해서 함수가 어떤 기능을 수행하는지 사양을 파악하기 위함도 있습니다.

 

사실 어찌 보면 너무 당연한 말인데, 간과하고 넘어갔던 사실이라는 생각이 들었고,
지적해 주신 동료분께 감사한 마음이 들었네요 :)

 

 

결국 대부분의 개발자는 커리어 내내 협업을 할 것이고,

내 가짠 코드는 다른 사람이 보기에도 의도가 명확하고, 신뢰감을 줄 수 있는 코드를 작성해야 한다는 사실을

잠깐 망각했습니다.

그래서 아래와 같은 내용으로 테스트해야 할 데이터가 큰 경우(가독성이 떨어질 수 있는 테스트)

어떤 식으로 개선했는지에 대한 내용을 다뤄보겠습니다~! 

 

 



종종 테스트를 작성하다 보면 테스트의 데이터가 너무 커서  
보는 이로 하여금 어떤 기능을 함수, 테스트인지 가독성이 너무 떨어지는 경우가 있습니다.

 

테스트를 통과하는 것은 단연한 것이고,

코드의 기능과  목적을 명확하게 가독성 좋게 전달해야 하는데요


테스트의 목적과 기능을 작성자가 아닌 제삼자가 보았을 때도
쉽게 파악할 수 있는 게 중요하다고 생각합니다~!

큰 데이터를 테스트하는 경우에 어떤 식으로 가독성이 좋은 코드를 구성할 수 있는지 살펴봅시다.
 


데이터가 큰 테스트  

아래와 같은 큰 데이터 타입의 배열을 매개변수로 받아 새로운 배열을 리턴하는 함수의 테스트를 작성한다고 가정해 봅시다.

데이터를 보면 데이터의 속성도 너무 많을뿐더러,
속성 하나하나가 원시 타입이 아닌 것도 많아 데이터의 깊이를 알 수 없는 속성도 많은데요,

해당 데이터 배열을 mock 데이터로 static 하게 선언 후 테스트를 작성한다면 아마 테스트 작성자 본인도 
다시 봤을 때 파악하기 어려운 테스트가 될 것입니다...!

export interface OptionVariant {
  /** 옵션 조합 (ID 목록) */
  optionCombination: OptionCombination[];
  /** 판매마켓 */
  sellingMarket: OptionVariantSellingMarketEnum;
  /** 대표옵션여부 */
  isRepresentative: boolean;
  /** 판매마켓 등록여부 */
  isActive: boolean;
  /** 최종원가 */
  originalPrice: number;
  /** 최종원가 구성요소 */
  originalPriceAttribute: OriginalPriceAttribute;
  /** 판매가 */
  finalPrice: number;
  /** 마진율 */
  profitMargin: ProfitMargin;
  /** 뱃지 */
  badge: OptionVariantBadgeEnum;
  /**
   * 재고수량
   * @example 20
   */
  stock: number;
  /** 크기 */
  size?: Dimensions;
  /** 무게 */
  weight?: Weight;
}




테스트 작성해 보기

위의 데이터 타입의 배열 형태인 optionVariants (OptionVariant []) 형태의 매개 변수를 받아,
optionVariants들 중  sellingMarket과 combination이 완전히 같은 
variants 중복제거해 새로운 배열을 return 하는 함수를 만들었습니다.


이름은 `removeCombinationDuplicateOptionVariants`으로 optionVariants를 매개변수로 받아 optionVariants를 리턴하는 함수입니다.


이 테스트의 목적은 아래와 같습니다.

optionVariants 중 optionCombination과 sellingMarket이 완전히 같은 배열요소는 중복을 제거한다.

위의 목적을 토대로 보아, 이 테스트의 핵심 키워드는 
OptionVariant의 데이터 속성 중 optionCombination과 sellingMarket이 될 것입니다.


그렇다면 테스트에서도 두 개의 데이터를 중점적으로 보고 함수의 기능을 예상해 볼 수 있다면 훨씬
테스트의 목적을 파악하기 쉽겠죠??



별도의 헬퍼함수를 이용해 테스트의 목적을 코드에 강조해 보자  


아래 코드에서 getTestOptionVariants를 살펴보면 테스트에서 중점적으로 봐야 하는 sellingMarket과 optionCombination 속성을 받아
OptionVaraiant 형태로 반환하는 기능을 하고 있습니다.

좀 더 살펴보면 sellingMarket과 optionCombination를 제외한 나머지 속성들은 dummy 데이터를 이용해 OptionVariant 형태로 리턴해주고 있습니다.

describe('removeCombinationDuplicateOptionVariants', () => {
    it('should remove duplicate option variants based on selling market and option combination', () => {
      // 테스트에 필요한 부분만을 추출하는 타입
      type TestOptionVariant = Pick<
        OptionVariant,
        'sellingMarket' | 'optionCombination'
      >;

      // 테스트 데이터 생성을 도와주는 헬퍼 함수
      function getTestOptionVariants(
        testVariants: TestOptionVariant[],
      ): OptionVariant[] {
        return testVariants.map(({ sellingMarket, optionCombination }) => ({
          sellingMarket,
          optionCombination,
          ...excluedeTestAttributesOptionVariants,
        }));
      }

      const inputVariants: OptionVariant[] = getTestOptionVariants([
        {
          sellingMarket: OptionVariantSellingMarketEnum.SmartStore,
          optionCombination: [
            { optionId: '1', valueId: 'A' },
            { optionId: '2', valueId: 'B' },
          ],
        },
        {
          sellingMarket: OptionVariantSellingMarketEnum.Coupang,
          optionCombination: [
            { optionId: '2', valueId: 'B' },
            { optionId: '3', valueId: 'C' },
          ],
        },
        {
          sellingMarket: OptionVariantSellingMarketEnum.SmartStore,
          optionCombination: [
            { optionId: '1', valueId: 'A' },
            { optionId: '2', valueId: 'B' },
          ],
        },
        {
          sellingMarket: OptionVariantSellingMarketEnum.St11,
          optionCombination: [
            { optionId: '3', valueId: 'C' },
            { optionId: '4', valueId: 'D' },
          ],
        },
      ]);

      const expectedOutput: OptionVariant[] = getTestOptionVariants([
        {
          sellingMarket: OptionVariantSellingMarketEnum.SmartStore,
          optionCombination: [
            { optionId: '1', valueId: 'A' },
            { optionId: '2', valueId: 'B' },
          ],
        },
        {
          sellingMarket: OptionVariantSellingMarketEnum.Coupang,
          optionCombination: [
            { optionId: '2', valueId: 'B' },
            { optionId: '3', valueId: 'C' },
          ],
        },
        {
          sellingMarket: OptionVariantSellingMarketEnum.St11,
          optionCombination: [
            { optionId: '3', valueId: 'C' },
            { optionId: '4', valueId: 'D' },
          ],
        },
      ]);

      const result = removeCombinationDuplicateOptionVariants(inputVariants);

      expect(result).toEqual(expectedOutput);
    });
  });



참고로 excluedeTestAttributesOptionVariants 변수는 sellingMarket과 optionCombination을 제외한 OptionVariant 타입의 데이터로 아래와 같은 dummy 데이터입니다.

/** removeCombinationDuplicateOptionVariants를 테스트하기 위한 optionCombination와 sellingMarket이 제거된 optionVariant */
export const excluedeTestAttributesOptionVariants: Omit<
  OptionVariant,
  'optionCombination' | 'sellingMarket'
> = {
  isRepresentative: false,
  isActive: true,
  originalPrice: 29480,
  originalPriceAttribute: {
    localPrice: 89,
    localShippingFee: 0,
    exchangeRate: {
      currencyCode: 'CNY',
      exchangeRate: 182.54,
      symbol: '¥',
      updateAt: '20231130',
    },
    cardFeePercent: 22,
    shippingAgentFee: {
      currencyCode: 'KRW',
      amount: 6400,
    },
    additionalShippingFee: {
      currencyCode: 'KRW',
      amount: 0,
    },
    customsDutyPercent: 0,
    marketFeePercent: 10.5,
  },
  finalPrice: 26800,
  profitMargin: {
    unit: ProfitMarginUnitEnum.PERCENT,
    percent: 5,
    amount: {
      currencyCode: 'KRW',
      amount: 1340,
    },
  },
  badge: OptionVariantBadgeEnum.ADDITIONAL_SHIPPING_FEE,
  stock: 200,
};




이렇게 테스트를 구성하면서 작성자 이외의 다른 사람이 해당함수의 기능을 파악하기 위해
테스트를 살펴볼 때 테스트의 목적과 함수의 기능을 훨씬 파악하기 쉬운 코드가 됐습니다만~....

더 좋은 방법이 있다면 한 수 가르쳐주시면 감사 하겠습니다~!







Comments