Typescript 일반 유형 테스트 파트 1

typescript로 도구를 만들려고 할 때, 특히 유형 안전성과 유연성을 목표로 할 때 많은 일반 유형으로 끝나는 것이 일반적입니다.

그 중 일부는 유형 조작 논리의 큰 덩어리로 끝납니다.

그렇다면 우리의 유형이 작동하는지 어떻게 확신할 수 있으며 어떻게 우리의 유형을 테스트할 수 있습니까?

간단하면서도 어려운 것으로 밝혀졌지만 먼저 간단한 부분에 초점을 맞출 것입니다.

테스트 대상으로 삼자

type GetCountOfSubString<
    String_ extends string,
    SubString extends string,
    Count extends unknown[] = []
> = String_ extends `${string}${SubString}${infer Tail}`
    ? GetCountOfSubString<Tail, SubString, [1, ...Count]>
    : Count['length']


type NumberOfA = GetCountOfSubString<"a--a--aa--a","a"> // 5


우리는 GetCountOfSubString<"a--a--aa--a","a">가 항상 5가 되도록 하고 싶습니다.

기본적으로 둘 다 서로를 확장해야합니다

다음으로 체커를 만듭니다. 체커는 두 부분으로 구성됩니다.

첫 번째는 Expect입니다. 두 유형이 서로 확장되는지 확인하고 싶습니다.

type Expect<T, U>= T extends U ? U extends T ? true : false : false 

type r1 = Expect<GetCountOfSubString<"a--a--aa--a","a">,5> // true, success check
type r2 = Expect<GetCountOfSubString<"a--a--aa--a","a">,1> // false, fail check


playground



지금까지는 원하는 결과를 얻었습니다. 결과가 올바르면 유형이 true이고 결과가 올바르지 않으면 false입니다.

하지만 tsc로 유형 검사를 실행할 때 뭔가 빠져 있습니다.
아무 일도 일어나지 않습니다. 이것은 단순히 유형을 true와 false로 반환하고 그것에 대해 유효하지 않은 것이 없기 때문에 typescript는 불평하지 않습니다.

그래서 두 번째 부분인 어설션이 필요합니다.


type Assert<T extends true> = T // be anything after '=', doesn't matter



그들을 적용

type Expect<T, U>= T extends U ? U extends T ? true : false : false 

type Assert<T extends true> = T // be anything after '=', doesn't matter

type r1 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,5>> // true, pass test
type r2 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,1>> // false, fail test




이제 실패 테스트가 실패했음을 알 수 있으며 tsc를 실행하면 콘솔에서 오류를 볼 수 있습니다.

하지만 아직 옳지 않은 것이 있습니다. 무엇입니까?

글쎄요, 실패 테스트는 실패해야 하며, 예상되는 대로 오류를 유발해서는 안 됩니다

그래서 우리는 원점으로 돌아왔습니까?

아니요, 더 가깝습니다. @ts-expect-error 주석을 사용하여 해결하는 방법은 다음과 같습니다.

type r1 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,5>> // true, pass test
// @ts-expect-error
type r2 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,1>> // false, fail test


playground



더 이상 유형 검사 오류가 없습니다.
@ts-expect-error 라인에 오류가 있는 경우에만 오류를 억제합니다. 그렇지 않고 완벽하게 괜찮은 라인에서 사용하면 TS가 대신 오류를 표시하며 이것이 우리가 원하는 동작입니다.

GetCountOfSubString에 버그가 있는지 살펴보겠습니다. 예상대로 작동할까요?

통과 테스트에 실패하도록 합시다.

type GetCountOfSubString<
    String_ extends string,
    SubString extends string,
    Count extends unknown[] = []
> = "BUG!!"

type Expect<T, U>= T extends U ? U extends T ? true : false : false 

type Assert<T extends true> = T // be anything after '=', doesn't matter

type r1 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,5>> // true, pass test
// @ts-expect-error
type r2 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,1>> // false, fail test




playground

실패 테스트에 실패하도록 합시다.

type GetCountOfSubString<
    String extends string,
    SubString extends string,
    Count extends unknown[] = []
> = 1

type Expect<T, U>= T extends U ? U extends T ? true : false : false 

type Assert<T extends true> = T // be anything after '=', doesn't matter

type r1 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,5>> // true, pass test

// @ts-expect-error
type r2 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,1>> // false, fail test




playground

예, 작동합니다!

그러나 아직 끝나지 않았습니다. eslint와 같은 linter를 사용하는 경우 유형이 선언되었지만 사용되지 않는다고 불평합니다.



그것을 해결하는 두 가지 방법이 있습니다:

먼저 우리는 그들을 내보낼 수 있습니다


또는 대신 Assert를 함수로 바꿉니다.

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const assert = <T extends true>() => {
    //
}

assert<Expect<GetCountOfSubString<'a--a--aa--a', 'a'>, 5>>() // true, pass test
// @ts-expect-error
assert<Expect<GetCountOfSubString<'a--a--aa--a', 'a'>, 1>>() // false, fail test


playground

두 번째 방법이 권장되며 모든 어설션에 대해 새 유형을 생성할 필요가 없기 때문에 더 짧습니다.

파트 1은 여기까지입니다. 어려운 부분인 몇 가지 엣지 케이스를 처리하겠습니다.

좋은 웹페이지 즐겨찾기