2026 소프트웨어 테스트 완전 가이드

유닛·통합·E2E·TDD·커버리지

by AI개발자
소프트웨어엔지니어링_0.png
이 장을 읽기 전에: 프론트엔드 또는 백엔드의 기본적인 개발 경험이 있으면 구체적인 예시와 연결하기 쉽다.


테스트의 목적은 코드의 올바름을 증명하는 것이 아니다. 본래의 목적은 안심하고 변경할 수 있는 상태를 만드는 것이다.

이 장에서는 테스트의 종류, 도구 선정, 커버리지, TDD, AI 생성 코드 시대의 검증 방침을 정리한다.

� 한국 시장 맥락: 국내 주요 IT 기업(카카오·네이버·토스·라인)은 Vitest + Playwright 조합(TypeScript 기반)을 신규 프로젝트 표준으로 채택하는 추세다. Java/Kotlin 백엔드는 JUnit5 + Mockito + Testcontainers 조합이 사실상 표준이다. 공공·SI 환경은 JUnit4 레거시와 공존하는 경우가 많다.



1. 테스트 전략

이 섹션이 답하는 질문: 어떤 종류의 테스트를, 어디에 두텁게 두어야 하는가?


기본 사고 방식

모든 것을 E2E로 확인하는 것은 느리고 망가지기 쉽다. 반대로 유닛 테스트만으로는 실제 접속 불량을 잡을 수 없다.

swe-2026-12-01.png


원칙

유닛 테스트를 토대로 한다 (피라미드의 밑면)

중요한 경계에는 통합 테스트를 둔다

주요 업무 플로만 E2E로 눌러둔다


판단 플로

swe-2026-12-02.png


정리

"뭐든 E2E"도 "뭐든 목(Mock)"도 편향되어 있다. 중요한 것은 망가졌을 때 곤란한 장소에 따라 테스트 종류를 배치하는 것이다.



2. 테스트의 종류

이 섹션이 답하는 질문: 유닛, 통합, E2E는 어떻게 다른가?


비교

swe-2026-12-03.png

목(Mock)의 사용법

목은 편리하지만, 너무 늘리면 본물과의 어긋남을 숨긴다.


좋은 사용법:

토스페이먼츠·카카오페이 등 외부 과금 API의 고액 호출을 피한다

시각이나 난수를 안정화한다

실패 조건을 의도적으로 만든다


나쁜 사용법:

DB 접속이나 HTTP 경계를 전부 목으로 처리한다

실제의 JSON 직렬화 차분을 보지 않는다

계약 불정합을 목으로 숨긴다



// 좋은 목 사용 예 — 외부 결제 API만 목으로 처리

// 실제 DB는 Testcontainers로 실제 DB를 사용


import { createOrder } from './order-service';

import { PaymentGateway } from './payment-gateway';


// 결제 게이트웨이만 목으로 처리 (실제 호출 방지)

jest.mock('./payment-gateway');

const mockPayment = PaymentGateway as jest.MockedClass<typeof PaymentGateway>;


test('주문 생성 성공 시 결제 게이트웨이 호출', async () => {

mockPayment.prototype.charge.mockResolvedValue({ success: true, txId: 'tx_123' });


const order = await createOrder({ userId: 1, amount: 10000 });


expect(order.status).toBe('PAID');

expect(mockPayment.prototype.charge).toHaveBeenCalledWith({

amount: 10000,

currency: 'KRW',

});

});


정리

목은 테스트를 빠르게 하지만, 현실 그 자체로 만들지 않는다. 경계의 일부는 본물로 확인할 필요가 있다.



3. 테스트 프레임워크 선정

이 섹션이 답하는 질문: 어떤 도구를 사용해서 테스트를 작성해야 하는가?


주요 선택지

swe-2026-12-04.png


TypeScript / JavaScript

swe-2026-12-05.png


Java / Kotlin (Spring Boot) — 국내 엔터프라이즈 표준


// JUnit5 + Mockito + Testcontainers — Spring Boot 테스트 예시

@SpringBootTest

@Testcontainers

class OrderServiceTest {


@Container

companion object {

@JvmField

val postgres = PostgreSQLContainer<Nothing>("postgres:16")

.apply { withDatabaseName("test_db") }

}


@Autowired

lateinit var orderService: OrderService


@MockBean

lateinit var paymentGateway: PaymentGateway // 결제 게이트웨이만 목


@Test

fun `주문 생성 성공 시 PAID 상태 반환`() {

// given

given(paymentGateway.charge(any()))

.willReturn(PaymentResult(success = true, txId = "tx_123"))


// when

val order = orderService.createOrder(

userId = 1L,

amount = 10000, // 원화

)


// then

assertThat(order.status).isEqualTo(OrderStatus.PAID)

assertThat(order.paymentTxId).isEqualTo("tx_123")

}

}


E2E

swe-2026-12-06.png


선택 방법

신규 TypeScript 계열: Vitest

지금 바로 작가의 멤버십 구독자가 되어
멤버십 특별 연재 콘텐츠를 모두 만나 보세요.

brunch membership
AI개발자작가님의 멤버십을 시작해 보세요!

AI Workflow Architect, LLM Engineer, Vibe Engineering, Claude Code, AI 업무 자동화 컨설팅/AI강의

104 구독자

오직 멤버십 구독자만 볼 수 있는,
이 작가의 특별 연재 콘텐츠

  • 최근 30일간 77개의 멤버십 콘텐츠 발행
  • 총 106개의 혜택 콘텐츠
최신 발행글 더보기
이전 11화2026 모니터링, 옵저버빌리티 완전 가이드