외부활동/우아한테크코스 [프리코스]

테스트 주도개발 TDD 실천법과 도구 정리 도서 요약[FAQ & 설계사고과정]

softmoca__ 2025. 11. 13. 21:35
목차

https://product.kyobobook.co.kr/detail/S000001223650

 

테스트 주도 개발 TDD 실천법과 도구 | 채수원 - 교보문고

테스트 주도 개발 TDD 실천법과 도구 | 효율적인 설계와 간결한 코드를 만드는 필수 TDD 기법『TDD 실천법과 도구』는 업고픔질 소프트웨어를 만드는 유쾌한 개발 비법 TDD를 다룬 책이다. 초급 개

product.kyobobook.co.kr

Q.private 메소드도 테스트 케이스를 만들어야 하나요?

우선 먼저'Private 메소드가 왜 생기나?' 혹은 언제 private 메소드를 만들게 되나?'를 생각해볼 필요가 있다.

Java에서의 private은 접근 범위(access scope)로 봤을 때 오직 자신의 클래스 내에서만 접근 가능한 속성이나 메소드이다.

즉, 외부에 알리고 싶지 않은 부분이라는 전제하에 만들어집니다.

하지만 클래스의 효용 가치는 그 클래 스가 갖고 있는 정보와 그 정보를 기반으로, 다른 클래스와 상호작용한다는 데 있다. 이를 모듈화, 독립화라고도 하는데, 다른 클래스 입장에서는 자신이 원하는 기능을 올바로 해주기만 하면 더 이상 신경 쓰지 않아도 된다.

그리고 그 외부에 제공 하는 기능이 제대로 동작하는지 보장해준다는 의미로 테스트 케이스를 만드는 것이다..

우리는 자동차를 살 때 특정 전선들이 정상 동작하는지, 모터의 회전수가 일정 수준 이 상으로 유지되는지를 확인하지 않습니다. 자동차를 이용하는 사용자 측면에서는 헤드라이트가 잘 커지고 꺼지고, 핸들이 정확히 동작하는지를 테스트해보고, 만족하면 되 는 거다.

즉, 이 문제에 대해서는 많은 사람이 다양하게 논의를 해왔 고, 근래에는 private 메소드는 테스트하지 않아도 무방하다는 것이 주류이다.

그리 고 public 메소드를 테스트하면 그에 딸린 private 메소드는 자연스럽게 테스트되었다 고 간주한다.

물론 굳이 private 메소드에 대한 테스트 케이스 수행을 원한다면 리플 레션 등을 이용할 수는 있지만, 리플렉션이라는 기술의 특성상 유리처럼 부서지기 쉬운 테스트 코드가 될 가능성이 크다.

 

 

Q 사용자가 직접 수행하는 수동 테스트와 자동화된 테스트, 굳이 TDD에 한정하지 않고 봤을 때, 어느 것이 더 중요한가요?

TDD의 가치를 크게 두 개만 꼽는다면, 지속적인 설계 개선과 자동화된 테스트라고 할 수 있다. 자동화는 우리가 지향해야 하는 목표가 맞다.

그런데 자동화는 대부 분의 경우에 장점이 되지만 모든 경우에 무조건 그런 건 아니다. 우선, 자동화하는 데는 많은 노력이 들어간다. 자동화의 비율이 높을수록 비용이 많이 든다.

따라서 자동화된 테스트를 작성하고자 할 때는 ROI를 고려해야 한다. 당장 오픈이 낼 모래 인데, 테스트 자동화를 위해 노력하는 건 어리석은 일일 수 있다. 하지만 자동화는 확실히 다양한 이점을 제공하며 특히 오랜 기간을 놓고 볼 때는 더욱 그렇습니다.

수동 테스트의 단점이 자동화된 테스트의 장점인 경우가 대부분이므로, 반대로 수동 테스트의 단점을 살펴보면 이렇다.

첫째, 회귀 테스트가 팀에 큰 부담이된다. 개발자가 기능을 추가/변경할 때마다 매 번 기능 테스트를 수행해야 하는데, 누락되거나 지나치게 단순화된 형태로만 테스트를 진행하게 될 가능성이 크다.

둘째, 실패/성공, 테스트 커버리지 등 다양한 보고서를 손쉽게 볼 수 없다.

결론은 적절한 수준에서의 자동화 테스트를 결정해야 한다. 때로는 전체 자동화(fall automation)가 불가능할 경우가 있다. 그렇다고 자동화를 포기하지는 마라.

 

Q. TDD를 적용하기 어려운 대표적인 이유는 무엇입니까?

TDD는 이름처럼 테스트가 주도적으로 개발을 이끌어나가는 것이 목표이다.

작성해야 할 기능(ToDo)이 있으면 코드를 작성하기 전에 테스트 케이스를 작성해서 진행하는 것이 일반적인 순서이다. 그런데 문제는 해야 할 기능이나 기능 리스트가 명확하지 않으면 테스트 코드를 작성하기가 매우 어려워진다는 점이다. 실제로 TDD가 너무 어렵다며 포기한 사례가 많다. 물론 TDD 튜 토리얼에서 제시하는 코드들이 있다.

하지만 예제 코드의 수준이 현업의 업무 장 황이나 레벨과 차이가 나는 경우가 많다. 또한 개발자들의 연습이 되어 있지 않 다는 점도 큰 이유이다.

그리고 TDD를 효과적으로 적용하기 위한 체계적인 가이드 가 없다는 것도 큰 문제이다.

어찌어찌 어렵게 테스트 코드를 작성해나가지만, 생각 보다 꽤 고통스러운 과정이라고 느끼게 된다.

그리고 결과적으로 이를 통해 만들어 진 코드들이 어지럽게 펼쳐져서는 한곳으로 모이지 못하고, 즉 TDD가 지향하는 설계 의 개선으로 흐름이 이뤄지지 않고, 결국 TDD를 포기해버리는(진행이 붕괴돼버리는, 그래서 전통적인 개발 방식으로 다시 회귀해버리는) 케이스도 적지 않다.

또한 일 부 사람들은 TDD를 하면, 설계를 하지 않고 테스트 케이스 작성과 리팩토링을 통해 설계를 만들어간다고 너무 강력하게 믿기에 요구사항 그 자체를 ToDo로 보고 무작정 TDD를 시작하는 경우도 있다. 그런데 TDD 본래의 지향점은 설계를 하지 않는 데 있진 않다.

완벽한 설계가 이뤄진 다음 개발에 들어가는 것이 아니라 설계를 일부 먼저 하고, 그 이후에는 TDD를 통해 불완전한 설계 구조를 지속적으로 개선해나가는 방식을 취한다.

결과적으로는 고객의 다양한 요구사항을 필요한 수준에서 어렵지 않 게 만족시킬 수 있는 더 나은 구조로 설계 모습을 갖게 되는 거다. 따라서 선행 설계는 반드시 필요하다. 다만, 그 깊이와 목표가 전통적인 방법론에서 이야기하는 내용과는 다소 다르다.

애자일에서는 설계는 하되, 노력 대비 효용이 높 은 최소 비용 설계'를 지향합니다. 그럼, 여기서 말하는 최소 비용 설계란 무엇일까? 또는 어떻게 해야 할까?

설계의 첫 시작은 우선 요구사항을 정련하는 것에서부터 시작해야 한다.

그 다음에 종이가 됐든, 설계 프로그램이 됐든, 참여한 개발자들이 의견일치(Concensus)할 수 있 도록, 사용자 스토리를 만족시킬 수 있는 최소한의 개발 구조(설계, 디자인)를 도출하 고, 그 구조를 바탕으로 정련된 요구사항에서 ToDo를 발췌해나간다. 그 이후 각각 의 TODo에 대응하는 테스트를 하나씩 작성하고 만족시키는 식으로 개발을 진행한다.

따라서. (정말 안타깝지만) TDD는 개발 언어 사용기술만 갖고서는 원활한 진행이 다소 어려울 수 있다(불가능한 건 아니지만, 다시 앞 단계로 돌아가서 설계를 고치 는 일이 반복적으로 생기게 된다).

요구사항 도출 정련 기법과 기본 설계 개념에 대 해서는 미리 어느 정도 이상의 지식이 있어야 한다. 이러한 사실이 다소 부담이 될 순 있지만, 고무적인 일은, 개발 1년차 개발자와 함께 요구사항을 정련해서 간략한 설 계를 도출한 다음 ToDo를 찾아내서 시작할 수 있도록 유도하는 작업을 했던 적이 있다.

개발자가 개발에 어느 정도 감이 있었기에, 요구사항 도출과 정련, 그리고 개 략적인 설계에 대한 몇 가지 기본 원칙과 방법'을 전달해준 것만으로도, 두어 시간 만 에 해당 개발자의 개발 방식이 크게 향상될 수 있다.

처음에는 막막해하던 개발 자가 ToDo 리스트에서 무엇부터 할지를 선택해 TDD를 진행하며 스스로 어떤 부분이 잘못 돼가는 것 같다는 판단과 함께, 이후 어떻게 진행할 것인지 생각하면서 개발할 수 있었다.

다시 말해, 짧은 시간의 도움만으로도 TDD를 할 수 있다는 자신감과 함께 개발 진행 스타일에 대해 어느 정도나마 감을 잡을 수 있게 됐다.

 

 

Q 테스트 케이스를 최대한 정교하게 작성하려고 노력하고 있습니다. 그런데, 그러다 보니 테 스트 대상 객체나 코드가 조금만 변경이 일어나도 테스트가 와장창 깨져서 유지보수하는 데 비 용이 많이 듭니다. 이젠 솔직히 TDD에 대한 회의가 들 정도에요. 뭐가 잘못된 걸까요?

민감한 센서일수록 오탐(false postve)불이 더 높은 게 보통이다.

사실, 질문하신 내용이 TDD의 어떤 한계점이라고 이야기하는 사람들도 있다.

아쉽지만, 뾰족한 답은 없다. 다만, 모든 테스트 케이스를 다 정교히 작성하기보다는, 중요하거나 민 감하게 반응해야 하는 모듈에 대해서만 정교한 테스트 케이스를 작성하는 것도 하나의 방법이다.

정확히 지키긴 어렵지만, 딱, 필요한 만큼만 테스트한다'는 원칙을 세워보 면 어떨까?

 

Q 면접 시에 TDD에 대해 물어보는 건 어떻게 생각하시나요?

  • 만일 제가 면접관이라면 "TDD를 해본 적이 있습니까? 있다면 장점과 단점은 무엇이 라고 생각합니까?'라고 물어볼 것 같다.
  • 단순히 관심은 있습니다 보다는 "적용해 본 적이 있습니다" 쪽이 실력은 둘째 치고라도 개발에 대해 더 열의가 있는 편이라고 생각할 수 있을 것 같다.
  • 그리고 TDD를 안다고 말한다면, 꼭 TDD의 단점에 대해 물어보자. 대답은 뭐가 되어도 괜찮다.
  • 단점을 이야기하는 게 장점을 물어보는 것보다는 더 면접자의 진실을 알려줄 수 있다.

Q 팀에 TDD를 도입하고자 할 때 초반에 TDD 외에 다른 어떤 활동을 같이 하면 좋을까요?

TDD를 도입하면 초반에 개발자들이 익숙해질 때까지는 다 함께 코드 리뷰를 진행할 것을 권장한다.

작성 시 유의점과 어려움에 대해 함께 이야기하는 것으로 팀원들이 TDD를 진행하는 데 어려움을 줄여줄 수 있다.

또한 새로운 기능을 추가하거나 버그를 찾을 때 테스트 케이스를 먼저 만들도록 어느 정도 강제화할 필요가 있다. TDD는 습관이 중요하므로, 한두 번은 넘어가는 식으 로 체계가 무너지면 곤란하다. 어느 정도 TDD가 익숙해지면 CI 서버(지속적인 통합 서버) 등을 함께 사용할 것을 권장한다.

 

Q void 메소드를 테스트 가능하게 만들려면 어떻게 해야 할까요?

우선 void 메소드가 언제 사용되는지에 대해 먼저 고민해봐야 한다.

우리는 흔히 리 턴값이 없는 메소드를 Void라고 생각하고 개발을 하는 경우가 많지만, 엄밀히 따지면 이렇다.

  • 리턴값이 있는 메소드: 기능(function)
  • void 메소드: 절차(procedure)

메소드의 리턴값이 없다는 의미는 로직의 절차를 기술했거나, 프로그램 로직 트리의 맨 끝단에 해당하는 기능 위임 작업(이를테면 화면 출력, 데이터 등록 같은 작업)을 목 표로 하겠다는 의미이다.

즉, 해당 void 메소드의 이름이 의미하는 작업을 그 메소드 에서 끝을 내겠다는 뜻이다.

이런 메소드는 Mock 객체를 설명했던 부분에서 이야기 했던, 행위 기반 테스트를 해야 한다. 가장 간단한 방법은, 상태(state)를 확인할 수 있는 다른 메소드로 결과를 테스트하는 것이다.

만일 상태를 확인할 인터페이스10 를 제공받지 못하는 상황이라면, 리플렉션 같은 방법을 사용할 수도 있다. 하지만 그다지 권장하진 않는다.

누누히 이야기 하지만 리플렉션으로 작성된 테스트 코드 는 특히나 깨지기 쉬운 테스트가 돼버리곤 하기 때문이다. 가급적이면 상태를 확인 할 수 있는 isClicked(), isReady) 같은 상태확인 인터페이스를 만들어줘라.

아니면 Mock 프레임워크를 사용할 수도 있다. 다시 한번 강조하지만, 테스트 가능한 설계가 곧 좋은 설계라는 걸 잊지 말아야한다. 

 

TODO 리스트, 대략적인 설계와 기능 리스트 작성 방법 사고 과정 예시

우선 해당 도메인의 대략적인 업무 시나리오는 다음과 같다.

고객에게 비디오 타이틀 을 대여하고 대여한 타이틀에 대한 요금을 알려준다.

비디오 가게에서는 포인트 제도 를 운영 중이며, 영화 종류와 대여기간에 따라 포인트가 조금 다르다. 구현해야 하는 기능을 중심으로 요구사항을 상세히 발췌해봤더니 다음과 같았다.

목적

  • 비디오 가게에서 고객이 대여하는 비디오의 대여정보를 조회할 수 있는 프로그램을 작성한다.

요구사항 ① 고객은 이름을 갖는다. ② 고객은 한 번에 여러 개의 비디오를 대여할 수 있으나 각각의 대여기간은 다를 수 있다. ③ 비디오는 영화, 스포츠, 다큐멘터리의 세 종류가 있다. ④ 각 비디오는 독립적인 일일 대여요금을 갖는다. ⑤ 영화는 대여기간이 2일 이상 되면 3일째부터는 대여요금이 12로 할인된다. ⑥ 다큐멘터리는 3일 이상 대여하면 4일째부터는 1/3로 할인된다. ⑦ 스포츠는 장기대여 할인이 없다. ⑧ 비디오 1개 대여할 때마다 보너스 포인트는 1포인트씩 올라간다. 단, 스포츠는 2포인트씩 올라간다. ⑨ 과거의 대여기록을 보유하고 있을 필요는 없으나 고객이 얻은 총 보너스 포인트 정보는 알 고 있어야 한다. 10 고객의 현재 대여정보를 구할 수 있는 기능을 작성하라.

  • 총 대여비디오 수
  • 대여정보 : 비디오(종류,제목, 가격), 대여기간 리슽으
  • 총대여 가격
  • 현재 대여하고 있는 비디오로 인해 추가된 포인트

많은 개발자가 이런 요구사항을 받아본 다음에 별로 복잡하지 않다는 느낌이 오면, 바로 에디터 창을 띄워 개발에 들어가려는 경향이 있다.

그건, 경험 많은 개발자에게조차 그다지 권장할 만한 방법은 아니다. 이 예제 같 은 경우엔 사용자 시나리오도 시나리오지만, 그전에 요구사항 정련이 먼저 필요하다.

대표적인 기본 규칙은 다음과 같다.

  • 하나의 목적을 갖는 의미나 동작으로 최대한 간결히 표현한다.
  • '업무규칙'과 '기능 요구사항'을 분리한다.
  • '고유명사는 대명사로, 명시적인 숫자는 변수로' 참조할 수 있도록 표시해두면 도움이 된다. 이때 종류 중 하나를 나타낼 때는 어떤(any)으로 치환해 표현하는 것도 때로 도움이 된다.
  • 행동의 주체를 가급적 명확히 표현한다.
  • 동작과 소유를 분리한다. 예: 객체의 동작인가? 객체의 소유인가?
  • 복합 한자어는 유심히 봐서 분리 가능한지 확인해서 풀어쓰면 더 좋다. 예: 대여요금- 대여행위 시에 발생하는 요금
  • 상호작용을 유심히 관찰한다. 예: 시스템의 행위인가? 고객의 행위인가?
  • 구어체는 좀 더 명확한 의미를 가진 문어체로 변경한다. 예: 구한다 - 화면에 보여준다. 계산한다. 저장한다 올라간다- 증가한다. 누적된다.

이를 기반으로 요구사항을 정련하는 모습을 다음과 같다.

요구사항 항목별로 업무규칙인지 기능 요구사항인지 구분해 표기하였다.

번호가 없는 항목들은 정련 작업 없이 그대로 사용할 생각이다.

고객은 한 번에 여러 개의 비디오를 대여할 수 있으나 각 대여기간은 다를 수 있다.

  • 업무규칙 *
  • 고객은 한 번에 여러 개의 비디오를 대여할 수 있다.
  • 대여기간은 비디오마다 각각 다를 수 있다.

비디오는 영화, 스포츠, 다큐멘터리의 세 종류가 있다.

  • 업무규칙 *
  • 비디오에는 종류가 있다.
  • 현재 비디오의 종류는 영화, 스포츠, 다큐멘터리의 세 종류다.

영화는 대여기간이 2일 이상 되면 3일째부터는 대여요금이 1/2로 할인된다.

  • 업무규칙 *
  • 영화는 대여기간이 n일 이상이 되면 m일째부터는 대여요금이 p로 할인된다.

이런 식으로 요구사항을 상세화해서 다시 적어보면 다음과 같다.

  • 업무규칙 *
  1. 고객은 이름을 갖는다.
  2. 고객은 한 번에 여러 개의 비디오를 대여할 수 있다.
  3. 대여기간은 비디오마다 각각 다를 수 있다.
  4. 비디오에는 종류가 있다.
  5. 현재 비디오의 종류는 영화, 스포츠, 다큐멘터리의 세 종류다.
  6. 각 비디오는 독립적인 일일 대여요금을 갖는다.
  7. 영화는 대여기간이 2일 이상 되면 3일째부터는 대여요금이 1/2로 할인된다. 예: 영화는 대여기간이 n일 이상이 되면 m일째부터는 대여요금이 p로 할인된다.
  8. 다큐멘터리는 3일 이상 대여하면 4일째부터는 1/3로 할인된다. 예: 어떤 비디오의 타입은 n일 이상 대여하면 m일째부터는 p로 할인된다.
  9. 스포츠는 장기대여 할인이 없다. 예: 어떤 비디오 타입은 장기대여 할인이 없다.
  10. 비디오 1개 대여할 때마다 보너스 포인트는 1포인트씩 누적된다(단, 스포츠는 2포인트씩). 예: 고객의 보너스 포인트 누적점수는 비디오를 n개 대여할 때마다 보너스 포인트는 m포 인트씩 누적된다(단, 특정 타입은 p포인트씩).
  11. 시스템은 고객의 과거의 대여기록을 보유하고 있을 필요 없다.
  • 고객과 협의해 확정해야 하는 업무규칙 *
    • 대여는 일 단위다.
    • 포인트는 정수다.
    • 할인은 분수 비율다.
  • 기능 요구사항 *
  1. 고객은 비디오를 대여할 수 있다.
  2. 시스템은 고객의 현재 대여정보를 제공할 수 있다.
    1. 대여정보
      1. 총 대여비디오 수
      2. 비디오(종류 + 제목 + 가격), 대여기간 리스트
      3. 총 대여가격
      4. 현재 대여하고 있는 비디오로 인해 추가된 포인트
  3. 시스템은 현 대여로 인해 발생하는 포인트 총합을 계산할 수 있다.
  4. 시스템은 대여된 비디오들의 총 대여가격을 계산할 수 있다.
  5. 시스템은 총 대여비디오 수를 계산할 수 있다.
  • 기타 요구사항 확인 작업 *
    • 과거의 대여기록을 보유하고 있을 필요는 없으나 고객이 얻은 총 보너스 포인트 정보는 알고 있어야 한다. → 포인트 정보의 기원을 의미하는 것지, 개별 포인트만의 관리가 아닌 대여 반납의 포인트는 할인가지를 확인한다.

자. 이번엔 사용자 시나리오를 작성해 그걸 기반으로 테스트 케이스를 만드는 식으로 진행하는 것이 아니라, 클래스를 먼저 도출해보는 방식이다.

자. 개발에 편한 클래스, 즉 행위 기반으로 요구사항을 목록으로부터 읽고 도출해보자. 크게 행위 기반 클래스와 소유 기반 클래스로 나뉘었다.

행위 기반 클래스 도출

  • 대여한다 (고객이 비디오를)
  • 할인된다 (비디오의 일일 대여가격이)
  • 누적된다 (보너스 포인트가)
  • 계산한다 (시스템이 포인트 총합을)
  • 계산한다 (시스템이 총 대여가격을)
  • 계산한다 (시스템이 총 대여비디오 수를)
  • 제공한다 (시스템이 대여정보를)

이제, 이 정도면 됐다면서 구현에 들어가고 싶은 마음이 굴뚝같을 텐데 조금만 더 참자. 아직 보정이 필요하다.

우선 쉬운 것부터

OOP에서 객체에 수동 액션을 담는 일은 좀처럼 없다(버스가 운전을 당한다? 설마…). 그런데 요구사항으로부터 액션(행동)을 도출하고 보면 본의 아니게 수동이 쓰이는 경우가 있다. 만일 수동이 쓰인 상태로 구현을 하게 되면 타 모듈에 대한 의존성이 높아져서 설계가 전체적으로 복잡해진다. 다음 예를 살펴보자.

  • 할인된다 (비디오 일일 대여가격이)
  • 누적된다 (보너스 포인트가)

위 두 가지 액션을 하는 행위를 능동으로 바꿔보자. 바꾸다 보면, 행위의 주체가 시스템이라는 걸 자연스럽게 알 수 있다.

  • 할인한다 (시스템이 비디오 일일 대여가격을)
  • 누적한다 (시스템이 포인트를)

이번엔 살짝 복잡한 걸로

언제나 느끼지만 요구사항의 한자어는 위험하다. ‘대여한다’는 말은 사실 매우 어려운 말이다.

무언가를 가져와서 일정 기간 동안 소유로 삼다가 돌려준다는 뜻인데, 요구사항을 보면, 여러 개를 대여할 수 있고, 항목별 대여일수가 다를 수 있으며, 대여한 일수에 따라 대여 항목별로 비용과 포인트가 각각 책정되었다고 적혀있다.

이를 지원하기 위해서는 각 대여항목을 독립적으로 다룰 무언가가 필요하다. 클래스를 도출하기 위해 명사를 기준으로 분석해보면 ‘대여’라는 실체가 없는 개념이 넘어 필요해진다. 대여라는 행위를 하나의 추상 개념으로 구현할 경우 고객과 영화를 직접 다루는 ‘고객 – 영화’ 식으로 설계한 경우에는, 이러한 대여항목별 대여일자 정보를 같이 다루기가 어려워진다. 따라서 다소 떠한 결론적으로, 대여에는 영화를 포함하는 식의 ‘고객 – 대여 – 영화’ 같은 구성을 갖게 되면, 고객:대여=1:m , 대여:영화=1:1 관계를 통해 대여일수 관리가 가능해진다.

다음은 소유 기반으로 클래스를 도출한 부분이다.

소유 기반 클래스 도출

  • 이름을 갖는다 (각 고객은)
  • 종류를 갖는다 (각 비디오는)
  • 대여요금을 갖는다 (각 비디오는)
  • 포인트를 갖는다 (각 비디오)
  • 대여일수를 갖는다 (비디오인가? 고객이?)

마찬가지로 정련 작업을 해보자.

우선 쉬운 것부터

TDD에서는 크게 강조하는 내용에 해당하진 않지만, 소유 기반 클래스를 살펴볼 때는 소유의 기준이 되는 조건이 무엇인지 생각해볼 필요는 있다. 예를 들면, 포인트 정보를 갖고 있어야 하는 것은 대여한 비디오(클래스)인가? 아니면, 대여한 비디오의 타입인가? 비디오가 포인트를 갖는다고 생각하기 쉽지만, 사실 부여되는 포인트는 비디오가 어떤 타입을 갖느냐에 달려 있다. 스포츠 타입인가? 아니면 다큐멘터리 타입인가? 하는 식으로 말이다.

이번엔 살짝 어려운 걸로

대여일수를 갖는다 (비디오가? 고객이?)

대여일수 정보는 어디에 둘 것인가에 대한 고민이다. 어느 쪽(클래스)에 놓을지 애매할 경우에는 객체의 구체적인 인스턴스를 예로 들어서 해당 정보를 ‘갖고 있다’는 말이 적합한지 테스트해본다.

예: 타이타닉은 대여일수를 갖고 있다. (타이타닉의 대여일이 고정?)

장동건은 대여일수를 갖고 있다. (장동건은 7일? 무슨 소리냐 이건?)

이런 식으로 따져보면, 비디오도, 고객도 대여일수는 갖는 모습이 어색하다는 걸 알 수 있다. 앞서 찾아낸 ‘대여’라는 추상적인 개념이 이 부분에서 쓰이는 것이 적절해보인다. 그리고 이때 클래스에 해당 정보가 위치해야 하는 이유를 함께 생각해볼 필요가 있다. 예를 들자면, ‘대여일수를 갖는다’ → 대여일수를 대체 왜 필요하고 하는가? → 대여가격과 포인트를 계산하려고! 하는 식으로 말이다.

예: 타이타닉 비디오 영화에 대한 ‘대여’는 대여일수를 갖고 있다. (비용과 포인트 계산이 가능!)

이런 식으로 표현해보면, 영화와 대여가 1:1 관계가 된다는 것도 문장에서 자연스럽게 파악된다. 이 정도까지 발전하면 이제 ‘대여일수를 갖는다’ 주체와 그 클래스를 해야 하는 행동이 눈에 보이게 된다. 이렇게 해서, 최소의 문장을 정련한 모습은 다음과 같다.

대여일수를 갖는다 (각 대여는)

 

이쯤 되면 사람이 따라 “TDD는 이런 게 아니라고! 설계를 해버렸잖아!”라고 말하고 싶을 수도 있다. 아쉽지만, 그건 또 그렇지가 않다. TDD에 들어가는 시험에 의해 최소한의 클래스와 시스템의 행위는 나와 있어야 한다. XP가 극단을 추구했기에 eXtreme이라고 불리지만, 창시자 켄트 벡도, 그의 절친 마틴 파울러 아저씨도 필요할 만큼의 설계는 개발 전에 해야 한다고 말한다.

그리고 정확히는 TDD란 말 자체도, ‘시스템 구축을 개발부터’의 의미를 나타내는 것이 아니라, ‘개발할 때는 테스트 코드부터(Test First Development)’의 의미를 갖는다. 약 설명들은, 글로 설명하느라고 더 길고 복잡해진 것이지, 사실 두 명 정도의 인원이 요구사항을 같이 읽고 바로 정련에 들어갔다면 20~30분 내외에서 TDD를 시작할 수 있는 수준이다.

 

위 내용을 다 따라서 최종적으로 정리해보면 다음과 같이 클래스가 도출된다.

 

행위 기반 클래스

  • 대여한다 (고객이 비디오를)
  • 할인한다 (시스템이 비디오 일일 대여가격을)
  • 누적한다 (시스템이 포인트를)
  • 계산한다 (시스템이 포인트 총합을)
  • 계산한다 (시스템이 총 대여가격을)
  • 계산한다 (시스템이 총 대여비디오 수를)
  • 제공한다 (시스템이 대여정보를)

소유 기반 클래스

  • 이름을 갖는다 (각 고객은)
  • 종류를 갖는다 (각 비디오는)
  • 대여요금을 갖는다 (각 비디오는)
  • 포인트를 갖는다 (각 비디오 타입은)
  • 대여일수를 갖는다 (각 대여는)

 

물론 이 정도 수준의 요구사항 정제만으로 만들어진 위와 같은 설계가 절대 완벽할 리는 없다.

하지만 개발의 시작점은 마련해줄 수 있기 때문에, TDD로 개발해나가면서 리팩토링과 함께 설계를 개선하는 방향을 통해 충분히 보완할 수 있다.

이제 각 클래스에 대한 테스트 케이스를 만드는 식으로 구현에 들어가면 좀 더 쉽게 작업할 수 있다.

이 때 시작은 소유 기반 클래스부터, 그리고 다른 클래스로의 확인이 낮은 클래스부터 시작하는 걸 권장한다.

그런 클래스들은 자신만으로 테스트 케이스 작성이 가능하기 때문에 테스트 케이스 작성의 난이도가 비교적 낮다. 위의 클래스 다이어그램 기준으로는 비디오 타입 클래스나 비디오 클래스부터 하는 것을 말한다. 행위 기반 클래스들은 이런저런 클래스들을 함께 이용해야 하는 경우가 많다. 시스템에 해당하는 VideoShop 클래스를 보면, 기능(=행위)이 잔뜩 포진해 있기 때문에, 기능을 구현할 때 많은 클래스가 필요하다.

그래서 일반적인 상황이라면, VideoShop 클래스에 대한 TDD 개발은 다소 늦추면서 이런저런 가능성이 크다.

이 정도 설계를 한 후, 소유 기반 클래스부터 시작해 행위 기반 클래스까지 차분히 테스트 케이스를 만들면서 개발해나가다 보면, 최종적으로는 통합 테스트(integration test) 수준의 테스트 케이스가 자연스럽게 만들어진다.