목록으로
ENGINEERING

우리는 무엇으로 코드를 믿는가

우리는 무엇으로 코드를 믿는가

들어가며

우리는 코드가 맞다고 믿어야 한다. 코드가 동작해야 제품이 동작하기 때문이다.

문제는, 그 믿음의 근거다.

많은 개발자들은 테스트가 있으니 안전하다고 말한다. 어떤 이들은 TDD로 개발했으니 괜찮다고도 말한다. 하지만 정말 그럴까?

테스트가 있다는 사실만으로, 우리는 코드를 신뢰할 수 있을까?

특히 AI가 정말 많은 코드를 만들어내고 있는 지금, 우리는 점점 더 많은 코드를 충분히 이해하지 못한 채 받아들이고 있다. 나 역시도 가끔은 동작하니까 맞겠지. 테스트도 있으니 괜찮겠지 하고 넘어가곤 한다.

하지만 코드를 믿는다는 것은, 결국 무엇이 검증되었는가의 문제다.

테스트의 착각

테스트가 통과했다. 이 코드는 믿어도 되는가?

테스트는 코드가 옳다는 사실을 증명하지 않는다. 다만 우리가 세운 가정이, 우리가 준비한 범위 안에서 통과했다는 사실만 보여준다.

예를 들어보자.

당신은 미국에서 서비스를 운영하는 개발자다. 서비스 내부에는 UTC로 저장된 시간을 뉴욕 현지 시간으로 보여주는 로직이 있다.

그래서 당신은 함수를 작성한다.

function toNewYorkTime(utcDate: Date) {
  const NEW_YORK_OFFSET = -5 * 60 * 60 * 1000; // -5시간
  return new Date(utcDate.getTime() + NEW_YORK_OFFSET);
}

이제 이 함수의 신뢰도를 위해 테스트를 작성해보자

describe('toNewYorkTime', ()=>{
  test('뉴욕 시간대로 변경한다.', ()=>{
    const utc = new Date('2026-01-01T05:00:00.000Z');
    expect(toNewYorkTime(utc)).toEqual(new Date('2026-01-01T00:00:00.000Z'));
  })
})

테스트는 성공할 것이다. 실제로 한동안은 아무 문제 없이 운영 될 수 있다.

하지만 이 로직은 여름이 되면 틀리게 될 것이다. 우리가 간과한 것은 단순한 구현 실수가 아니다. 시간대에 대한 근본적인 실수이다.

뉴욕의 시간은 항상 UTC-5가 아니다. America/New_York은 계절에 따라 UTC-5이기도 하고 UTC-4이기도 하다. 서머타임이 시작되면 1시간씩 앞당겨지기 때문에 위의 로직은 1시간씩 어긋날 것이다.

즉, 이 테스트가 증명한 것은 “뉴욕 시간대로 변경한다”가 아닌 “5시간 늦은 시간대로 변경한다”만 증명한 셈이다.

테스트는 언제나 진실의 증거가 되지 않는다. 잘못된 가정을 함께 믿는 테스트는 우리가 믿고 싶은 것을 확인해줄 뿐이다.

AI 시대의 테스트

AI가 코드를 쓰는 시대에는 이 문제가 더 선명해질 수 있다. AI는 코드의 생산 속도와 양에서 인간을 압도하고 있다. 하지만 문제를 이해하고, 맥락을 책임지고, 무엇을 검증해야 하는지 판단하는 방식은 여전히 인간의 문제이다.

AI에게 요구사항을 대충 설명하면, AI는 그럴듯한 구현을 만든다. 그리고 그 구현이 통과할 만한 테스트도 함께 만든다. 하지만 구현과 테스트가 같은 오해를 공유한다면, 그 테스트를 검증이라고 할 수 있을까?

구현과 테스트가 같은 오해를 공유한다면, 그 테스트는 로직을 검증하거나 보호하지 못한다. 오히려 잘못된 확신을 더 단단하게 만들 수 있다.

그렇다면 AI 시대에서 테스트가 중요하지 않은가? 아니다. 오히려 더 중요해졌다. 다만 이제 중요한 것은 테스트를 많이 만드는 일이 아니라, 무엇을 검증해야 하는지를 더 분명하게 판단하는 일이다.

또, AI 시대의 테스트는 자유 분방한 AI에게 적당히 길을 알려주는 길잡이 역할을 할 수도 있다. 이런 이유에서 테스트의 중요성은 더더욱 중요해질 수 있다.

TDD에 대한 고찰

나는 비즈니스 로직을 테스트하는 것을 좋아한다. 하지만 그렇다고 해서 TDD를 언제나 기본값으로 두어야 한다고는 생각하지 않는다.

내가 어떤 방법론을 볼 때 가장 먼저 묻는 것은 이것이다. 이 방법은 어떤 문제를 해결하기 위해 만들어졌는가?

TDD는 테스트를 먼저 작성함으로써 요구사항을 더 분명하게 만들고, 작은 피드백 루프 안에서 설계를 다듬게 돕는다. 함부로 인터페이스를 흔들지 못하게 하는 장점도 있다. 이런점에서 TDD는 분명 유용한 개발방법이다.

다만 내가 늘 망설이게 되는 지점은, 아직 문제를 충분히 이해하지 못한 순간에도 너무 이른 확신을 만들 수 있다는 점이다. 초기 요구사항은 자주 바뀐다. 비즈니스 규칙도 아직 안정되지 않은 경우가 많다.
이때 테스트를 먼저 작성하면, 나중에 지켜야 할 규칙보다 내가 처음 떠올린 가정이 더 먼저 굳어질 수 있다.

그 순간 테스트는 증명의 도구가 아니라 선입견의 기록이 되기 쉽다.

그래서 나는 TDD를 원칙이라기보다 도구에 가깝게 본다. 입출력이 명확한 함수, 계산 로직, 이미 규칙이 충분히 드러난 비즈니스 로직처럼 무엇을 검증해야 하는지가 비교적 선명한 영역에서는 명확히 도움이 될 수 있다.

반대로 아직 개념이 흔들리고 있고, 요구사항 자체가 탐색 중인 영역에서는 테스트보다 먼저 문제를 더 오래 이해하는 시간이 필요할 때도 있다.

내가 경계하는 것은 TDD 자체가 아니다. 어떤 상황에서도 반드시 테스트를 먼저 써야 한다는 태도다.

방법론은 사고를 돕기 위해 존재한다. 사고를 대신하기 위해 존재하는 것은 아니다.

내가 믿는 것

내가 믿는 것은 구현의 모양을 따라가는 테스트가 아니다.

시스템이 반드시 지켜야 할 약속을 붙잡고 있는 테스트다.

현재 브리딩 시스템을 개발하면서 이런 부분에 테스트 코드를 작성하고 있다. 가장 간단한 예시를 들어보자.

나비어리에서 브리딩된 알에는 각자 고유의 번호가 부여된다. 형식은 아래와 같다.

260317-25P01-1-005

  • 260317: 태어난 년월일 (KST 기준)
  • 25P01: 부모새의 pairId
  • 1: 1번째 클러치
  • 005: 해당 부모의 해당 클러치의 몇번째 알인지

이런 비즈니스 규칙은 쉽사리 바뀌지 않는다. 그렇기 때문에 테스트가 작성되어도 좋다고 생각한다.

여기서 중요한 것은 이 ID 규칙이 도메인에서 명시적인 약속이라는 점이다.

describe('generateId Test', () => {
  test('산란일, 부모code, 부화 순서를 입력하면 알 고유 아이디를 생성한다.', () => {
    const id = Egg.generateId({
      laidAt: new Date('2025-07-20T20:00:00.000Z'),
      pairId: '25P01',
      clutch: 1,
      sequence: 5,
    });

    expect(id).toBe('250721-25P01-1-005');
  });
});

유닛테스트로 먼저 검증을 한 후 추후에는 실제로 부모새의 pair 데이터, 해당 pair의 알 데이터등을 준비해서 integration test를 진행해봐도 좋다고 생각한다.

내게 있어 테스트를 어디까지 진행해야하냐 묻는다면, 당신이 불안하지 않을때까지 라고 답할 것 같다.

마무리

테스트가 많은 코드는 안전해 보일 수 있다.

하지만 그것이 믿을 수 있는가는 잘 모르겠다.

전 회사에서 견적 시스템을 맡았을 때 계산 로직의 테스트 케이스만 100개가 넘었다. 하지만 여전히 버그는 발생했다. 항상 예기치 못한 곳에서 버그가 나타나는 법이니까..

계산식도 많이 바뀌고 그럴때마다 100개가 넘는 테스트 케이스가 발목을 잡았었다. 그리고 안정화가 되었을 즈음에는 더이상 버그가 발견되지 않았다.

지금와서 돌이켜보면 나는 안심을 하려고 테스트케이스를 작성했던 것 같다.

내게 테스트는 내가 지켜야 할 비즈니스 로직의 증거이다. 실제로 나 역시 비즈니스 로직이 계속해서 변경되고 하는 과정에서 테스트 코드 역시 많이 변경되었다.

다만 이것은 내가 이 로직이 어떤식으로 작성해야한다는 증거를 코드 외의 수단으로 남겨놓은 셈이다.

시스템이 지켜야 할 약속이 테스트로 작성되어 있을 때, 비로소 그 코드를 조금 더 믿을 수 있다.

AI 시대에서 테스트는 프롬프트만으로는 남기기 어려운 방향성을 제시해주는 나침반이 될 수 있다.

#테스트 #TDD #AI #개발 철학 #나비어리