본문 바로가기
프로젝트

숨겨진 요구사항을 다루는 방법: 정책 중심 설계와 TDD 적용기

by DevPearl 2026. 4. 15.

안녕하세요, 저는 에너지 SCADA 시스템 백오피스를 개발중인 주니어 개발자입니다.
요즘 관리 시스템의 가장 핵심 부분이 되는 장치관리 및 사이트 관리작업을 진행하면서,
도메인 정책을 정책이 아닌 CRUD로 접근하면서, 엣지케이스를 런타임버그로 잡고 정책변경에 어려움을 겪는 등 여러 문제를 겪었습니다.
그 과정에서 겪은 시행착오와 TDD와 애자일프로세스를 결합한 새로운 접근법에 대해 소개하고자 글을 작성합니다.
 
목차

  1. CRUD인 줄 알았는데, 사실은 도메인 정책 구현이었다
  2. TDD는 화면 테스트가 아니라 정책을 모으는 도구였다
  3. AI와 짧은 배포 주기로 피드백 루프를 만드는 법

1. CRUD인 줄 알았는데, 사실은 도메인 정책 구현이었다

나는 장치/사이트관리를 구현했지만, 도메인 특화된 맥락이 많아 커머스 도메인의 상품 관리에 빗대어 이야기해보려고한다.
장치 관리를 상품 관리로 치환해보면, 여러 속성을 가진 데이터를 등록/수정/조회/삭제하는 기능이 있다. 
DB설계서와 화면 사이의 간극을 채우고 숨은 요구사항들을 파악해야하는 상황에서 내가 사용한 방법은 "스펙문서"를 기반으로 개발하는 것이었다.
처음엔 질문을 리스트업하고 한번 가공해서 스펙문서로 만든 다음에 도메인 지식이 많은 시니어에게 질문했하는섹으로 접근했다.
예를 들어 이런 질문들이 있었다.

  • 화면에는 있는데 DB에는 없는 값은 A라는 필드는 유저에게 옵션을 어떻게 제공해야하나?
  • 상품번호 채번 규칙은 중간에 품절되거나 판매 중지된 상품까지 고려해야 할까?

최대한 빠짐없이 시나리오를 나누고, 답변하기 쉽게 선택지 형태로 정리하면서 나름대로는 꽤 탄탄하게 준비했다고 생각했다.
하지만 구현을 마치고 테스트 서버에 올린 뒤, 설계 단계에서는 보이지 않던 문제들이 하나씩 드러나기 시작했다.
엣지케이스가 런타임 수동검증으로 드러나고,
정챡이 하나씩 변경될 때 마다 영향범위가 커서 수정 대응이 어려웠다.

대표적인 예가 이런 정책이었다.

판매 중지된 상품은 고객 화면에서 숨겨야 한다.
단, 주문 내역 / 정산 / CS 이력에서는 보여야 한다.

 
처음에는 각 화면마다 요구사항을 반영했다.
하지만 상품 목록, 검색 결과, 상세 화면 에는 반영하면서
주문 이력이나 정산, CS 화면에서는 예외적으로 노출되어야 하는 등 정책이 세분화되고 여러 정책이 쌓이면서 코드를보고 규칙을 유추하기 어려워졌다.
물론 룰이 바뀔때마다 AI를 활용해 영향을 파악하고 수정하면 되지만,
문제는 이런 정책들이 추가되면서 코드 여러곳에 흩어지고, 문서로 정리해도 금방 다른 문서에 묻혀서 잊혀진다는 점이었다.
 
실수가 많아지고 도메인정책 수정 대응이 어려워지면서 다시 분석한 결과 문제의 원인은 내가 꼼꼼하지 못해서가 아니라, 도메인 정책을 고려하지 않고 CRUD로 접근했기 때문이라는 것을 알게됐다.
화면과 DB를 중심으로 요구사항을 정리하다 보니,
정책이 어떤 범위에 영향을 미치는지, 그리고 그 정책을 어디에 모아야 하는지는 충분히 보지 못했다.
필요했던 것은 요구사항을 더 완벽하게 수집하는 방식이 아니라,
핵심 정책을 먼저 코드로 고정하고, 짧은 피드백 루프로 검증하면서 확장하는 방식이었다.
 

2. TDD는 화면 테스트가 아니라 정책을 모으는 도구였다

시니어 분께 이 상황에 대해 조언을 구했을 때 이런 조언을 들었다.

처음부터 요구사항을 모두 파악하지 말고, 핵심을 파악한 후 빠르게 검증하고 다시 피드백을 받아 수정하는 방법도 있다.
TDD와 AI 싸이클을 결합하면 계획 단계에서 미리 검증을 고려할 수 있다.

 
최근 테스트코드를 조금씩 작성중인데, 주로 영향력이 크며 깨지기 쉬우나 깨졌을 때 조용한곳위주로 작성했다.
여러곳에서 공통으로 사용하는 함수면서 특수성이 있어서 깨졌을 때 찾기 어려운 DB 날짜 파싱 로직이나,
DB설계와 화면설계의 괴리로 코드를 특정 부분에서만 복잡하게 처리해놓은 곳 등에 테스트를 짜서 
혹시 나중에 깨졌을 때 빠르게 파악할 수 있게 했다.
실제로 그런 부분이 많이 깨지는걸 봐왔고, 파악하기 어려웠기 때문이다.
유지비용을 상쇄하는 좋은 테스트를 짤 수 있을까라는 불안감에 테스트커버리지를 좁게 가져갔다.
 
이번에 TDD라는 방법을 시도하면서 정책이면 먼저 케스트부터 짰다. “아무리 중요해도 굳이 정책을 왜 테스트를 하지? 명세는 문서로 남기면 되는거 아닌가?" 라는 의구심을 가지고있었는데 순서만 바꿨을 뿐인데 효과가 좋았다.
특히 설계단계에서 검증을 고려할 수 있던 점이 강점이다.
테스트를 먼저 작성하니 정책을 분리하게되고, 엣지케이스도 가정할 수 있었다.
테스트로 명세를 고정하고 통과하도록 구현하면서, 자연스럽게 도메인 레이어가 분리됐다.
또한 정책이 한곳으로 모이니 변경이 있어도 수정범위가 작았고, 코드 수정시 바로 테스트코드가 깨지며 명세 변경을 파악할 수 있었다.
즉, 정책이 빠지거나 예외 처리가 누락되는 실수가 줄었다.
 
간단한 예시를 들면 “판매 중지된 상품은 고객 화면에서 숨기되, 주문 이력에서는 보여야 한다”는 요구사항이 있다면,
이는 시스템 전반에서 지켜져야 하는 규칙이다.
이런 규칙을 한곳에 모으지 않으면, 각 화면 안에서 각각 처리하면서 다음과 같은 코드가 여러 곳에 흩어진다.

products.filter(product => !product.isStopped)
 

처음에는 간단해 보이지만,
이 코드가 목록, 검색, 선택 드롭다운, 추천 영역에 각각 존재하기 시작하면
수정이 어려울 뿐만 아니라 어느곳이 정책에 의한것이고 어느곳은 페이지 특수성에 의한 규칙인지 파악이 어렵다.
이를 TDD로 접근하면 먼저 명세를 코드로 고정할 수 있다.
 
먼저 서비스에서 지켜야하는 규칙을 테스트로 고정한다.

describe("product visibility policy", () => {
  it("판매 중지된 상품은 고객 목록에서 숨긴다", () => {
  });

  it("판매 중지된 상품은 주문 이력에서는 노출한다", () => {
  });
});

 
테스트가 실패하고, 테스트를 통과시키는 최소한의 구현을 만든다.

type ProductViewContext =
  | "customerList"
  | "customerSearch"
  | "productSelector"
  | "orderHistory"
  | "settlement";

export function isVisibleProduct(product: Product, context: ProductViewContext) {
  if (context === "orderHistory" || context === "settlement") return true;
  return !product.isStopped;
}
 

다시 테스트가 통과하는지 검증한다

describe("product visibility policy", () => {
  it("판매 중지된 상품은 고객 목록에서 숨긴다", () => {
    expect(getVisibleProducts([active, stopped], "customerList")).toEqual([active]);
  });

  it("판매 중지된 상품은 주문 이력에서는 노출한다", () => {
    expect(getVisibleProducts([active, stopped], "orderHistory")).toEqual([active, stopped]);
  });
});

 
이렇게 정책을 한곳에 모으고 검증한다.

새로운 정책들을 TDD로 구현하며 느낀 또다른 장점은 “무엇이 정책인지”를 분명하게 만든다는 점이었다.
에너지 도메인 특성상 몇개의 핵심 정책이 정해져있으므로,
추후 완전히 새로운 기능이 추가될때도 테스트코드의 명세를 보고 쉽게 기존 정책을 적용할 수 있었다. 변경이 들어왔을 때도 수정해야 할 중심이 비교적 선명해졌다.
 

 
물론 TDD과정에서 문제도 많다.
어떤 부분을 어떻게 테스트할지 몰라서 상수를 테스트하는 등 크게 가치가 없는 테스트를 작성하기도 하고,
일부 테스트는 데이터 설계 차원의 변경이 필요한 부분을 억지로 커버하고 있기도 하다.

하지만 한 번 결정된 정책을 일관되게 구현하고,
이후의 변경에도 흔들리지 않게 고정하는 데에는 TDD가 분명한 도움이 되었다.
TDD는 정책을 흩어지지 않게 붙잡아두는 설계 도구로 실수를 줄여주고 후반 속도를 높이는데 기여했다.
 

3. AI와 짧은 배포 주기로 피드백 루프를 만드는 법

시니어 개발자분께 "AI루프 싸이클과 TDD 싸이클, CI/CD를 함께 활용하면, 검증이 포함된 개발 루프를 빠르게 가져갈 수 있다"는 조언을 듣고 바로 적용해보았다.
 
AI의 일반적인 개발 흐름은 조사 → 계획 → 구현 → 검증으로 이어진다.
하지만 실제로는 검증 단계가 구현 이후에 수행되기 때문에,
계획 단계에서 충분히 고려되지 못하는 경우가 많다.
반면 TDD는 테스트 → 구현 → 리팩토링의 흐름을 가지며,
처음부터 검증을 중심에 두고 개발을 진행한다.
 
이 두 가지를 결합하면,
AI가 계획을 세우는 단계에서부터, TDD를 활용해 검증을 함께 설계하는 방식으로 확장할 수 있다.
계획 단계에서부터 “어떻게 검증할 것인지”를 먼저 정의하는 방식으로 개발 루프를 바꾸는 것이다.
 
이 방법을 적용하면서, AI가 작성한 plan을 그대로 사용하는 대신 빠진 정책이나 애매한 조건에 직접 주석을 달며 수정했다.
특히 조사 단계에서는 “이 정책이 적용되지 않는 예외는 없는지”,
“어떤 화면에서는 다르게 동작해야 하는지”를 추가로 정리하며 정책의 맥락을 보강했다.
그 결과, 이전에는 구현 이후에 발견되던 엣지케이스들이 계획 단계에서부터 훨씬 명확하게 드러나기 시작했다.
 
이렇게 계획 → 테스트 → 구현 → 검증 사이클을 빠르게 돌리면서,
작은 단위로 계속 피드백을 받는 방식으로 개발을 진행하는 과정에서 CI/CD가 왜 애자일 개발에서 중요한 요소인지 자연스럽게 체감할 수 있었다.
이전에는 한 번에 명세와 정책을 정리하고 기능을 완성한 뒤 피드백을 받았기 때문에,
수정 범위도 크고 어디가 문제인지 파악하는 데도 시간이 걸렸다.
반면, 작은 단위로 사이클을 반복하며 PR 단위로 피드백을 받으니 변경 범위가 명확해지고,
심지어 스스로도 어떤 부분을 점검해야 하는지 훨씬 쉽게 파악할 수 있었다.
작은 단위로 나누면 피드백이 빨라지고, 피드백이 빨라지면 개발 자체가 더 명확해진다.
 
이전에는 기능을 먼저 만들고 나중에 검증했다면,
지금은 도메인 기준으로 문제를 나누고, 테스트로 정책을 먼저 고정한 뒤 구현한다.
이 경험을 통해 깨달은 것은,
개발에서 중요한 것은 단순히 기능을 만드는 것이 아니라
도메인을 이해하고, 그 안의 정책을 흔들리지 않게 유지하는 것이라는 점이었다.
이제는 어떤 정책을 어떻게 드러내고 유지할 것인지부터 고민하며 개발하려고 한다.

'프로젝트' 카테고리의 다른 글

Next.js Proxy와 Redis로 구현한 낙관적 권한 체크  (0) 2026.05.01