Post

Canon TDD by Kent Beck 원문 번역

Kent Beck의 Canon TDD 포스팅을 번역하였음

Canon TDD by Kent Beck 원문 번역

*이 글은 kent beck의 Canon TDD를 번역한 글입니다.


켄트 벡: 다음에 이어지는 내용은 당신이 TDD를 이렇게 해야한다는 뜻이 절대 아니다. 당신이 어떤 방식을 선택하든 자신의 작업 품질에 책임을 져라. 정말로 책임을 지기만 한다면 말이다.
이제 이어질 내용은 이런 주장들에 대한 나의 답이다:
“TDD는 구려 친구, 왜냐면 <’TDD가 아닌 무언가를 언급하며’> 때문이지”, 가장 흔한 예는 이런식이다. “왜냐면 내가 코드를 적기도 전에 모든 테스트를 미리 작성하는 것이 싫기 때문이야”
만약 당신이 무언가를 비판하려면, 실제 그 대상 자체(비판하려는 대상을 바로 그 자체)를 비판해라.

  1. 다루고 싶은 테스트 시나리오 목록을 작성한다.
  2. 그 목록에서 정확히 하나만 골라, 실제로 구체적이고 실행 가능한 테스트로 만든다.
  3. 해당테스트(그리고 기존의 모든 테스트)가 통과 하도록 코드를 변경한다. 이 과정에서 새 테스트가 필요하다는 것을 알게 되면 시나리오 목록에 계속 추가한다.
  4. 필요하다면 구현 설계를 개선하기 위한 리팩터링을 한다.
  5. 목록이 다 빌때까지, 2번으로 돌아가라

INTRO

‘Vic Wu’ 이 포스트의 요약을 도식화함 Vic Wu summarized this post graphically:

최근 TDD에 대한 해명들을 하면서 한가지 놀랐던 점은 사람들이 TDD의 정의에 동의를 하지 않는다는 것 이였다. 난 내 책에서 그 부분을 가능한 한 명확히 썼다. 적어도 난 충분하다고 생각했다. 아니, 내 불찰이다.

아래에 적힌 워크플로우랑 다르게 하고 있는데도 잘 돌아간다면, 축하한다! 그건 *Canon TDD는 아니겠지만, 그래서 뭐 어쨌다는 건데?. 이 단계를 뚝딱뚝딱 그대로 따라했다고 골드 스타(우리로 치면 칭찬카드)를 주는것도 아니다.
*canon:규범,계율

만약 당신이 TDD를 까겠다고 계획을 짜고는 아래 워크플로우를 까는게 아니라면, 그건 그냥 허수아비 때리기(straw man fallacy)다.

그게 바로 내가 남은 인생의 소중한 시간을 들여서까지 이 글을 쓰는 이유다.
-나는 당신한테 어떻게 프로그래밍을 하라고 하는 것도 아니다. 칭찬 카드를 위해 요금을 청구하는 것도 아니다.-

나는 평소에 되도록 긍정적이고 건설적으로 말하려고 한다. 하지만 이 글은 어쩔 수 없이 간결하고 부정적일 것이다.
“사람들이 이걸 잘못 알고 있다. 이게 진짜다!”
누군가의 워크플로우를 까려는 것도 아니다. 하지만 Canon TDD에 대한 이해를 더 날카롭게 다듬으려는 것이다.


Overview

테스트 주도 개발(TDD, Test-Driven Development)는 하나의 프로그래밍 워크플로우다.
프로그래머는 시스템의 동작을 바꿔야 한다( 그 시스템이 지금은 텅 비어 있을지라도 ). TDD는 프로그래머가 다음과 같은 상태에서 시스템의 새로운 state를 생성하도록 돕기 위해 존재한다.

  • 예전에 잘 돌아가던 것들이 모두 여전히 잘 돌아가고 있다.
  • 새로운 동작은 예측한대로 작동한다.
  • 시스템은 다음 변경을 할 준비가 되어있다.
  • 프로그래머와 그들의 동료들은 위 내용들에 대해 확신을 느낀다.

Interface/Implementation Split

첫 번째 오해는 사람들이 설계라는 걸 죄다 한 덩어리로 묶어버린다는 거다. 거기엔 두 가지 결이 있다.

  • 특정한 부분의 동작이 어떻게 요청되는가
  • 그 동작을 어떻게 시스템이 구현하는가

(내가 학교에 다닐 적에는 이걸 논리적 설계와 물리적 설계라고 불렀다. 그리고 두 가지는 절대 섞일수 없다고 말이다. 하지만 누구도 어떻게 섞지말라는 것은 설명해주지 않더라. 그건 내가 나중에 직접 알아내야 했다.)


The Steps

사람은 형편없는 컴퓨터다. 앞으로 이어질 것들이 컴퓨터 프로그램처럼 보이겠지만 아니다. 프로그램을 다루는데 익숙한 사람들에게 효과적으로 전달하기 위한 시도(attempt)로 이렇게 적는 것이다.
내가 “시도”라고 말하는 이유는, 앞에서 말했듯이, 사람들이 이런식으로 말하기 쉽게 보이기 때문이다.
“ 야 TDD 구리다! 나 완전히 다른 것(자신은 TDD라고 생각하고 했지만 결국은 TDD가 아닌)을 했는데 실패했거든 “

1. Test List

TDD의 첫 단계는, 바꾸고 싶은 동작과 시스템이 주어졌을 때, 그 새로운 동작에서 기대되는 모든 변수(경우의 수)를 쭉 나열하는 것이다.
“기본 케이스가 있고, 그리고나서 만약 이 서비스가 타임아웃이 난다면, 또 키가 데이터베이스에 아직 없으면, 그리고 또…”

이건 분석이지만, 동작(Behavior)에 대한 분석이다. 지금 당신은 그 동작의 변경이 무조건 작동 해야하는 모든 다른 경우의 수를 생각하고 있다. 만약 당신이 이미 존재하는 동작들을 깨지 않고 동작의 변경을 하는 방법들이 생각났다면, 그것도 리스트에 넣어라.

실수: 구현 디자인에 관한 의사결정들을 섞어 넣는 것.
참아라. 내부 구조를 어떻게 할지 의사결정할 시간은 나중에 얼마든지 있다.
당신이 테스트 목록을 작성하는데만 모든 집중을 다한다면 더 나은 성과를 낼 수 있을거다.(만약 당신이 네임팬으로 냅킨에 구현 스케치를 해야한다면, 그래라, 하지만 내가 해봤는데 당신은 그게 꼭 필요하지는 않을거다. 그냥 시험 삼아 해봐라)

책에서 이 단계를 놓친 사람들이 있는 것 같아 보인다.
“ TDD는 이제 막 시작했다 🚀 넌 너가 언제 TDD를 끝낼지 절대 모를거야 “. 응 아냐(NOPE)

2. Write a Test

단 하나의 테스트.
진짜로 자동화된 테스트 말이다, 셋업도 있고 호출도 있고, assertion도 있는(꿀팁: 가끔 assertion에서부터 거꾸로 작업해보길)
바로 이 테스트를 쓰는 과정에서 당신이 디자인 의사결정들을 만들어 내기 시작할 것이다. 하지만 그 의사결정들은 대부분 인터페이스에 관한 것들 일거다.
구현에 대한 결정이 새어나올지도 모르지만, 그건 시간이 지나면 점점 더 잘 피할 것이다.

실수: 코드 커버리지를 올려보겠다고 assertion도 없는 테스트를 작성하는 것.

실수: 테스트 리스트에 있는 항목들을 전부 구체적인 테스트로 바꿔놓고, 그걸 한번에 통과시키는 방식.
테스트를 통과시키는 과정에서, 그동안 만든 수많은 ‘가상의 테스트들’이 전체에 영향을 주게되어 의사결정을 다시 고려해야 한다면? 재작업(Rework)이다.
그리고 테스트 6번째 쯤에 왔는데 아직까지 단 하나도 통과한게 없다면..? 그건 그냥 우울이나 지루함으로 이어진다.

역자왈: 테스트 리스트에 있는 항목들을 전부 구체적인 테스트로 바꿔놓는 다는 것은 테스트 리스트의 테스트를 하나씩 하나씩 사이클을 돌리는게 아니라 모든 테스트를 한번에 다 짜놓고 전체 테스트를 한번에 해버리는 상황을 의미한다. 그렇게 된다면 red-green-refactor의 사이클을 돌리는게 무의미해진다(빠른 피드백도 받을 수 없다). 그것은 TDD가 아니다.

다음 테스트를 고르는건 중요한 기술이며, 그건 경험을 통해서만 습득할 수 있다. 테스트의 순서는 프로그래밍을 하면서 느끼는 경험 전체와 최종 결과에도 지대한 영향을 끼친다.
(열린 질문: 코드는 초기 조건에 민감한가?)

카오스 이론

3. Make it Pass

이제 실패하는 테스트를 하나 만들었으니, 그 테스트가 통과하도록 시스템을 바꿔라.

실수: 테스트가 통과한 ‘척’ 하게 만들려고 assertion(검증)을 지워버리는 것. ‘진짜로’ 통과되게 만들어라.

실수: 실제 계산된 값을 그대로 복사해서 테스트의 expected 값에 붙여넣는 것.
그렇게 하면 이중 확인(double checking) 이 무너지고, TDD가 주는 검증 가치의 상당 부분이 사라진다.

실수: 테스트를 통과시키는 과정에 리팩터링을 섞어버리는 것.
또다시 나오는 그 “두 개의 모자를 동시에 쓰는(동시에 두 가지 일을 하는)” 문제다. 일단 돌아가게 만들고, 그다음에 올바르게 만들어라. 당신의 뇌는 (결국엔) 그걸 고마워할 거다.

레드에서 그린으로 가는 과정에서 새로운 테스트가 필요하다는 걸 발견한다면, 그 테스트를 테스트 리스트에 추가해라. 만약 그 테스트가 당신이 이미 해놓은 작업을 무효화한다면 (“젠장, 빈 폴더의 경우에는 처리할 방법이 아예 없네.” 같은 상황), 계속 밀고 갈지, 아니면 처음부터 다시 할지 결정해야 한다.
(꿀팁: 다시 시작해라. 하지만 이번에는 테스트를 구현하는 순서를 다르게 골라라.)

그리고 테스트가 통과하면, 그 테스트를 리스트에서 체크해라.

4. Optionally Refactor

이제 당신은 구현 디자인 의사결정을 내릴 차례다.

실수: 이번 단계에서 필요로 하는 정도보다 더 많이 리팩터링하는 것.
뭔가 정리하는 기분도 들고 좋을 수 있다. 그리고 다음 테스트를 마주하는게 두려울 수 있다. 특히 만약에 그 테스트가 당신이 어떻게 통과시켜야 할지 모르는 것이라면.
(나도 지금 사이드 프로젝트 하나에서 바로 그 문제에 갇혀 있다.)

실수: 너무 일찍 추상화시키는 것.
중복은 힌트일 뿐, 명령이 아니다.

역자왈: 코드에서 중복이 발견되면, 그것은 “여기서 추상화나 리팩터링을 고려해볼 만하다”는 힌트라는 것이다. 중복을 발견했다고 무조건 중복을 제거해야하는 것은 아니다. 아직 패턴이 명확하지 않고, 추상화가 복잡성을 증가시켜버린다는 판단이 된다면 안할 수 도 있다. 그러므로 중복이 발견 되었다고 “바로 리팩터링을 하라”는 명령같은게 아니라는 의미이다.

5. Until the Test List is Empty, Go To 2.

코드의 동작에 대한 두려움이 지루함으로 변할 때까지. 계속 테스트하고 코딩해라!

This post is licensed under CC BY 4.0 by the author.