C.W.K.
Stream
Lesson 02 of 04 · published

영어처럼 읽히는 단언

~18 min · vitest-setup, assertions, matchers

Level 0테스트 호기심
0 XP0/32 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"테스트 이름이 스펙이야. 단언은 증명이고."

90% 의 시간 동안 쓰는 어휘

Vitest 는 Chai 스타일 expect() API 와 Jest 호환 확장을 같이 가져와. matcher 수십 개 있지만, 일상에서 손이 가는 건 한 다스 정도, 나머지는 필요할 때 문서에서 찾아 써. 아래는 먼저 내재화할 것들이야.

  • toBe(value) — strict equality (===). primitive 와 객체의 reference equality 용.
  • toEqual(value) — deep structural equality. 값이 중요한 객체/배열용.
  • toStrictEqual(value)toEqual 비슷하지만 여분의 undefined 프로퍼티와 prototype 불일치도 거부. 모양이 중요할 때 써.
  • toContain(item) — 배열과 문자열만. 인덱싱이나 includes() 체크보다 읽기 싸.
  • toMatch(regex) — 문자열이 regex 매칭. 길이와 존재를 따로 두 번 assert 하는 것보다 나아.
  • toThrow() / toThrow(message) — 던지는 호출을 arrow function 으로 감싸. arrow 없으면 throw 가 expect 보기 전에 발생해버려.
  • toBeCloseTo(value, precision) — float 용. 계산된 소수 비교할 땐 무조건 이거. 0.1 + 0.2 !== 0.3 는 진짜야.
  • resolves / rejects — Promise modifier. await expect(promise).resolves.toEqual(...) 가 try/catch 없이 깔끔하게 읽혀.

describe / it / expect — 골격

describe 는 관련 테스트를 묶어. it (또는 test) 는 하나 등록. expect 는 단언. 이 골격은 Vitest, Jest, 그리고 현대 runner 대부분에서 균일해. 차이는 matcher 가용성과 config 에 있지 모양에 있는 게 아냐.

describe 는 nest 할 수 있어. 흔한 패턴은 바깥 describe 가 대상 unit (함수, 컴포넌트) 이름, 안쪽 describe 가 동작이나 입력 클래스로 묶고, it 블록이 구체 동작을 이름 짓는 거.

테스트 이름은 문장, matcher 는 동사. 테스트를 이렇게 읽어: "it <테스트 이름>." "it tests that the function works" 가 안개처럼 읽히면 이름이 틀린 거야. "It rejects negative inputs by throwing RangeError" 는 스펙처럼 읽혀. 실제로 스펙이거든.

메시지 위생

모든 테스트는 결국 실패해 — 보통 CI 에서, 누군가 터미널 출력만으로 뭐가 깨졌는지 이해하려고 하지. 세 습관이 디버깅 시간을 되사줘:

  • 테스트는 문장으로 이름 지어. test('userService') 말고. it('works') 말고. it('returns null when the user is not found') 같이.
  • expect() 의 두 번째 인자 를 matcher 가 담을 수 없는 context 에 써: expect(result, 'after second fetch').toEqual(...). 실패 메시지에 노출돼.
  • 가능하면 테스트당 단언 하나. 같은 동작을 묘사하는 다중 단언은 괜찮은데, 다른 면을 테스트하는 거면 쪼개. 중간에 실패한 테스트는 나머지 실패를 숨겨.

부정 단언은 조심해

expect(x).not.toBe(y) 는 잘 읽히지만 긍정 매칭보다 약한 주장을 증명해. "x 는 5 가 아님" 은 x 가 실제로 뭔지 안 알려줘. 가능하면 긍정 단언을 선호해 — expect(x).toBeLessThan(5)expect(x).not.toBeGreaterThan(4) 보다 나아.

Code

한 테스트 파일에 일상 matcher 들·typescript
import { describe, it, expect } from 'vitest';
import { divide } from './math';

describe('divide', () => {
  it('returns the quotient for positive integers', () => {
    expect(divide(10, 2)).toBe(5);
  });

  it('returns a float for non-divisible integers', () => {
    expect(divide(7, 2)).toBeCloseTo(3.5, 5);
  });

  it('throws on division by zero', () => {
    // Arrow function so toThrow can call it and catch the throw.
    expect(() => divide(5, 0)).toThrow('Division by zero');
  });

  it('returns 0 when the numerator is 0', () => {
    expect(divide(0, 10), 'zero numerator should yield zero').toBe(0);
  });
});
Matcher 치트시트 — 90% 키트·typescript
// Common matcher quick-reference
expect(value).toBe(5);                    // ===
expect(value).toEqual({ a: 1 });          // deep equality
expect(value).toStrictEqual({ a: 1 });    // deep + shape
expect(arr).toContain('item');            // membership
expect(arr).toHaveLength(3);
expect(str).toMatch(/^Hello/);            // regex
expect(value).toBeCloseTo(0.3, 5);        // float-safe
expect(value).toBeTruthy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeGreaterThan(0);
expect(() => boom()).toThrow();           // exception
expect(() => boom()).toThrow('details');  // exception with message

await expect(promise).resolves.toEqual({ ok: true });
await expect(promise).rejects.toThrow(NetworkError);
비동기 단언 — 잘 읽히는 모양으로 골라·typescript
// Async assertions — three valid shapes

// 1. Modifier — cleanest for one assertion
it('fetches the user', async () => {
  await expect(fetchUser(1)).resolves.toMatchObject({ id: 1 });
});

// 2. Await then assert — verbose but flexible
it('fetches the user', async () => {
  const user = await fetchUser(1);
  expect(user.id).toBe(1);
  expect(user.email).toMatch(/@/);
});

// 3. Rejection — easier with the modifier
it('rejects unknown ids', async () => {
  await expect(fetchUser(-1)).rejects.toThrow('not found');
});

External links

Exercise

divide(a, b) 함수 짜 — 몫 반환, 0 으로 나누면 throw, 결과가 무한 소수 확장이면 floor. 그 다음 테스트 파일 짜 — describe 하나, it 네 개, toBe, toBeCloseTo, toThrow, 그리고 그 중 하나는 expect(value, 'reason').toBe(...) 형식으로. 테스트 이름 다 완전한 문장으로. npm test 로 돌려.
Hint
it('test 1', ...) 같은 거 참아. 테스트 이름은 구현보다 오래 살아. 미래의 유지보수자가 divide 가 뭘 해야 하는지 이해하려고 suite 를 읽는다는 느낌으로 써.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.