Skip to main content
backend2025년 11월 2일12분 소요

2026년의 단위 테스트: 더 이상 변명의 여지가 없다

도구들이 발전했습니다. AI가 테스트 스캐폴드를 작성하고, 프레임워크는 빠르며, 더 이상 테스트되지 않은 코드를 배포할 좋은 이유가 없습니다.

testingvitestjest
2026년의 단위 테스트: 더 이상 변명의 여지가 없다

3년 전만 해도 테스트를 작성하지 않는 것에 대한 변명은 적어도 그럴듯했습니다. 테스트 프레임워크는 느렸고, 목(mock) 설정은 번거로웠습니다. 테스트를 작성하는 데 코드를 작성하는 것보다 더 많은 시간이 걸렸습니다. 제품-시장 적합성을 향해 질주하는 3인 스타트업에게는 ROI가 명확하지 않았습니다. 저는 그러한 변명에 동의하지는 않았지만, 이해는 했습니다.

2026년에는 그러한 변명들이 사라졌습니다. Vitest는 Jest가 부팅하는 데 걸리던 시간 안에 전체 테스트 스위트를 실행합니다. AI 도구는 함수 시그니처로부터 포괄적인 테스트 파일을 스캐폴드합니다. TypeScript는 컴파일 시점에 전체 버그 범주를 잡아내어 실제로 테스트해야 할 범위를 좁힙니다. Docker Compose는 통합 테스트를 위해 실제 데이터베이스를 몇 초 만에 가동합니다. "테스트 없음"과 "잘 테스트됨" 사이의 간격은 그 어느 때보다 좁아졌습니다.

현대적인 테스트 환경

테스트 생태계는 몇 가지 훌륭한 도구를 중심으로 통합되었으며, 과거에 테스트 설정을 연구 프로젝트처럼 만들었던 파편화는 대부분 사라졌습니다.

Vitest vs Jest: 결정은 내려졌다

모든 새로운 프로젝트에서 Vitest는 기본 선택입니다. Jest가 나쁘기 때문이 아니라(Jest는 견고하고 검증된 프레임워크입니다), Vitest가 현대 개발에 중요한 모든 면에서 측정 가능하게 더 좋기 때문입니다.

속도: Vitest는 변환을 위해 esbuild를 사용하고, 네이티브 ESM 지원을 통해 워커 스레드에서 테스트를 실행합니다. 제 프로젝트에서 동일한 테스트 스위트는 Jest에 비해 Vitest에서 3-5배 더 빠르게 실행됩니다. Jest에서 45초 걸리던 스위트가 Vitest에서는 12초 만에 완료됩니다.

설정: Vitest는 Vite 설정을 재사용합니다. 빌드를 위해 이미 Vite를 사용하고 있다면(2026년에는 대부분의 프로젝트가 그렇습니다), 추가 설정이 전혀 필요 없습니다. 이를 Jest의 moduleNameMapper, transform, transformIgnorePatterns 및 일부 ESM 패키지가 작동하지 않는 이유에 대한 필연적인 문제 해결과 비교해 보세요.

호환성: Vitest는 Jest 호환 API를 구현합니다. describe, it, expect, beforeEach, afterEach, vi.fn(), vi.mock() — 모두 예상대로 작동합니다. Jest에서 Vitest로 마이그레이션하는 것은 대부분 jest.fn()vi.fn()으로 찾아 바꾸는 작업입니다.

// vitest.config.ts — this is often all you need
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: ['node_modules/', 'dist/', '**/*.d.ts', '**/*.config.*'],
    },
  },
});

Jest를 고수해야 할 때: 대규모 Jest 테스트 스위트(500개 이상의 테스트)가 있고 당장 불편한 점이 없다면, 마이그레이션은 시급하지 않습니다. Jest는 계속 업데이트되고 잘 작동합니다. 하지만 새로운 프로젝트나 100개 미만의 테스트를 가진 프로젝트의 경우 Vitest가 명백한 선택입니다.

실제 테스트 피라미드

전통적인 테스트 피라미드(많은 단위 테스트, 적은 통합 테스트, 소수의 E2E 테스트)는 원칙적으로 여전히 옳지만, 경계가 바뀌었습니다.

단위 테스트는 개별 함수, 유틸리티 메서드 및 순수 비즈니스 로직을 검증합니다. 이들은 빨라야 하며(각각 10ms 미만), 외부 의존성이 없어야 하며, 구현보다는 동작을 테스트해야 합니다.

통합 테스트는 구성 요소들이 함께 작동하는지 검증합니다 — 데이터베이스 쿼리와 함께하는 API 라우트, 외부 API 호출과 함께하는 서비스 메서드, 상태 관리와 함께하는 React 컴포넌트. 이들은 더 느리지만(각각 100-500ms) 단위 테스트가 놓치는 버그를 잡아냅니다.

E2E 테스트는 실제 애플리케이션을 통해 완전한 사용자 워크플로우를 검증합니다. 이들은 가장 느리고(각각 5-30초) 가장 취약하지만, 다른 어떤 테스트도 잡지 못하는 버그를 잡아냅니다. Playwright와 같은 도구는 Selenium 시대보다 E2E 테스트를 훨씬 더 신뢰할 수 있게 만들었습니다.

제가 목표로 하는 비율은 단위 70%, 통합 20%, E2E 10%입니다. 정확한 숫자는 각 수준에서 대표성을 갖는 것보다 덜 중요합니다.

무엇을 테스트하고 무엇을 건너뛸 것인가

모든 코드가 동일한 테스트 엄격함을 필요로 하는 것은 아닙니다. 테스트 노력을 어디에 투자해야 하는지 아는 것은 테스트를 작성하는 방법을 아는 것만큼 중요합니다.

항상 테스트해야 할 것

비즈니스 로직 및 도메인 규칙. 코드가 비즈니스 규칙(가격 계산, 권한 확인, 데이터 유효성 검사, 상태 머신 전환)을 구현한다면 테스트가 필요합니다. 이러한 규칙이 잘못되면 비용이 발생하거나 보안 취약점을 초래할 수 있습니다.

// pricing.ts
export function calculateOrderTotal(items: OrderItem[], discount?: Discount): number {
  const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);

  if (!discount) return subtotal;

  if (discount.type === 'percentage') {
    return subtotal * (1 - discount.value / 100);
  }

  if (discount.type === 'fixed') {
    return Math.max(0, subtotal - discount.value);
  }

  return subtotal;
}
// pricing.test.ts
import { describe, it, expect } from 'vitest';
import { calculateOrderTotal } from './pricing';

describe('calculateOrderTotal', () => {
  const items: OrderItem[] = [
    { id: '1', name: 'Burger', price: 12.99, quantity: 2 },
    { id: '2', name: 'Fries', price: 4.99, quantity: 1 },
  ];

  it('calculates subtotal without discount', () => {
    expect(calculateOrderTotal(items)).toBe(30.97);
  });

  it('applies percentage discount', () => {
    const discount = { type: 'percentage' as const, value: 10 };
    expect(calculateOrderTotal(items, discount)).toBeCloseTo(27.873);
  });

  it('applies fixed discount', () => {
    const discount = { type: 'fixed' as const, value: 5 };
    expect(calculateOrderTotal(items, discount)).toBe(25.97);
  });

  it('does not go below zero with fixed discount', () => {
    const discount = { type: 'fixed' as const, value: 50 };
    expect(calculateOrderTotal(items, discount)).toBe(0);
  });

  it('handles empty items array', () => {
    expect(calculateOrderTotal([])).toBe(0);
  });
});

데이터 변환 함수. 데이터를 한 형태에서 다른 형태로 변환하는 모든 함수(API 응답 매퍼, 폼 데이터 직렬 변환기, CSV 파서)는 대표적인 입력과 엣지 케이스를 포함한 테스트가 필요합니다.

오류 처리 경로. API가 500을 반환하면 어떻게 될까요? 입력이 null일 때는요? 파일이 존재하지 않을 때는요? 개발자들이 행복 경로(happy path)를 수동으로 테스트하고 오류 경로가 작동한다고 가정하기 때문에 버그는 오류 경로에 숨어 있습니다.

유틸리티 함수. 문자열 포맷터, 날짜 헬퍼, 배열 유틸리티, 유효성 검사 함수. 이들은 모든 곳에서 사용되며, 유틸리티 함수에 버그가 있으면 코드베이스 전체로 확산됩니다.

건너뛰거나 가볍게 테스트할 것

직접적인 프레임워크 래퍼. 함수가 잘 테스트된 프레임워크 메서드의 얇은 래퍼라면, 이를 테스트하는 것은 코드가 아니라 프레임워크를 테스트하는 것입니다. <h1>{title}</h1>을 렌더링하는 React 컴포넌트는 h1이 렌더링되는지 확인하는 테스트가 필요 없습니다.

설정 파일. 정적 설정, 상수, 타입 정의 — 이들은 테스트가 필요 없습니다. TypeScript는 이미 컴파일 시점에 이들을 검증합니다.

서드파티 라이브러리 통합 글루 코드. 데이터 모델의 매개변수로 stripe.charges.create()를 호출하는 코드는 Stripe를 목(mock)하고 호출했는지 확인하는 단위 테스트가 필요 없습니다. 이는 종단 간(end-to-end) 결제 흐름을 검증하는 통합 테스트가 필요합니다.

목(Mocking) 전략

목(Mocking)은 테스트가 "동작 검증"에서 "구현 세부 사항 테스트"로 넘어가는 지점입니다. 목표는 테스트를 빠르고 결정론적으로 유지하면서 가능한 한 적게 목하는 것입니다.

의존성 주입(Dependency Injection) 접근 방식

모듈을 목하는 대신, 의존성을 매개변수로 전달하세요. 이렇게 하면 테스트가 자연스러워지고, 테스트를 모듈 구조에 결합시키는 vi.mock()의 마법을 피할 수 있습니다.

// user-service.ts
export function createUserService(db: Database, emailClient: EmailClient) {
  return {
    async createUser(data: CreateUserInput): Promise<User> {
      const user = await db.users.create({ data });
      await emailClient.send({
        to: user.email,
        template: 'welcome',
        data: { name: user.name },
      });
      return user;
    },
  };
}
// user-service.test.ts
import { describe, it, expect, vi } from 'vitest';
import { createUserService } from './user-service';

describe('createUserService', () => {
  it('creates user and sends welcome email', async () => {
    const mockUser = { id: '1', name: 'Jane', email: 'jane@example.com' };
    const db = {
      users: { create: vi.fn().mockResolvedValue(mockUser) },
    };
    const emailClient = {
      send: vi.fn().mockResolvedValue(undefined),
    };

    const service = createUserService(db as any, emailClient as any);
    const result = await service.createUser({ name: 'Jane', email: 'jane@example.com' });

    expect(result).toEqual(mockUser);
    expect(emailClient.send).toHaveBeenCalledWith({
      to: 'jane@example.com',
      template: 'welcome',
      data: { name: 'Jane' },
    });
  });
});

vi.mock() 호출도, 모듈 경로 문자열도, 순서에 의존하는 설정도 없습니다. 테스트는 무엇이 실제이고 무엇이 가짜인지 명시적으로 보여줍니다.

vi.mock()를 사용해야 할 때

모듈 수준 목(mocking)은 의존성 주입을 제어할 수 없을 때 적절합니다. 일반적으로 모듈을 직접 임포트하는 React 컴포넌트를 테스트하거나 환경별 전역 변수를 사용하는 코드를 테스트할 때 그렇습니다.

// When you genuinely need module mocking
vi.mock('./api-client', () => ({
  fetchUser: vi.fn().mockResolvedValue({ id: '1', name: 'Test User' }),
}));

API 목(Mocking)을 위한 MSW

HTTP 요청을 하는 테스트의 경우, Mock Service Worker (MSW)는 서비스 워커 수준에서 네트워크 요청을 가로챕니다. 이는 fetchaxios를 목하는 것보다 우수합니다. 왜냐하면 실제 HTTP 클라이언트 코드를 테스트하기 때문입니다.

import { setupServer } from 'msw/node';
import { http, HttpResponse } from 'msw';

const server = setupServer(
  http.get('https://api.example.com/users/:id', ({ params }) => {
    return HttpResponse.json({
      id: params.id,
      name: 'Test User',
      email: 'test@example.com',
    });
  }),

  http.post('https://api.example.com/orders', async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json(
      { id: 'order-1', ...body, status: 'created' },
      { status: 201 }
    );
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

AI 지원 테스트 생성

AI 도구는 테스트 작성의 경제학을 진정으로 변화시켰습니다. 초기 테스트 스캐폴드(상용구, 기본적인 행복 경로 케이스, 표준 엣지 케이스)를 생성하는 것은 LLM이 잘 처리하는 반복적이고 패턴 기반 작업의 정확한 유형입니다.

AI가 잘하는 것

  • 함수 시그니처 및 타입 정의로부터 테스트 파일 생성
  • 생각하지 못했을 엣지 케이스 제안 (빈 배열, null 값, 경계 숫자)
  • 반복적인 설정/해체 상용구 작성
  • 인터페이스 형태와 일치하는 목(mock) 객체 생성
  • 많은 입력 조합을 가진 함수를 위한 매개변수화된 테스트 케이스 생성

AI가 잘 못하는 것

  • 비즈니스 컨텍스트 이해 ("사용자는 자정 이후에 주문할 수 없으므로 실패해야 한다"는 도메인 지식)
  • 여러 함수 호출에 걸친 복잡한 상태 상호 작용 테스트
  • 실제 시스템 경계를 활용하는 의미 있는 통합 테스트 작성
  • 무엇을 테스트하고 무엇을 테스트하지 않을지 결정
  • 구현보다는 동작을 검증하는 테스트 작성

실제 워크플로우

AI 지원 테스트를 사용하는 저의 워크플로우:

  1. 함수 또는 모듈을 작성합니다.
  2. AI에게 기본 케이스를 포함한 테스트 파일을 생성해 달라고 요청합니다.
  3. 생성된 테스트를 검토하고 수정합니다 — AI가 생성한 테스트는 종종 동작보다는 구현 세부 사항을 테스트합니다.
  4. AI가 놓친 도메인별 케이스를 추가합니다.
  5. 테스트를 실행하고 코드를 일시적으로 망가뜨려 실제로 버그를 잡는지 확인합니다.

AI는 2단계(과거에는 지루했던 부분)를 수행하고, 저는 3-5단계(판단이 필요한 부분)에 집중합니다. 이는 표준 유틸리티 및 서비스 코드의 테스트 작성 시간을 약 50-60% 단축시킵니다.

React 컴포넌트 테스트

컴포넌트 테스트는 크게 발전했습니다. Enzyme의 얕은 렌더링에서 React Testing Library의 사용자 중심 테스트 철학으로의 전환은 제가 컴포넌트 테스트에 대해 생각하는 방식을 바꾸었습니다.

컴포넌트에서 테스트할 것

  • 컴포넌트가 다른 props로 올바르게 렌더링되는가?
  • 사용자 상호 작용(클릭, 입력, 선택)이 예상된 동작을 트리거하는가?
  • 컴포넌트가 로딩, 오류, 빈 상태를 처리하는가?
  • 조건부 렌더링이 올바르게 작동하는가?

컴포넌트에서 테스트하지 않을 것

  • 내부 상태 값 (React가 추적하는 것이 아니라 사용자가 보는 것을 테스트)
  • 구현 세부 사항 (어떤 함수가 호출되었는지, 몇 번의 재렌더링이 발생했는지)
  • 스타일링 (이를 위해서는 시각적 회귀 테스트 사용)
// OrderSummary.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { OrderSummary } from './OrderSummary';

describe('OrderSummary', () => {
  const defaultProps = {
    items: [
      { id: '1', name: 'Burger', price: 12.99, quantity: 2 },
      { id: '2', name: 'Fries', price: 4.99, quantity: 1 },
    ],
    onCheckout: vi.fn(),
  };

  it('displays order items with prices', () => {
    render(<OrderSummary {...defaultProps} />);

    expect(screen.getByText('Burger')).toBeInTheDocument();
    expect(screen.getByText('$25.98')).toBeInTheDocument(); // 12.99 * 2
    expect(screen.getByText('Fries')).toBeInTheDocument();
    expect(screen.getByText('$4.99')).toBeInTheDocument();
  });

  it('displays total', () => {
    render(<OrderSummary {...defaultProps} />);
    expect(screen.getByText('Total: $30.97')).toBeInTheDocument();
  });

  it('calls onCheckout when checkout button is clicked', async () => {
    const user = userEvent.setup();
    render(<OrderSummary {...defaultProps} />);

    await user.click(screen.getByRole('button', { name: /checkout/i }));
    expect(defaultProps.onCheckout).toHaveBeenCalledOnce();
  });

  it('shows empty state when no items', () => {
    render(<OrderSummary {...defaultProps} items={[]} />);
    expect(screen.getByText(/your cart is empty/i)).toBeInTheDocument();
    expect(screen.queryByRole('button', { name: /checkout/i })).not.toBeInTheDocument();
  });

  it('disables checkout button during loading', () => {
    render(<OrderSummary {...defaultProps} isLoading={true} />);
    expect(screen.getByRole('button', { name: /checkout/i })).toBeDisabled();
  });
});

주목할 점: 필요하지 않으면 getByTestId를 사용하지 않고, 내부 상태를 확인하지 않으며, CSS 클래스를 검증하지 않습니다. 테스트는 사용자가 보고 수행하는 것에 대한 설명처럼 읽힙니다.

API 라우트 테스트

API 라우트 테스트는 미들웨어, 유효성 검사 및 응답 형식 지정과 함께 HTTP 핸들러가 올바르게 작동하는지 확인하는 통합 테스트입니다.

// Using supertest with an Express app
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import { app } from '../app';
import { db } from '../database';

describe('POST /api/orders', () => {
  let authToken: string;

  beforeAll(async () => {
    await db.migrate.latest();
    await db.seed.run();
    // Get auth token for test user
    const loginResponse = await request(app)
      .post('/api/auth/login')
      .send({ email: 'test@example.com', password: 'testpassword' });
    authToken = loginResponse.body.token;
  });

  afterAll(async () => {
    await db.destroy();
  });

  it('creates an order with valid data', async () => {
    const response = await request(app)
      .post('/api/orders')
      .set('Authorization', `Bearer ${authToken}`)
      .send({
        items: [{ productId: 'prod-1', quantity: 2 }],
        deliveryAddress: '123 Main St',
      });

    expect(response.status).toBe(201);
    expect(response.body).toMatchObject({
      id: expect.any(String),
      status: 'pending',
      items: expect.arrayContaining([
        expect.objectContaining({ productId: 'prod-1', quantity: 2 }),
      ]),
    });
  });

  it('returns 401 without auth token', async () => {
    const response = await request(app)
      .post('/api/orders')
      .send({ items: [{ productId: 'prod-1', quantity: 2 }] });

    expect(response.status).toBe(401);
  });

  it('returns 400 with invalid data', async () => {
    const response = await request(app)
      .post('/api/orders')
      .set('Authorization', `Bearer ${authToken}`)
      .send({ items: [] });

    expect(response.status).toBe(400);
    expect(response.body.errors).toBeDefined();
  });

  it('returns 400 when product does not exist', async () => {
    const response = await request(app)
      .post('/api/orders')
      .set('Authorization', `Bearer ${authToken}`)
      .send({
        items: [{ productId: 'nonexistent', quantity: 1 }],
        deliveryAddress: '123 Main St',
      });

    expect(response.status).toBe(400);
    expect(response.body.message).toContain('not found');
  });
});

데이터베이스 작업 테스트

데이터베이스 작업 테스트에는 실제 데이터베이스가 필요합니다. SQL 쿼리를 목(mock)하는 것은 쿼리가 아니라 목을 테스트하는 것입니다. 각 테스트 실행마다 다시 생성되는 테스트 데이터베이스를 사용하세요.

// database.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient({
  datasources: { db: { url: process.env.TEST_DATABASE_URL } },
});

beforeEach(async () => {
  // Clean tables in dependency order
  await prisma.orderItem.deleteMany();
  await prisma.order.deleteMany();
  await prisma.user.deleteMany();
});

describe('Order repository', () => {
  it('creates order with items and calculates total', async () => {
    const user = await prisma.user.create({
      data: { email: 'test@example.com', name: 'Test User' },
    });

    const order = await prisma.order.create({
      data: {
        userId: user.id,
        items: {
          create: [
            { productName: 'Burger', price: 12.99, quantity: 2 },
            { productName: 'Fries', price: 4.99, quantity: 1 },
          ],
        },
        total: 30.97,
      },
      include: { items: true },
    });

    expect(order.items).toHaveLength(2);
    expect(order.total).toBe(30.97);
    expect(order.userId).toBe(user.id);
  });

  it('enforces unique email constraint', async () => {
    await prisma.user.create({
      data: { email: 'duplicate@example.com', name: 'User 1' },
    });

    await expect(
      prisma.user.create({
        data: { email: 'duplicate@example.com', name: 'User 2' },
      })
    ).rejects.toThrow();
  });
});

CI에서의 테스트 데이터베이스 설정

# In GitHub Actions
services:
  postgres:
    image: postgres:16
    env:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
      POSTGRES_DB: test_db
    ports:
      - 5432:5432

Prisma를 사용하는 경우, 테스트 전에 npx prisma migrate deploy를 실행하여 스키마를 적용하세요. 순수 SQL을 사용하는 경우, 마이그레이션 스크립트를 실행하세요. 테스트 데이터베이스는 모든 CI 실행마다 새로 시작됩니다.

중요한 커버리지 지표

코드 커버리지는 유용한 신호이지만, 끔찍한 목표입니다.

커버리지 함정

100% 커버리지를 최적화하는 것은 동작을 검증하기보다는 커버되지 않은 라인을 맞추기 위해 존재하는 테스트로 이어집니다. 저는 다음과 같은 테스트를 본 적이 있습니다:

// This test exists purely for coverage
it('should have a default export', () => {
  expect(module).toBeDefined();
});

이 테스트는 의미 있는 것을 아무것도 검증하지 않습니다. 커버리지 숫자를 늘리지만, 신뢰도는 전혀 높이지 않습니다.

의미 있는 커버리지 목표

일괄적인 커버리지 임계값 대신, 저는 다른 코드 범주에 대해 다른 목표를 사용합니다:

카테고리 목표 근거
비즈니스 로직 / 도메인 90%+ 여기서의 버그는 비용을 발생시킵니다
API 라우트 / 컨트롤러 80%+ 통합 경계
유틸리티 함수 95%+ 널리 사용되며 영향력이 큽니다
UI 컴포넌트 70%+ E2E로 잡히는 시각적 버그
설정 / 셋업 목표 없음 정적이며 거의 고장 나지 않습니다
// vitest.config.ts
export default defineConfig({
  test: {
    coverage: {
      thresholds: {
        'src/domain/**': { statements: 90, branches: 85 },
        'src/api/**': { statements: 80, branches: 75 },
        'src/utils/**': { statements: 95, branches: 90 },
      },
    },
  },
});

뮤테이션 테스트 대안

커버리지 숫자가 공허하게 느껴진다면, 뮤테이션 테스트는 더 정직한 평가를 제공합니다. Stryker와 같은 도구는 코드에 작은 변경(뮤테이션)을 도입하고 테스트가 이를 잡아내는지 확인합니다. 살아남은 뮤테이션(테스트가 여전히 통과하는 경우)은 테스트 스위트의 공백을 나타냅니다.

npx stryker run

뮤테이션 테스트는 느리지만(모든 뮤테이션에 대해 전체 테스트 스위트를 실행) 통찰력을 제공합니다. 저는 매 커밋마다 실행하기보다는 매달 실행합니다.

실제 테스트 주도 개발 (TDD)

저는 TDD를 독단적으로가 아니라 선택적으로 실천합니다. 어떤 코드에는 탁월하게 작동하고, 다른 코드에는 마찰을 더합니다.

TDD가 빛을 발하는 곳

명확한 사양을 가진 순수 함수. 코드를 작성하기 전에 입력과 예상 출력을 알고 있다면, 테스트를 먼저 작성하는 것이 자연스럽고 생산적입니다.

버그 수정. 버그를 수정하기 전에, 버그를 재현하는 테스트를 작성하세요. 그런 다음 테스트가 통과할 때까지 코드를 수정하세요. 이렇게 하면 버그가 확실히 수정됩니다.

리팩토링. 리팩토링하기 전에 기존 동작에 대한 테스트를 작성하세요. 테스트는 안전망 역할을 하여 리팩토링된 코드가 동일한 결과를 생성하는지 확인합니다.

TDD가 마찰을 더하는 곳

탐색적 코드. 무언가가 어떻게 작동해야 하는지 알아낼 때(API 실험, UI 프로토타이핑, 데이터 구조 탐색), 테스트를 먼저 작성하는 것은 속도를 늦춥니다. 코드를 작성하고, 인터페이스를 안정화한 다음 테스트를 추가하세요.

**

DU

Danil Ulmashev

Full Stack Developer

함께 일하는 데 관심이 있으신가요?