[테스트 주도 개발] 32장. TDD 마스터하기

Seyeong·2022년 12월 29일
0

테스트 주도 개발

목록 보기
2/2

아래는 TDD를 프로그래머 각자의 습관에 통합시켜 나가는 과정에서 숙고해볼 수 있는 몇 가지 질문들이다. 여기서는 답이나 힌트를 제시하기도 하며, 직접 탐험해볼 수 있도록 답을 남기지 않기도 한다.

단계가 얼마나 커야 하는가?

사실 여기에는 두 가지 질문이 숨어 있다.

  • 각 테스트가 다뤄야 할 범위는 얼마나 넓은가?
  • 리팩토링을 하면서 얼마나 많은 중간 단계를 거쳐야 하는가?

만약 한 줄의 로직을 추가하고 약간의 리팩토링을 할 수 있을 정도 크기의 테스트를 만들 수 있는가 하면, 수백 줄의 로직과 수시간 분량의 리팩토링을 할 만큼의 크기를 갖는 테스트를 만들 수도 있을 때, 이중에서 어떤 것이 옳은가?

이것의 답변으로는 당신은 둘 다 할 수 있어야 한다. 시간이 지남에 따라 테스트 주도 개발자(Test-Driven Developer)의 경향은 분명히 나타난다. 바로 단계가 점점 작아지는 것이다.

테스트할 필요가 없는 것은 무엇인가?

간단한 대답으로는 애플리케이션 개발에 있어서 두려움이 지루함으로 변할때까지 테스트를 만드는 것이다. 그러나 이는 피드백 루프이고, 스스로 대답을 찾아야만 한다. 다음 목록들을 시도해보라.

  • 조건문
  • 반복문
  • 연산자
  • 다형성

하지만 당신이 작성하는 것들에 대해서만 테스트하라. 불신할 이유가 없다면 다른 사람이 만든 코드를 테스트하지 마라.

때때로 외부 코드의 정확한 스펙(버그)때문에 자신만의 로직을 더 작성해야 하는 경우엔, 그걸 테스트해야 할지 아닐지는 위의 목록을 참고하라. 필자의 경우 때로는 특별히 더욱 조심하기 위해 외부 코드에, 버그가 존재한다는 것을 문서화하는데, 그러한 방법으로 그 버그가 수정되면 버그가 있다는 것을 테스트하던 코드가 실패하게 될 것이다.

좋은 테스트를 갖췄는지의 여부를 어떻게 알 수 있는가?

다음은 설계 문제가 있음을 알려주는 테스트의 속성이다.

  • 긴 셋업 코드 : 하나의 단순한 단언을 수행하기 위해 수백 줄의 객체 생성 코드가 필요하다면 뭔가 문제가 있는 거다. 객체가 너무 크다는 뜻이므로 나뉠 필요가 있다.
  • 셋업 중복 : 공통의 셋업 코드를 넣어 둘 공통의 장소를 찾기 힘들다면, 서로 밀접하게 엉킨 객체들이 너무 많다는 뜻이다.
  • 실행 시간이 오래 걸리는 테스트 : 실행하는 데 오래 걸리면 테스트를 자주 실행하지 않게 되고, 한동안 실행이 안 된 채로 남게 되는 경우가 종종 있고, 이렇게 되면 테스트가 아예 동작하지 않을 수도 있다. 더 나쁜 점은, 애플리케이션의 작은 부분만 따로 테스트하기가 힘들다는 것을 의미한다.
  • 깨지기 쉬운 테스트 : 예상치 못하게 실패하는 테스트가 있다면 이는 애플리케이션의 특정 부분이 다른 부분에 이상한 방법으로 영향을 끼친다는 뜻이다. 연결을 끊거나 두 부분을 합하는 것을 통해 멀리 떨어진 것의 영향력이 없어지도록 설계해야 한다.

TDD로 프레임워크를 만들려면 어떻게 해야 하나?

모순 : 코드의 미래에 대해 고려하지 않음으로 인해, 코드가 더 뛰어난 적응성을 가질 수 있게 한다.

TDD는 이것을 뒤집는 것처럼 보인다. 내일을 위해 코딩하고 오늘을 위해 설계하는 것처럼.

개방-폐쇄의 원칙은 서서히 지켜져 가는데, 실제로 발생하는 변주들에 대해서는 특히 더 그렇다. 테스트 주도 개발은 비록 발생하지 않은 변주 종류는 잘 표현하지 못할지라도. 발생하는 변주 종류는, 바로 그것들을 잘 표현하는 프레임워크를 만들게 해 준다.

그래서 3년 후에 일반적이지 않은 변화가 발생하면 어떻게 될까? 그 변화를 수용하기 위해 정확히 필요한 지점에서 설계가 급격한 진화를 거치게 된다. 이는 개방-폐쇄 원칙을 잠시 위배하게 되지만, 이에 따른 비용은 크지 않다. 왜냐하면 스스로 뭔가를 잘못하지 않았다는 확신을 줄 수 있는 많은 테스트들이 존재하기 때문이다.

변화를 매우 빨리 연달아 도입하는 극한적 상황에서 TDD는 미리 설계하고 개발하는 것 처럼 보인다.

피드백이 얼마나 필요한가?

테스트를 얼마나 작성해야 할까?


작은 예제를 직접 스스로 풀어보라.

삼각형의 각 변의 길이를 나타내는 세 개의 정수를 받아서 다음 값을 반환하는 문제다.

  • 정삼각형이면 1을 반환
  • 이등변삼각형이면 2를 반환
  • 부등변삼각형이면 3을 반환
  • 삼각형이 될 수 없다면 예외를 던진다.

필자는 이 문제를 풀기위해 6개의 테스트를 작성했고, 누군가는 같은 문제를 위해 65개의 테스트를 작성한다. 당신은 자신의 경험과 숙고를 통해, 얼마나 많은 테스트를 작성할지 결정해야 할 것이다.

TDD의 테스트에 대한 관점은 실용적이다. TDD에서 테스트는 어떤 목적을 위한 하나의 수단이다. 만약 어떤 구현에 대한 지식이 신뢰할 만 하다면 그에 대한 테스트는 작성하지 않을 것이다. 의도적으로 구현을 무시하는 블랙 박스 테스팅은 몇 가지 이점이 있다. 블랙 박스 테스팅은 코드를 무시함으로써 하나의 다른 가치 체계를 드러낸다. (몇몇 상황에서는 적절한 태도이긴 하나 TDD와는 다르다.)

테스트를 지워야 할 때는 언제인가?

테스트가 많으면 좋기야 하지만, 서로 겹치는 두 개의 테스트가 있어도 기들을 남겨두어야 할까? 답은 두 가지 기준에 의해 결정된다.

  • 첫째 기준은 자신감이다. 테스트를 삭제할 경우 자신감이 줄어들 것 같으면 절대 테스트를 지우지 말아야 한다.
  • 둘때 기준은 커뮤니케이션이다. 두 개의 테스트가 코드의 동일한 부분을 실행하더라도, 이 둘이 서로 다른 시나리오를 말한다면 그대로 남겨두어야 한다.

라고 말은 했지만, 자신감이나 커뮤니케이션 면에서 별 부가적인 이득이 없는 중복 테스트가 존재한다면, 덜 유용한 것을 삭제하라.

프로그래밍 언어나 환경이 TDD에 어떤 영향을 주는가?

TDD 주기(테스트/컴파일/실행/리팩토링)를 수행하기가 힘든 언어나 환경에서 작업하게 되면 단계가 커지는 경향이 있다.

  • 각 테스트가 더 많은 부분을 포함하게 만든다.
  • 중간 단계를 덜 거치고 리팩토링을 한다.

이렇게 하면 개발 속도가 더 빨라질까?

TDD 주기를 더 잘 지원하는 언어와 환경에서 작업하게 되면 더 많은 실험을 해보려고 할 것이다. 이것이 당신을 더 빨리 진행하도록 또는 더 나은 해법에 도달하도록 도와주는가, 아니면 순수한 숙고를 위해 일정 시간을 제도화하는 것이 더 나을까?

거대한 시스템을 개발할 때에도 TDD를 할 수 있는가?

YES

애플리케이션 수준의 테스트로도 개발을 주도할 수 있는가?

작은 규모의 테스트로 개발을 주도하는 것의 문제는 실제로 사용자가 원하지 않는데 그들이 원할 거라 생각하고 구현할 수도 있는 위험을 끌고 간다는 점에 있다. 애플리케이션 수준에서 테스트를 작성한다면 프로그래머에게 약간의 도움을 받아서 사용자가 원하는 바를 테스트로 작성할 수 있을 것이다.

하지만 여기에는 기술적인 문제가 있다. 바로 고정물을 만드는 것이다.
아직 만들지 않은 기능에 대한 테스트를 어떻게 작성하고 실행할 것인가? 이 문제에서 탈출하는 방법이 어떻게든 있는 것 같다. 전형적인 방법은 아직 어떻게 해석해야 할지 모르는 테스트를 만났을 때 우아하게 에러를 뱉어내는 해석기(interpreter)를 도입하는 것이다.

애플리케이션 테스트 주도 개발(ATDD, Application Test-Driven Development)에는 사회적인 문제가 존재한다. 바로 사용자에게 기존에 없던 테스트를 작성시키는 책임이 존재하는 것이다.
또 다른 문제는 테스트와 피드백 사이의 길이다. 만약 고객이 테스트를 작성하고 통과하기까지 열흘이 걸린다면, 거의 열흘동안 빨간 막대만 보게 될 것이다. 내 생각에 난 앞으로도 프로그래머 수준의 TDD를 원할 것 같다. 그래서

  • 즉시 초록 막대를 볼 수 있고
  • 내부 설계를 단순화할 수 있길 원한다.

프로젝트 중반에 TDD를 도입하려면 어떻게 해야 할까?

이미 상당량의 코드가 있을때 새 코드는 TDD로 작업하길 원하는 것은 여러 문제를 발생시킬 수 있다. 대표적으로는 테스트를 염두에 두지 않고 만든 코드는 테스트하기가 그리 쉽지 않다는 점이다. 일부분만을 격리해서 실행하고 결과를 검사할 수 있게끔 인터페이스가 설계되어 있지 않다.

"고치면 되지 않은가?" 라는 질문을 할 수 있겠지만, 리팩토링 과정에서 에러가 발생할 수도 있는데 아직 테스트가 없기 때문에 에러가 생겼다는 점을 알아내기가 매우 힘들다.

따라서 먼저 해야 할 일은 변경의 범위를 제한하는 것이다. 지금 당장 변할 필요가 없는 부분을 봤다면, 그냥 그대로 놔둘 것이다.

그 다음으로는 테스트와 리팩토링 사이에 존재하는 교착 상태(deadlock)를 풀어주는 것이다. 테스트가 아닌 다른 방법으로도 피드백을 얻을 수 있는데, 아주 조심스럽게 작업을 하거나 파트너와 함께 작업을 하는 방법 등이 그런 것이다.

TDD는 누구를 위한 것인가?

만약 당신이 어느 정도는 작동하는 코드를 한번에 몰아 입력해 넣는 것에 행복해 하고, 그 결과를 두 번 다시 쳐다보지 않는 것에 행복해 한다면, TDD는 당신을 위한 것이 아니다. TDD는 더 나은 코드를 작성한다면 좀더 성공할 것이라는, 매력적일 정도로 나이브하며 해커적인(geekoid) 가정에 근거한다.

TDD는 더 깔끔한 설계를 할 수 있도록, 그리고 더 많은 것을 배워감에 따라 설계를 더 개선할 수 있도록, 적절한 때 적절한 문제에 집중할 수 있게끔 도와준다.

사실 TDD는 오버액션이다. TDD는 현재 업계에서 통용하는 수준보다 훨씬 더 적은 수의 결함과 훨씬 더 깨끗한 설계의 코드를 작성하게 해준다.

TDD는 코드에 감정적 애착을 형성하는 해커들에게도 좋다. TDD를 사용하지 않으면 엄청난 흥미를 가지고 새 프로젝트를 시작해서는 시간이 지남에 따라 서서히 코드가 썩어가는 걸 보게되면서 하루빨리 이를 버려버리고 새로운 프로젝트가 시작되기를 기다리는걸 심심치 않게 볼 수 있다. 그러나 TDD는 시간이 지남에 따라 코드에 대한 자신감을 점점 더 쌓아갈 수 있게 해준다. 테스트가 쌓여감에 따라(그리고 테스팅 기술이 늘어감에 따라) 시스템의 행위에 대한 자신감을 얻게 되고, 설계를 개선해 나감에 따라 점점 더 많은 설계 변경이 가능해진다.

TDD는 초기 조건에 민감한가?

테스트를 취할 때 특정한 순서로 하면 매우 매끄럽게 잘 넘어가는 것 같은데, 똑같은 TDD 주기를 가지더라도 테스트를 전혀 다른 순서로 구현해보면 작은 단계로 나아갈 수 있는 방법이 없는 것처럼 보이기도 한다. 이는 테스트를 특정 순서로 구현하는 것이 다른 순서에 비해 훨씬 쉽고 빠른게 사실일까? 단지 내가 구현 기술이 부족해서는 아닐까? 혹 테스트를 특정 순서로 공략해야 한다는 것을 넌지시 알려주는 무언가가 테스트 속에 있는 건 아닐지 고민해보자

TDD와 패턴의 관계는? (★)

TDD와 패턴의 또 다른 관계는 패턴 주도 설계(Pattern-Driven Design)에 대한 구현 방법으로써 TDD다. 어떤 작업을 수행하기 위해 전략(Strategy) 패턴을 사용하기로 결정했다고 가정해보자. 그 다음 리팩토링 단계에서 자연스럽게 전략 패턴이 나타날 수 있도록 하기 위해 의식적으로 두 번째 테스트를 작성해본다. 문제는 설계가 항상 의도하지 않은대로 흘러간다는 것이다. 완벽하게 사리에 맞는 설계 아이디어가 결국은 틀린 것으로 판명난다. 그냥 시스템이 무슨 일을 할지 생각하고 나중에 설계가 알아서 정해지도록 하는 것이 더 낫다.

어째서 TDD가 잘 작동하는가?

TDD는 결함을 빨리 발견해 고칠 수 있게함으로써 비용을 낮출 수 있다(테스트 자체가 주는 이점이니까). 그러면서 결함 감소에서 오는 이차적인 심리학적, 사회적인 효과가 많다.

TDD의 또다른 효과는 설계 결정에 대한 피드백 고리를 단축시킨다는 점이다. 하루에도 수십~수백 번씩 테스트하면서 구현에 대한 피드백 고리는 확실히 짧아졌다. 설계 결정에 대한 피드백 루프의 길이는 설계에 대한 생각(API가 ~식으로 생기면 좋겠다던지, 메타포가 이래야 할 것이라던지 등) 사이의 간격이다.

0개의 댓글