본문 바로가기

프로젝트 개발일지/toss-slash 라이브러리

[toss-slash 라이브러리] arrayIncludes 함수와 테스트 코드 (toss 라이브러리에 이슈 올리기)

이번에는 배열 관련 유틸 함수를 쭉 알아보려 한다.

사실 프로젝트에서 사용할 만한 유틸 함수들 위주로만 글을 적어보려고 했는데, 이 함수도 알아보면 좋을 만한 내용이 있어서 가져오게 되었다.

 

이번에는 arrayIncludes 함수이다.

아래 사진은 해당 함수 설명 문서이다.

 

해당 함수는 string과 같이 넓은 타입으로 정의된 요소가 문자열 리터럴 타입 같이 좁은 타입 요소로 이루어진 배열에 존재하는지 타입 단언 없이 판단하는 함수라고 생각하면 된다.

 

그래서 함수의 구현체를 살펴보도록 하자.

export const arrayIncludes = <T>(
  array: T[] | readonly T[],
  item: unknown,
  fromIndex?: number
): item is T => {
  return array.includes(item as T, fromIndex);
};

 

사실, Array.includes 메소드에 자잘한 타입만 추가해서 나타낸 유틸 함수이다.

이전 글들에서 알아봤던 것처럼 타입 가드를 이용했고, 이 바로 전 assert 함수에서 알아봤던 타입 관련 내용들과 거의 유사하다.

 

그럼에도 불구하고, 이 글을 들고 온 이유는 테스트 코드에 있다.

아래는 토스 라이브러리에 올라와 있는 테스트 코드이다.

describe('arrayIncludes', () => {
  it('should work identical to Array.prototype.includes', () => {
    const arr: Array<'a' | 'b' | 'c'> = ['a', 'b', 'c'];

    const includedElement = 'a';
    expect(arrayIncludes(arr, includedElement)).toBe(true);

    const excludedElement = 'd';
    expect(arrayIncludes(arr, excludedElement)).toBe(false);
  });

  it('should work well with fromIndex', () => {
    const arr: Array<'a' | 'b' | 'c'> = ['a', 'b', 'c'];

    const element = 'a';
    expect(arrayIncludes(arr, element, 0)).toBe(true);
    expect(arrayIncludes(arr, element, 1)).toBe(false);
  });
});

 

별다른 문제가 없는 것 같아 보이지만, 내 눈에는 아주 사소한 문제가 있었다.

바로 아래 부분이었다.

 

includedElement, excludedElement는 테스트 코드에서는 const 키워드를 사용해서 선언되었기 때문에 타입이 각각 'a', 'd', 즉 문자열 리터럴 타입으로 나타난다.

그런데, 이 함수의 취지로는 includedElement, excludedElement가 string으로 선언이 되고, arrayIncludes 함수를 통해 배열 내 존재하는 요소인지 판단해야 하는 것이 아닌가?라는 생각이 들게 되었다.

it('should work identical to Array.prototype.includes', () => {
    const arr: Array<'a' | 'b' | 'c'> = ['a', 'b', 'c'];

    const includedElement = 'a';
    expect(arrayIncludes(arr, includedElement)).toBe(true);

    const excludedElement = 'd';
    expect(arrayIncludes(arr, excludedElement)).toBe(false);
  });

 

그래서, 내가 생각한 해결 방법은 두 가지이다.

첫 번째, const 키워드 대신 let 키워드 사용하기.

이는, 타입 넓히기와 관련된 내용인데, let으로 선언된 변수는 const로 선언된 변수보다 넓은 타입으로 지정된다.

그래서 아래의 경우, let으로 선언하면 string으로 const로 선언하면 문자열 리터럴 타입이 되는 것이다.

it('should work identical to Array.prototype.includes', () => {
    const arr: Array<'a' | 'b' | 'c'> = ['a', 'b', 'c'];

    let includedElement = 'a';
    expect(arrayIncludes(arr, includedElement)).toBe(true);

    let excludedElement = 'd';
    expect(arrayIncludes(arr, excludedElement)).toBe(false);
  });

 

두 번째 방법은 더 간단하다.

타입 선언을 통해 string으로 지정해주기.

아래와 같이 string 타입을 지정해주면 두 변수는 string 타입이 된다.

it('should work identical to Array.prototype.includes', () => {
    const arr: Array<'a' | 'b' | 'c'> = ['a', 'b', 'c'];

    const includedElement: string = 'a';
    expect(arrayIncludes(arr, includedElement)).toBe(true);

    const excludedElement: string = 'd';
    expect(arrayIncludes(arr, excludedElement)).toBe(false);
  });

 

아주 사소한 문제이기는 하나, 이런 거도 토스 라이브러리에 issue로 올려도 되나 싶지만, 일단은 올려보기로 했다.

그래서 등록한 PR은 다음과 같다.

https://github.com/toss/slash/issues/416

 

어떤 답변이 올라올지 정말 기대가 된다.