LogoSEO Jing
  • All Posts
  • SEO Jing
  • okayJing
  • KD Team
  • CLAB Coreteam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

이펙티브 타입스크립트 2판 Day 10: 타입도 테스트하고 빠지는 분기는 막는다

2026년 7월 5일·5분 읽기
범위: Effective TypeScript 2판 Item 55–59
오늘의 질문: “런타임 테스트가 통과했으면 타입 설계도 안전하다고 봐도 될까?”

먼저 보는 코드

ts
type Result =
  | { kind: "success"; value: string }
  | { kind: "error"; message: string };

function render(result: Result) {
  if (result.kind === "success") {
    return result.value;
  }

  return "다시 시도해주세요";
}

지금은 괜찮아 보입니다. 그런데 나중에 loading 상태가 추가되면 어떨까요?

ts
type Result =
  | { kind: "success"; value: string }
  | { kind: "error"; message: string }
  | { kind: "loading" };

런타임 테스트가 success/error만 보고 있었다면 loading 누락을 놓칠 수 있습니다. 타입스크립트는 이런 변경을 알려줄 수 있지만, 코드가 그 신호를 받도록 작성되어 있어야 합니다.

타입 테스트와 누락 분기 차단 흐름

타입도 테스트 대상이다

런타임 테스트는 값의 동작을 확인합니다. 하지만 타입 설계의 의도는 값으로만 드러나지 않을 때가 있습니다.

예를 들어 public API 타입을 바꿀 때 이런 것을 확인하고 싶습니다.

특정 prop 조합은 허용되어야 한다. 서로 배타적인 prop 조합은 막혀야 한다. 반환 타입이 넓어지지 않아야 한다. 잘못된 호출은 계속 컴파일 에러여야 한다.

이때는 타입 테스트가 필요합니다.

ts
type Equal<A, B> =
  (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2
    ? true
    : false;

type Expect<T extends true> = T;

type _UserIdIsString = Expect<Equal<User["id"], string>>;

실무에서는 tsd, expect-type, vitest의 타입 테스트 패턴처럼 도구를 쓸 수 있습니다. 중요한 점은 도구 이름이 아니라 타입 관계가 깨졌을 때 PR에서 실패 신호가 나야 한다는 것입니다.

@ts-expect-error는 의도한 실패를 고정한다

타입 테스트는 “이 타입이 맞다”뿐 아니라 “이 호출은 실패해야 한다”도 남깁니다.

ts
createButton({ label: "저장", href: "/save" });

// @ts-expect-error href와 onClick은 동시에 받을 수 없다.
createButton({ label: "저장", href: "/save", onClick: () => {} });

@ts-ignore와 달리 @ts-expect-error는 에러가 사라지면 오히려 실패합니다. 그래서 타입 설계가 느슨해졌을 때 알아차리기 쉽습니다.

리뷰에서는 이런 질문을 합니다.

이 타입 변경이 허용해야 할 예시와 막아야 할 예시를 둘 다 남겼나?

타입 표시가 어려우면 API도 어려울 수 있다

복잡한 조건부 타입이나 재귀 타입을 쓰면 IDE가 보여주는 타입이 길어질 수 있습니다. 타입 표시가 길다고 무조건 나쁜 것은 아니지만, 팀원이 에러 메시지를 읽고 수정 방향을 알 수 없다면 비용이 큽니다.

ts
type DeepReadonly<T> = {
  readonly [K in keyof T]: DeepReadonly<T[K]>;
};

이런 유틸리티는 쓸모가 있지만, 깊은 객체·순환 구조·라이브러리 타입과 만나면 에러가 어려워질 수 있습니다. Item 55–59 구간의 감각은 “타입 시스템으로 어디까지 표현할까?”를 묻습니다.

타입이 구현보다 어렵다면 한 번 멈춰야 합니다.

public API에서 정말 필요한 제약인가? 더 단순한 union이나 명시적 overload로 충분하지 않은가? 타입 테스트로 의도를 설명할 수 있는가? 에러 메시지가 소비자에게 읽히는가?

never로 빠지는 분기를 막는다

discriminated union을 쓸 때는 마지막 분기가 중요합니다.

ts
function assertNever(value: never): never {
  throw new Error(`Unexpected value: ${JSON.stringify(value)}`);
}

function render(result: Result) {
  switch (result.kind) {
    case "success":
      return result.value;
    case "error":
      return result.message;
    default:
      return assertNever(result);
  }
}

Result에 loading이 추가되면 default의 result는 더 이상 never가 아닙니다. 컴파일러가 누락된 분기를 알려줍니다.

프론트엔드에서는 상태 모델이 계속 바뀝니다. idle, loading, success, empty, error, refreshing 같은 상태가 늘어납니다. never exhaustiveness는 “새 상태를 추가했는데 화면 분기를 안 고친” 실수를 줄여 줍니다.

코드 생성은 타입의 출처를 분명하게 한다

API 스키마, GraphQL schema, OpenAPI, Prisma schema처럼 이미 권위 있는 출처가 있다면 타입을 손으로 맞추기보다 생성하는 편이 낫습니다.

text
OpenAPI schema → generated client types → frontend API wrapper

좋은 코드 생성 흐름은 타입을 자동으로 만든다는 점보다, 타입의 출처가 하나로 정리된다는 점이 큽니다. 서버 계약이 바뀌면 생성 결과가 바뀌고, 프론트엔드가 그 변경을 컴파일 단계에서 봅니다.

다만 코드 생성도 만능은 아닙니다.

생성물이 너무 커서 리뷰가 어려우면 생성물과 작성 코드를 분리해야 한다. schema가 느슨하면 생성 타입도 느슨하다. 런타임 검증이 필요한 외부 입력은 여전히 검증해야 한다. 생성 스크립트가 CI에서 재현되지 않으면 로컬에서만 맞는 타입이 된다.

프론트엔드 코드 리뷰 체크리스트

변화확인할 질문
public type 변경허용/거부 예시가 타입 테스트로 남았나?
@ts-expect-error 추가실제로 의도한 실패를 고정하는가?
조건부/재귀 타입 추가에러 메시지와 표시 타입이 소비자에게 읽히는가?
union 상태 추가never exhaustiveness로 누락 분기를 잡는가?
API 타입 코드 생성schema→생성→사용 흐름이 CI에서 재현되는가?
타입 설계가 런타임을 대신함외부 입력 검증을 생략한 것은 아닌가?

이번 범위의 핵심은 타입스크립트를 더 화려하게 쓰자는 말이 아닙니다. 타입 설계도 회귀할 수 있고, public API 타입도 깨질 수 있습니다. 그래서 타입은 문서이면서 테스트 대상이고, union은 상태 모델이면서 누락 분기를 막는 계약입니다.

Day 10 타입 리뷰 퀴즈

Quiz1 / 4
Q.@ts-expect-error를 타입 테스트에 쓰는 가장 큰 이유는 무엇일까요?

Post Q&A

오케이징에게 물어보기

이펙티브 타입스크립트 2판 Day 10: 타입도 테스트하고 빠지는 분기는 막는다 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!

0/500

포스트 목록

/study/effective-typescript
파일 10개, 폴더 0개
이펙티브 타입스크립트 2판 Day 1: 타입스크립트를 믿기 전에 알아야 할 경계이펙티브 타입스크립트 2판 Day 2: 타입을 값의 집합으로 보기이펙티브 타입스크립트 2판 Day 3: 타입 반복을 줄이는 리뷰 감각이펙티브 타입스크립트 2판 Day 4: 추론을 살리는 값 생성 패턴이펙티브 타입스크립트 2판 Day 5: 타입 추론을 방해하지 않는 API 설계이펙티브 타입스크립트 2판 Day 6: 유효한 상태만 표현하는 타입 설계이펙티브 타입스크립트 2판 Day 7: 문자열보다 도메인 의미를 좁히기이펙티브 타입스크립트 2판 Day 8: any를 밖에 세우고 unknown으로 검증하기이펙티브 타입스크립트 2판 Day 9: 제네릭은 관계를 보존할 때만 강하다이펙티브 타입스크립트 2판 Day 10: 타입도 테스트하고 빠지는 분기는 막는다

같은 섹션의 대표 이미지

10 posts · latest first
타입 계약, 타입 테스트, never exhaustiveness, 코드 생성 경계를 보여주는 다이어그램
Study26. 07. 06.

이펙티브 타입스크립트 2판 Day 10: 타입도 테스트하고.

Item 55–59 범위를 바탕으로 type test, 타입 표시, 꼬리 재귀 한계, 코드 생성, never exhaustiveness를 코드 리뷰에서 다루는 방법을 정리합니다.

26. 07. 06.SEOJing
외부 경계, 제네릭, 조건부 타입, 템플릿 리터럴 타입의 역할과 과한 타입 계산의 위험을 보여주는 다이어그램
Study26. 07. 05.

이펙티브 타입스크립트 2판 Day 9: 제네릭은 관계를 보존할.

Item 49–54 범위를 바탕으로 type coverage, 제네릭, 조건부 타입, 템플릿 리터럴 타입을 코드 리뷰에서 어떻게 판단할지 정리합니다.

26. 07. 05.SEOJing
외부 JSON을 unknown으로 받고 검증 뒤 도메인 타입으로 넘기는 경계와 any가 내부로 번지는 위험을 보여주는 다이어그램
Study26. 07. 04.

이펙티브 타입스크립트 2판 Day 8: any를 밖에 세우고.

Item 42–48 범위를 바탕으로 any, unknown, 타입 단언, monkey patching, soundness 함정을 외부 입력 경계와 코드 리뷰 관점에서 정리합니다.

26. 07. 04.SEOJing
넓은 문자열과 선택적 필드를 도메인 이름과 유효 상태 유니온으로 좁히는 다이어그램
Study26. 06. 29.

이펙티브 타입스크립트 2판 Day 7: 문자열보다 도메인.

Item 35–41 범위를 바탕으로 string 남용, optional 필드, 특수 값, 도메인 이름 설계를 코드 리뷰 관점에서 정리합니다.

26. 06. 29.SEOJing
API 응답을 유효한 상태 유니온으로 변환하는 흐름 다이어그램
Study26. 06. 28.

이펙티브 타입스크립트 2판 Day 6: 유효한 상태만 표현하는.

Item 28–34 범위를 바탕으로 추론 위치, API 경계, null 처리, union 설계를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 06. 28.SEOJing
타입스크립트 추론이 값에서 API 경계로 흐르는 과정을 보여주는 다이어그램
Study26. 06. 27.

이펙티브 타입스크립트 2판 Day 5: 타입 추론을 방해하지 않는.

Effective TypeScript 2판 Item 19–22를 바탕으로 타입 추론, 타입 넓히기, 함수형 기법, 유니온 설계를 코드 리뷰 관점에서 정리합니다.

26. 06. 27.SEOJing
TypeScript에서 넓은 타입으로 먼저 담으면 key와 literal 정보가 사라지고 리뷰 체크가 약해지는 흐름 다이어그램
Study26. 06. 26.

이펙티브 타입스크립트 2판 Day 4: 추론을 살리는 값 생성.

Effective TypeScript 2판의 Item 16–21을 바탕으로 index signature, 타입 추론 기본, 변수/객체 생성 패턴을 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 06. 26.SEOJing
반복된 타입 선언을 원본 타입에서 파생한 타입으로 바꿔 리뷰 체크를 강하게 만드는 흐름 다이어그램
Study26. 06. 25.

이펙티브 타입스크립트 2판 Day 3: 타입 반복을 줄이는 리뷰.

Effective TypeScript 2판의 Item 11–15를 바탕으로 excess property check, 함수식 타입, type vs interface, readonly, 타입 반복 제거를 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 06. 25.SEOJing
TypeScript 타입을 값의 집합, type space, value space, assertion 경계로 정리한 다이어그램
Study26. 06. 24.

이펙티브 타입스크립트 2판 Day 2: 타입을 값의 집합으로.

Effective TypeScript 2판의 Item 6–10을 바탕으로 타입 시스템을 탐색하는 법, 타입을 값의 집합으로 보는 관점, type/value space, 타입 단언의 경계를 코드 리뷰 관점에서 정리합니다.

26. 06. 24.SEOJing
TypeScript를 JavaScript 위의 정적 타입 계층, 타입 제거, 구조적 타이핑, any의 구멍으로 정리한 다이어그램
Study26. 06. 23.

이펙티브 타입스크립트 2판 Day 1: 타입스크립트를 믿기.

Effective TypeScript 2판의 Item 1–5를 바탕으로 TypeScript와 JavaScript의 관계, tsconfig, 타입 제거, 구조적 타이핑, any의 위험을 프론트엔드 코드 리뷰 관점에서 정리합니다.

26. 06. 23.SEOJing