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

테스트 주도개발 TDD 실천법과 도구 정리 도서 [핵심 요약]

softmoca__ 2025. 11. 13. 16:27
목차

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

 

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

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

product.kyobobook.co.kr

 

 

전통적인 개발 및 테스트의 문제점들

  1. 개발 기간이 길어질수록 개발자의 목표 의식이 흐려진다.
    1. 어디까지 짰더라?, 내가 뭘하고 있는거였지?, 이 모듈이 무슨 기능을 해야하더라?
  2. 분량이 늘어날수록 확인이 어려워진다.
  3. 개발자의 집중력이 필요해진다
  4. 코드의 사용방법과 변경 이력을 개발자의 기억력에 의존하게 돼서 논리적 오류를 찾기 어려워진다.
    1. 여기서 이값이 들어가고 이값이 나가는게 맞나??
  5. 테스트를 점차 간소화 하는 항목들이 늘어난다.
  6. 코드 수정 시 기존 코드의 정상 동작에 대한 보장이 어렵다.
    1. 아 여기 고쳤는데 이거 다른 데서도 썼던가..?
  7. 테스트를 해보려면 기존 코드를 수정해야하는 등 번거로운 선행 작업이 필요할수있다.
    1. 입고 처리 테스트 하려면 우선 주문 완료됬다고 테이블을 업데이트 시켜야하고…등등
  8. 테스트는 개발자의 노동력을 소모한다.

 

TDD란 ?

  • 메소드나 함수 같은 모듈을 작성할 떄 ‘ 작성 종료 조건을 먼저 정해놓고코딩을 시작’
    • 빠른 피드백을 통해 만들어야할 프로그램’을 학습해나가는 점진적인 기법. 피드백과 학습
  • 테스트 케이스를 작성하는 가장 기본적인 과정인 함수를 생성하기 전에 그 함수가 무엇 을 수행하고 무엇을 처리해야 하는지에 대해 생각해보는 것은 여행을 떠나기 전에 지도를 한번 살펴보는 것과 크게 다르지 않다.
  • 내가 도착해야 할 곳이 어디인지를 마음속 에 선언하는 것은 내가 다른 유혹에 빠지지 않도록 해주고 본래의 의도 또한 좀 더 명확하 게 하는 데 큰 도움을 줄 수가 있다.
    • 또한 이런 믿음과 확신을 바탕으로 테스트 케이스 를 동료와 공유함으로써 개발자의 인식 상태를 '나'의 코드가 얼마나 컴퓨터에서 잘 동작할 까 하고 고민하는 맹목적 효율성'으로부터 '우리'의 코드가 얼마나 쉽게 기억해낼 수 있고, 잘 이해할 수 있는지와 같은 '인간성'에 대한 실천으로 옮겨놓을 수 있다.
    • 간혹 개발자 로서 아키텍처 설계와 같은 구조적 혹은 기술적으로 아름다운 상위 레벨의 어떤 것들을 동경하게 된다.
    • 하지만 작은 피드백에 좀 더 신경을 쓰고 자신의 표현을 좀 더 인간의 어떤 것에 가깝게 하는 것은 작고 초라해 보일 수 있을지 몰라도 우리를 가장 안전하고 행복하 게 하는 책임감' 있는 움직임이라 이다.
      • humans for humans(프로그래밍은 '사람'을 위해 '사람'이 행하는 인간적인 작업이다)
      • TDD가 가지는 무엇인가에 '반응'하고 그것에 책임'을 수 정신과 관련된 내용.
  • TDD에서 가장 중요한 사항은 무엇인가를 해결하는 데만 집중하는 것이 아니라, 하고자 하는 게 무엇인지에 정신이 머무르게 하는 것입니다.
  • 테스트를 통과 시키는 것은 나에게 주어진 '책임'에 정중히 '응답'하는 것.

 

진행방식

  • 질문 - 테스트작성을 통해 시스템에 질문한다.(결과는 실패)
    • 작성하고자 하는 메소드나 기능이 무엇인지 선별하고 작성 완료 조건을 정해서 실패하는 테스트케이스 작성
  • 응답- 테스트를 통과하는 코드를 작성해서 질문에 대답( 결과는 성공)
  • 리팩토링- 아이디어를 통합하고, 불필요한건 삭제하고, 모호한건 명환하게 리팩토링
    • 가독성이 적절한다
    • 중복된 코드는 없는가
    • 이름이나 잘못 부여된 메소드나 변수명은 없는가
    • 구조의 개선이 필요한 부분은 없는가?
  • 반복- 다음 질문을 통해 계속 진행

 

접근방식

  • TDD에서는 테스트자동화를 통해서 개발이 시작된 시점부터 완료될 때 까지 가능한 빠른 시점내에, 자주 실패를경험하도록 유도한다.
    • OK 조건을 사전에 정해두고 빠르게 실패를 경험하며, 그 조건을 등대로 삼아 실패 상황을 최대한 빨리 극복하고자 노력한다.
    • 성공한 항목과 실패한 항목이 명확하고, 작업해야하는 부분이 확실하다.
    • 설공에 필요한 조건을 만들고, 실패하는 조건항목을 성공시킨다.
  • TDD를 하며 테스트케이스 자체가 “Amount 클래스의 구조에 대해 생각해봐라” 라는 말을 건다
  • 충분한 만큼의 테스트 케이스가 작성됐다 생각하면 제대로 구현시작
    • 모호하지만, 현재 집중하고 있는 요구 사항그 한가지만을 통과하기에는 적절한 숫자의 테스트케이스.
    • 경험이 축적됨에 따라 노련해지는 부분

 

TDD의 장점

  • 개발의 방향을 잃지 않게 유지해준다
    • 현재 자신이 어떤 기능을 개발하고 있고, 또 어디까지 와 있는지를 항상 살펴볼 수 있다.
    • 그리고 남은 단계와 목표를 잊지 않게 도와준다.
    • TDD를 진행할 때 만들어지는 테스트 케이스들, 자신이 어디까지 왔고, 앞으로 나아가야 하는 곳이 어디인지를 알려주는 나침반이 된다.
  • 품질 높은 소프트웨어 모듈 보유
    • TDD를 통해 만들어진 애플리케이션은 필요한 만큼 테스트를 거친 품질이 검증된 부 품'을 갖게 되는 것과 마찬가지다.
    • 품질 좋은 부품이 꼭 품질 좋은 제품을 보장해주는 건 아니지만, 좋은 제품을 만드는 데 있어 기본 조건임에는 틀림없다.
  • 자동화된 단위 테스트 케이스를 갖게 된다
    • TDD의 부산물로 나오는 자동화된 단위 테스트 케이스들은, 개발자가 필요한 시점에 언제든지 수행해볼 수 있다. 그리고 그 즉시 현재가자 작성된 시스템에 대한 이상 유무 를 바로 확인할 수 있다.
    • 또한 기능을 추가한다든가, 수정하게 됐을 때 수행해야 하는 회귀 테스트에 대한 부담도 줄이는다.
  • 사용설명서 & 의사소통의 수단
    • TDD로 작성된 각 모듈에는 테스트 케이스라고 하는 테스트 코드가 개발 종료와 함께 남게 된다
      • 테스트 코드들의 가치는 시간이 지나면서 두고두고 빛을 발한다.
      • 그 가치 안에는 고객만을 위한 것이 아 니라, 현재의 자신과 주위의 개발자, 그리고 미래의 개발자에게 제공되는 상세화된 모 듈 사용 설명서라는 부분도 포함되어 있다.
      • 즐겨 보는 물건은 아니지만, 그렇다고 매뉴 얼 없는 TV 등의 가전 제품을 받는다면, 제품에 대한 느낌이 어떨까? TDD를 통해 작 성된 테스트 케이스는 사용 설명서이자, 그와 동시에 다른 개발자와 소통하는 커뮤니 케이션 통로가 된다.
      • 사실, 무한한 상상력을 필요로 하는 기존 코드(egacy codes)의 난해함과 문서화 부재 가 주는 상실감이 IT 프로젝트에서 얼마나 악영향을 미치는지에 대한 연구 사례는 많다.
      • TDD는 이 부분에서도 장점을 갖는다. 일반적으로 TDD는 리팩토링과 단짝으로 진행되며, 리팩토링의 최대 미덕은 사람이 이해할 수 있는 코드로 만든다'이다.
    • 또한 TDD의 산물인 테스트 케이스들은 그 자체가 API들의 사용 예가 돼준다. 대부분의 개발자는 API 참조매뉴얼을 좋아하지 않는다. 그보다는 상황에 들어맞도록 잘 만들어진 예제를 더 좋아한다.
    • 테스트 케이스를 잘 작성하면, 개발 문서뿐 아니라 테스트 문서까 지도 함께 해결할 수 있다.
  • 설계 개선
    • 테스트 케이스 작성 시에는 클래스나 인터페이스, 접근제어자(access modifier), 이름 짓기(naming), 인자(argument) 등에 이르는 개발에 포함된 다양한 설계 요소 들에 대해 미리부터 고민하게 된다.
      • TDD에서 개발자는 자신이 작성한 프로그램에 대해 메소드 혹은 함수 단위로 테스트를 수행하며, 이는결과적으로 이후에 **발생한느 테스트 단계(통합, 인수..)에서의 결함 발생 비용을 줄여**준다.
      • 결함을 빨리 발견하면 할수록 적은 비용으로 처리가 가능하다.
    • 흔히 테스트하기 어렵다고 생각되는 코드들은 객체 설계 원리 중 기본에 해당하는 원칙들이 잘못 적용됐거나 충분히 고려되지 않 았을 가능성이 높다.
    • TDD를 진행해나가면서, 테스트가 가능하도록 설계 구조를 고 민하다 보면 자연스럽게 디자인을 개선하게 된다.
    • 즉 누가 모니터 옆에서 손가락으 로 개선해야 하는 부분을 가리켜주는 것이 아니라, 스스로의 사고와 수련으로 발전 해나갈 수 있게 도와준다.
    • 테스트 케이스를 작성할 때는, 작성하게 되는 모듈이 접할 상황에 대해 사전에 고 민해보고 준비하게 된다.
    • 미리 고민해보고 작성하는 것과 작성하면서 그때그때 추가하 는 것은, 모듈의 응집도와 의존성 정도에 있어 적지 않은 차이를 만들어낸다.
  • TDD가 주는 설계상의 이점
    • TDD 자체가 단위 단위의 작은 설계를 만들어 낸다.
    • 입력과 출력, 해당 모듈이 동작하기 위해 필요한 요소 파악 등이 사전에 확실하게 고려되고 테스트 된다.
      • 이로 인해 다른 모듈에 대한 의존성이 상대적으로 적다.
      • 왜냐하면 다른 모듈에 의존성이 많아지면 적절한 테스트케이스를 작성하기가 점점 어려워 지기 때문에, 개발자가 스스로 모듈의 의존성을 줄이려는 노력을 초반부터 해나가게 된다.
      • 또한 각 모듈은 최대한 테스트를 독립적으로 수행할수 있도록 만들기 때문에 자기 오나결성이 높아진다.
    • 결과적으로 신뢰수준을 따져봐야 하는 모듈에 대해, 개발자가 접근해야 하는 영역을 좀 더 추상화된 상위 레벨로 올려줄수 있기 때문에, 소프트웨어의 복잡도가 낮아지고 개발자가 좀 더 일반화되고 추상된 형태의 디자인에 더 집중할 수 있게 도와 준다.
  • TDD와 OOP
    • TDD를 하면 기능과 객체의 관계를 스스로 먼저 고민하게 된다.
    • 그리고 떄떄로 현재와 같은 클래스 구조로는 테스트 코드를 미리 작성할 수가 없다는 사실을 스스로 알아차리게된다.
    • 테스트 케이스 코드는 입력과 출력이 명확하고, 사용되는 재료가 적으면 적을수록 작성하기 쉽다.
    • 즉, 의존과계가 많은 코드는 테스트 코드 자체를 만들기가 어렵다.
    • 사람의 본성이라는게 어려우면 안하게 된다.
    • 의존관계를 최대한 만들지 않고, 기능이 동작하도록 노력을 하게된다.
    • TDD는 자신의 모듈에 필요한게 무엇인지를 미리미리 고민하게 만든다. 기능 위주로 테스트를 하기 때문에 불필요한 속성/필드가 무엇인지도 초반부터 밝혀진다.
    • 결과적으로 TDD는 좀 더 나은 설계, 좀 더 낭느 모듈 디자인이 될 수 있도록 스스로 생각할 수 있게 유도하고, 결과적으로는 모듈이 더 나은 구조를 가질 수 있게한다.

 

 

Mock

  • 모듈의 겉모양이 실제 모듈과 비슷하게 보이도록 만든 가짜 객체를 MOck이라고 한다.
    • 실제 객체를 만들기엔 비용과 시간이 너무 많이 들거나 의존성이 길게 걸쳐져있어 제대로 구현하기 어려울 경우, 이런 가짜 객체를 만들어 사용한다.
    • 구현하는데 필요하지만 실제로 준비하기엔 여러가지 어려움이 따르는 대상을 필요한 부분만큼만 채워넣어서 만들어진 객체.
  • 언제 Mock 객체를 만드나?
    • 모듈이 필요로하는 의존성은 테스트작성을 어렵게 만든다. 그리고 그 의존성을 단절시키기 위해 사용된다

포비의 팀 내에 테스트 개발 문화 만들기

  • 팀장의 역할
    • 단위 테스트의 필요성을 인지하고 적극적으로 지원해줘야 한다.
    • 프로젝트 초반에 개발 생산성이 저하될 가능성이 크기 때문에 효과가 나타날 때까지 인내하고 기다려줄 수 있어야 한다.
    • 단위 테스트 및 소스코드 품질의 중요성을 강조하고, 잘하는 개발자에게 포상을 해줘야 한다.
  • 팀원의 역할
    • 단위 테스트의 필요성을 인지하고 단위 테스트를 만드는 데 적극적으로 참여해야 한다. 필요 없다고 판단된다면 적극적으로 의견 개진한다.
    • 모든 개발자가 단위 테스트를 작성해야 한다. 일부 개발자만 참여할 경우 효과가 희석되거나 실패할 가능성이 많다.
    • 좋은 단위 테스트 코드를 만들기 위해 끊임없이 공부하고 노력할 자세가 되어 있어야 한다.
  • 팀의 역할
    • 단위 테스트의 필요성을 인지하고 팀의 문화로 만들기 위한 지속적인 노력을 한다.
    • 팀 구성원끼리 지속적으로 정보를 공유하고 배우려는 문화를 정착시켜야 한다.
  • 하지 말아야 할 일
    • 단위 테스트를 내가 원해서가 아니라 위에서 시키니까 한다는 수동적인 자세로 시작할 계획이 라면 하지 않는 편이 낫다. 오히려 시간 낭비가 될 가능성이 많다.
    • 항상 100%가 실행되지 않는 테스트 코드는 추후 쓰레기가 될 가능성이 높다. 쓰레기가 된 테 스트 코드를 유지보수하는 비용은 낭비되는 시간일 뿐이다.
  • 단위 테스트의 효과를 높일 수 있는 팁
    • 지속적 통합 툴(C)을 도입하여 자동화된 단위 테스트가 가능하도록 한다.
    • Test Coverage 리포트에서 복잡도가 높으나 단위 테스트 Coverage가 낮은 코드를 우선적으 로 단위 테스트한다.
    • 가능하면 테스트 코드를 먼저 만드는 TDD를 습관화한다. 단, 많은 시간과 노력이 필요하다.
    • 코드의 복잡도를 낮출 수 있도록 지속적으로 리팩토링한다.
    • 테스트하기 쉬운 코드를 만들기 위해 노력한다.

유의사항

  • 테스트케이스는 이름이 중요하다
    • 이름이 길어지더라도 해당 테스트가 무엇이고, 실패한 세부 항목이 무엇인지 곧바로 알수 있어야한다.
  • 더이상 제대로 동작하지 않는 테스트케이스는 제거한다.
    • 자동화된 테스트는 소스의 품질을 좌우하는 중요한 자산이지만, 많다고 무조건 좋진않다.
    • 중복된 케이스나 업무 변경등의 이유로 제대로 동작하지 않는 테스트 케이스는 제거한다.
  • TDD는 자동화된 테스트를 만드는것이 목표가 아니다.
    • TDD는 개발의 목표 지점을 미리 정하기 위해 단위 테스트 케이스를 만들고, 목표 상태 도달 여부를 빨리 확인하기 위해 단위 테스트 케이스를 자동화 시킨다.
    • 자동화된 단위 테스트들은 TDD의 부산물이다.
    • 개발과 설계를 위한 보조 도구이지, 목적은 아니다.
  • 하나의 테스트케이스는 하나만 테스트 하도록 작성한다.
    • 여러개의 실패하는 테스트케이스를 한번에 만들지 않는다.
  • 테스트케이스는 최대한 고립시킨다.
    • 각각의 단위 테스트는 다른 모듈이나 시스템에 최대한 독립적이고 고립된 형태로 작성될수록 단단한 테스트가 될 수 있다.
    • 그래서 가급적이면 아래와 같은건 들어가지 않도록 작성해야한다.
      • 테스트케이스가 작성되어있지 않은 모듈
      • DB,외부 시스템, 콘솔출력, 네트웤,
    • 물론 위 나열한것 자체를 테스트한다면 달라지겠지만, 대부분의 경우 현재 기능을 위해 외부에 의존하는 것들이다.
    • 결국 최대한 의존관계를 줄이고, 경우에 따라Mock객체를 이용해서라도 독립성을 보장해줘야 특정 테스트 실패에 따른 실패 전파현상을 최소한으로 줄일 수 있다.
  • 생성자테스트
    • 객체 사용을 위해 반드시 갖춰야 하는 값을 생성자에 설정하는 경우 필요
    • 선행조건이나 업무 로직이 섞여있는경우
  • 테스트코드에서는 중복에 대한 접근이 약간다르다.
    • 중복 되는 부분이 테스트 시나리오의 일부라면 곰니해봐야한다.
    • 테스트 메소드가 하나의 문맥 측면에서 완결성을 갖는것이 중요할떄도 있음.
      • 텍스트 픽스처 메서드는 그럼 언제?
      • 테스트 시나리오에 참가하고 있는 객체들에게 테스트에 필요한 사전 조건이나 기반 환경을 제공할때.
  • 유연한 코드
    • 변경이 쉬워지려면 코드가 유연해야하는데, 유연함이란 ‘개발비용증가’의 또 다른 요소가 된다.
    • ‘현재 필요한 기능에만 최대한 집줍’하는게 적절한 선택이다.
  • 자동화된 테스트케이스는 소스가 작성된 후 작성하려고 하면, 심리적인 요인에 다가 테스트 작성에 소요되는 시간과 육체적 상황으로 인해 쉽지 않다.
    • 아 귀찮아 이미 잘 돌고 있다구 !
    • 어우 피곤해 그만하고 쉬어야지
  • TDD를 할때 하나의 단계가 끝날 때 마다 리팩토링을 하면, 대상 코드 뿐 아니라 자동화된 테스트케이스 자체도 사용하기 좋은 코드로 정련된다.
    • 리팩토링은 복잡하거나 어려운 그 무엇, 이를때면 디자인 패턴 같은 부류가 절대 아니다.
    • 쉽게 배우고 크게 효과를 볼 수 있어야한다.
    • 궁극적인 목표는 ‘이해하기 쉽고, 수정하기 쉬운 코드로 만들자’이다
  • TDD는 설계를 위한 기법이고, 작업을 해나가려면 떄떄로 많은 사고와 전략이 필요한 기법이다.
    • 보통사용자 스토리 보드를 완성하기 위한 세부 TOdo 리스트가 만들어진 다음에, 짝 프로그래밍으로 TDD를 적용해보면 효과가 더 높다.
  • TDD로 개발하려면 단순히 자판은 누르면서 진행해나가는 시행착오 반복 식 보다 초반에 고민해야하는 요소가 더 많다.→ 큰 에너지 소비이자 고통,
  • TDD를 어렵게 하는 요인
    • TDD에 집중하는건 단위 기능(메소드)이지만 객체지향언어로 작성된 대부분은 클래스들로 구성된다.
    • 클래스는 API나 라이버르리와 달리 권한, 위임, 책임 등을 갖고 외부와 통신한다.
    • TDD라는 행위 자체, 혹은 TDD를 통해 만들어진 자동화 된 단위 테스트는 그 영역을 커버하지 못한다. 그래서 전체적으로 산만한 아키텍쳐가 되거나 한쪾으로 치우칠수있따.
      • 이런 현상은 TDD만이 지나치게 강조됐을 떄 발생한다.
      • 해결방법은 TDD를 진행하기 앞서 적절한 레벨의 설계를 선행하는것이다.
      • 적어도 고수준 설계 정도는 마친다음 TDD를 적용해야 이런문제를 막을수있다.
  • 우리는 종종 테스트 코드를 작성해야한다에 지나치게 집중해버린 나머지 TDD의 목표가 테스트 작성이 아닌 좀 더 나은 설계에 있다는 사실을 잊느다.
  • TDD진행시, 억지로 테스트를 작성하는 건 잘못된 접근이다.
    • 테스트를 작성하기 쉽게코드를 작성하는게 맞는거다.
    • 테스트를 만들기 어렵다면, 기본적으로 코드 설계를 다시 봐야한다.
    • 어떻게든 테스트케이스를 작성하기 위해 다양한 트릭(Mcok 프레임워크 대거 사용), 테스트 유틸리티 작성 → 결국 유지하기 어렵다.
  • TDD는 버그를 없애기 위한 방법론이 아니다.
  • 해당 프로그램의 특성, 도메인의 성격에 맞는, 그리고 작업하는 팀에 맞는 테스트 커버리지 비율을 찾아라.
    • 자동화된 테스트 케이스는 TDD의 부산물이자, 리팩토링을 위한 안전장치에 더 가깝다.
    • 그 자체를 순수 목적으로 삼게되면, 더이상 TDD의 영역이 아니게 된다.
    • 그건 테스트의 영역이 된다.