우테코 2주차 회고

2025. 11. 26. 01:09·Programming

저번 프리코스에서는 그냥 원래 있던 테스트 코드를 참고해서 그대로 작성해봤는데, 이번 프리코스를 통해 처음으로 JUnit과 assert의 동작 원리를 직접 이해하게 되었다. 단순히 결과를 확인하기 위한 도구라고만 생각했던 테스트 코드가, 이번에는 실제로 코드의 동작을 보장하고 설계를 점검하는 역할을 한다는 것을 느꼈다. 그래서 자연스럽게 “테스트 코드를 어떻게 작성해야 하는가”뿐만 아니라, “테스트가 코드 구조에 어떤 영향을 미치는가”까지 고민하게 되었다.

1. 책임의 경계를 어떻게 잡을 것인가

RacingGame 클래스를 설계할 때 가장 먼저 고민한 것은 책임의 범위였다. 초기에는 자동차 생성, 이동, 우승자 계산뿐 아니라 입력 검증까지도 모두 RacingGame이 처리하도록 구현했는데, 그렇게 되면 클래스의 역할이 모호해지고 테스트가 복잡해진다는 문제가 있었다. 그래서 입력 검증은 별도의 InputView로 분리하고, RacingGame은 오직 게임 로직인 자동차 생성, 이동, 우승자 계산만 담당하도록 구조를 단순화했다. 이 과정에서 “얼마나 책임을 세밀하게 나누어야 하는가”에 대한 고민이 있었다. 책임을 과도하게 분리하면 구조는 깔끔해지지만, 호출 관계가 복잡해지고 전체 흐름을 한눈에 보기 어려워진다는 단점이 있었다. 결국 테스트하기 쉽고 읽히는 코드라는 균형점을 찾는 데 초점을 맞췄다.

2. 테스트를 위한 구조적 고민

Car 클래스의 이동 로직은 단순했지만, 테스트에서는 가장 고민이 많았던 부분이었다. 자동차는 랜덤으로 생성된 숫자가 4 이상일 때만 전진하도록 되어 있었는데, 처음에는 단순히 pickNumberInRange(0, 9)를 호출해 구현했다. 그러나 이렇게 하면 테스트를 실행할 때마다 결과가 달라져, “로직이 제대로 작동하는가”를 객관적으로 검증하기가 불가능했다. 이 문제를 해결하기 위해 MoveNumberGenerator라는 인터페이스를 만들고, 이를 구현한 클래스가 실제 숫자를 생성하도록 했다. RandomMoveNumberGenerator는 실제 실행 시 랜덤한 숫자를 반환하고, 테스트에서는 고정된 숫자를 반환하는 FixedMoveNumberGenerator를 사용해 예측 가능한 결과를 검증할 수 있도록 했다.

이 방식으로 테스트는 안정적으로 수행할 수 있게 되었지만, 동시에 “테스트를 위해 설계를 변경하는 게 과연 맞는가?”라는 고민도 생겼다. 즉, 실제 실행에는 불필요해 보이는 인터페이스가 오직 테스트를 위해 추가된 구조가 좋은 설계라고 볼 수 있는가에 대한 의문이었다. 결과적으로 이 선택은 단순한 테스트 편의성을 넘어서 의존성을 낮추고, 확장 가능성을 열어준 설계로 이어졌다. 테스트가 가능해졌다는 점도 중요했지만, 그보다 “랜덤성”이라는 비결정적 요소를 코드 밖으로 분리함으로써 로직의 순수성을 유지할 수 있었다는 점이 더 의미 있었다. 다만 여전히 테스트 중심 설계가 코드 복잡도를 어디까지 높여도 되는가에 대한 고민은 남았다.

라이브러리 제공 함수도 테스트해야 하는가?

이 과정에서 또 하나의 고민이 생겼다. 바로 pickNumberInRange 자체를 테스트해야 하는가에 대한 의문이었다. 처음에는 “랜덤 함수가 정말 0부터 9까지의 숫자를 생성하는지” 검증해야 하는지 헷갈렸다. 하지만 곰곰이 생각해보니 그 함수는 이미 외부 라이브러리에서 제공하는 기능이기 때문에 내가 검증할 책임이 없는 부분이었다. 내가 테스트해야 하는 것은 랜덤 자체가 아니라, “랜덤으로 생성된 값에 따라 Car가 올바르게 반응하는가”였다. 즉, 랜덤 함수의 신뢰성보다는 내 로직의 반응이 중요했다. 그래서 직접적인 랜덤 테스트는 제외하고, 대신 고정된 숫자를 주입해 “4 이상일 때 움직이고, 그 미만일 때는 멈추는 로직이 올바른가”를 검증하는 데 집중했다. 이 과정을 통해 테스트의 본질이 ‘값의 우연성’을 검증하는 것이 아니라 ‘로직의 결정성’을 검증하는 데 있다는 점을 명확히 인식할 수 있었다.

3. 입력값 검증을 하나로 묶을까, 나눌까

입력값 검증도 많은 고민이 필요했다. 초기에는 InputView에서 입력을 받을 때 이름이 비어 있는지, 중복되는 이름이 있는지, 5자를 초과하는지를 한꺼번에 확인하도록 validateCarNames() 함수에 넣었다. 처음엔 이렇게 한 함수에 모두 넣는 게 단순하고 가독성도 좋았다. 하지만 시간이 지나면서 검증 로직이 늘어날 경우 “하나의 함수로 계속 유지할 수 있을까?”라는 의문이 들었다. 각 검증을 개별 함수로 분리할 수도 있었지만, 그렇게 하면 결국 메인 검증 함수 안에서 여러 함수를 순차적으로 호출해야 해서 구조적인 변화는 거의 없었다.

또한 테스트 관점에서도 실제로는 어려움이 없었다. 잘못된 입력을 주었을 때 IllegalArgumentException이 발생하는지를 확인하는 방식으로 충분히 모든 검증 로직을 커버할 수 있었기 때문이다. 그래서 지금 단계에서는 오히려 단순함을 유지하는 게 더 효율적이라고 판단했다. 다만, 나중에 검증 규칙이 많아지거나 검증 항목별로 정책이 달라질 가능성이 생기거나 혹은 재사용성이 높다면, 그때는 validateLength(), validateUnique()처럼 세분화하는 것이 더 나을 것이다. 결국 지금은 “하나로 두되, 나중에 확장 가능한 구조로 남겨두자”는 식의 절충적 결정을 내렸다.

'Programming' 카테고리의 다른 글

우테코 3주차 회고  (0) 2025.11.26
Discord에 500 Error Webhook으로 알림 보내기  (0) 2025.11.26
AOP랑 OOP랑 뭐가 다르지?  (0) 2025.09.26
SOLID 원칙: 객체 지향 설계의 5가지 기본 원칙  (0) 2025.09.18
'Programming' 카테고리의 다른 글
  • 우테코 3주차 회고
  • Discord에 500 Error Webhook으로 알림 보내기
  • AOP랑 OOP랑 뭐가 다르지?
  • SOLID 원칙: 객체 지향 설계의 5가지 기본 원칙
baeminn
baeminn
새로운 기술을 익히는 데 그치지 않고, 실제 문제 해결에 적용하며 구조와 한계를 함께 고민해왔습니다. 서비스 설계, 공간 데이터 분석, 도구 개발 등 다양한 프로젝트를 통해 문제 해결에 필요한 기술을 직접 선택하고 적용해 왔으며, 하나의 역할에 머무르지 않고 필요한 영역까지 책임지는 개발자로 성장하고자 합니다!
  • baeminn
    BaeLog
    baeminn
  • 전체
    오늘
    어제
    • 분류 전체보기 N
      • Programming
      • Java
      • CS
        • DB
        • OS
      • Web
      • Spring
      • GeoInfo
      • Infra N
        • Kubernates
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    set
    redis
    Java
    I/O
    select
    database
    AOP
    쉬운코드
    멀티플렉싱
    replicaton
    Datebase
    데이터베이스
    sharding
    spring aop
    spring
    matcher
    epoll
    poll
    hash
    n+1
    OS
    dbcp
    cs
    우테코
    MAP
    infra
    db
    partitioning
    OOP
    io
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
baeminn
우테코 2주차 회고
상단으로

티스토리툴바