Test-Driven Development
"테스트 주도 개발(Test-driven development TDD)은 매우 짧은 개발 사이클을 반복하는 소프트웨어 개발 프로세스 중 하나이다. 개발자는 먼저 요구사항을 검증하는 자동화된 테스트 케이스를 작성한다. 그런 후에, 그 테스트 케이스를 통과하기 위한 최소한의 코드를 생성한다. 마지막으로 작성한 코드를 표준에 맞도록 리팩토링한다. 이 기법을 개발했거나 '재발견' 한 것으로 인정되는 Kent Beck은 2003년에 TDD가 단순한 설계를 장려하고 자신감을 불어넣어준다고 말하였다." - 위키피디아

 

테스트 주도 개발 (=TDD)은 테스트 코드를 먼저 작성하고, 작성된 테스트 케이스들을 이용하여 반복적으로 테스트하며 개발하는 것을 말합니다. 처음 TDD를 도입하려고 했을 때에는 시간이 부족하다는 혼자만의 판단으로 '나중에, 나중에...'를 계속 되새겼지만 실제 TDD를 도입해본 결과 분명히 일정한 양의 반복적인 재발비용이 발생하는 것은 사실이나 업무의 양이 두배, 혹은 수배나 되는 많은 시간이 (물론 익숙해지기 까지의 일련의 과정과 많은 삽질이 필요합니다 ㅡ_ㅡ;;) 가중되는 것은 아니었습니다.

 

 

 

TDD Cycle Red Green Refactor

처음 TDD를 진행하며 가장 막연했던 것은 Sanity Test가 끝난 후 '무엇을 테스트 하며 어떻게 구성하냐'였습니다. 제가 레퍼런스로 삼았던 Testing Vue.js Applications.js (Manning | Edd Yerburgh ) 책에서는 여기에 관하여 경험자로서 그리고 Vue.js 코어팀의 Test 관련 담당자로서 자신의 철학을 잘 설명해주고 있습니다. (아직 번역서는 없습니다.)

 

출처: Testing Vue.js Applications.js

 

여기서 저자는 프론트엔드 개발 시 이상적인 테스트 스위트의 구조를, 다음 세 단계의 Unit tests, Snapshot tests, e2e tests의 피라미드 형태로 보여줍니다. 전체의 테스트 스위트 중 해당 테스트가 차지하는 비율은 60% / 30% / 10%입니다.

 

이 세가지의 테스트는 동일하게 어플리케이션을 테스트하는 것이지만 관심사나 테스트 방식, 동작이 전혀 다릅니다.

 

유닛 테스트컴포넌트 컨트랙트로 동작해야 하는 기능들을 테스트하며 테스트 스위트 중 가장 많은 비율을 차지합니다. 이 유닛 테스트 과정에서 서버에 요청을 보내고 응답을 받는 일련의 과정은 테스트에 포함시키지 않았습니다. 가장 큰 이유 중 하나는 테스트 실행시간과 프론트엔드 개발의 관심사를 벗어난 부분이기 때문입니다. 제공된 API는 검증의 책임이 백엔드 서비스 / 로직에 있습니다. 그와 함께 현재의 네트워크 연결상태와 응답까지 걸리는 시간테스트 때마다 늘 증가하는 것은 생산성을 매우 많이 떨어뜨리게 만드는 행위이므로 이는 테스트 대상에서 제외합니다.

 

스냅샷 테스트는 말 그대로 컴포넌트 / 레이아웃의 현재의 상태를 저장(스냅샷)하여 다음번 테스트 실행시 원래의 스냅샷과 비교하여 오류 상태를 알려줍니다. 이전의 테스트가 존재하지 않다면 첫 실행시 스냅샷은 단순 저장만 합니다. 그러한 이유로 컴포넌트, 레이아웃의 모든 개발과정이 끝난 후 mannual testing을 거친 후 테스트 코드를 작성 해야합니다. 그렇지 않다면 계속해서 업데이트 되는 항목들이 생겨 올바른 테스트가 되질 않고 불필요한 시간을 소비하게 됩니다.

 

마지막 e2e 테스트는 모든 유닛 테스트와 스냅샷 테스트를 마친 후 어플리케이션 프로세스의 여정을 가상으로 테스트합니다. 말이 조금 생소하게 다가올지 모르나 엔드유저가 어플리케이션에 체류하면서 어떻게 사용할것인지 예상되는 방법들을 정리하여 브라우저를 띄운 상태에서(이는 자동 실행됩니다.) 해당 절차대로 어플리케이션을 동작시키는 테스트입니다.

 

간단히 TDD가 무엇이며 어떻게 구성되었는지 알아보았습니다. 이렇게 열거해놓고 보니 꽤나 복잡하게 느껴지실 겁니다. 하지만 TDD에 있어서 가장 중요한 것은 따로 있습니다.

 

일단 제멋대로 구현하고 있는 자신의 손과 sanity test, 즉!!! 컴포넌트를 정상적으로 테스트 프레임워크에서 동작시키도록 만드는 것이 가장 중요합니다. 앞서 설명한대로 TDD는 우선 테스트 코드를 작성하는 것으로 시작합니다. 많은 TDD 관련 서적과 정보를 찾아보면 아래와 같은 문구를 발견하실 수 있습니다.

 

'반드시 실패하는 테스트 코드를 먼저 작성한다.'

 

그렇다고 성공하는 테스트 코드를 실패하게끔 작성하라는 뜻은 아닙니다. 위의 문구는 구현부가 없기 때문에 반드시 실패하는 테스트 코드가 처음 작성될 것이라는 의미를 강조한다고 생각하시면 됩니다. TDD의 시작은 테스트 코드부터 작성을 하고 그것을 성공하게 만든 후 리팩토링을 하는 과정을 반복적으로 수행하는 것입니다. 위의 과정을 통해서 해당 테스트 코드로 다듬어진 구현부가 완성되는 것입니다.

 

그런데 테스트 주도 개발에 익숙하지 않은 개발자라면 우선 손가락이 먼저 움직이게 됩니다. 저 역시도 구현부를 만들다 도중에 테스트 코드를 작성하기도 하고 구현이 끝난 후 테스트 코드를 작성해가며 애를 먹었습니다. 우선은 많이 익숙해지기 전까지는 테스트 코드를 먼저 작성하는 습관을 들이는 것이 중요합니다.

 

그러한 습관이 들기 전에 마주할 커다란 벽은 해당 컴포넌트를 테스트할 환경을 구성하는 일입니다. 컴포넌트를 jest라는 프레임워크에 mount 하고 외부 라이브러리를 사용한다면 그에 대한 처리를, 해당 컴포넌트가 다른 컴포넌트의 하위 컴포넌트로 마운트 시점부터 propsData를 가지고 있어야 한다면 해당 데이터를 세팅해주어야 정상적으로 컴포넌트의 테스트 코드를 작성할 수 있습니다. 이런 일련의 과정이 처음에는 굉장히 무의미해 보이고 소비하는 시간 대비 생산성이 떨어질 때왜 이런 소모적인 일을 해야하는지, 이걸 반복적으로 해야되나하는 의구심과 불만이 커지게됩니다. 저 역시도 마찬가지였지만 지금 두개의 프로젝트에서 TDD를 도입하여 개발을 진행해본 결과,

 

'그것은 가치있다!'

 

로 노선을 바꾸게 되었습니다. 우선은 이렇게 함으로서 외부 라이브러리 동작방식에 대하여 생각하고 공부하는 시간이 많아졌습니다. 컴포넌트에 필요한 것을 생각하는 시간이 늘었고 동작방식이 이상하거나 개선해야 할 사항을 다시 한번 점검하게 되는 계기를 마련해 주었습니다.

 

테스트 주도 개발을 하며 늘어난 것 중 하나는 예외처리였습니다. 예상밖의 응답값 혹은 동작이 발생하였을 때 예외처리를 어떻게 할 것인가, 또 사용성 유지를 위하여 에러를 발생시키는 것이 나은지 아니면 프로세서의 처음으로 돌려보내는 것이 맞을지, 아니면 알림으로 엔드 유저에게 해당 부분의 처리를 가이드해줄지, 이러한 예외처리에 대한 부분이 코드 곳곳에 늘어나게 되었습니다.

 

한가지 더 늘어난 것 중 하나는 항상 유닛 테스트를 하기 때문에 회귀에러를 많이 줄일 수 있다는 것입니다. 물론 회귀에러가 줄어든 것이지 없어지지는 않습니다.

 

마지막으로 클래스 / 함수 / 메서드 등의 구현부가 조금 더 SOLID하게 변하게 되었습니다. 각각의 테스트를 진행하며 각각의 디스크립션을 작성하다 보면 해당 기능의 역할과 책임이 분명해집니다. 물론 개발을 하기 전에 기획서 혹은 요구사항 명세를 보겠지만 해당의 지시 혹은 문서는 말 그대로 서비스적인 관점에서 작성됩니다. (절대적이지 않으며 단순히 저의 경험에서 나온 결론입니다.) 엔드 유저 관점에서는 관심이 없는 부분이지만 개발할 때에는 꼭 지켜져야 하는 부분, 예를 들면 해당 데이터의 응답 혹은 연산이 비용이 많이 드는 것이라면 메모이제이션으로 해당 값을 캐싱한다는 계획 같은 것은 개발자가 구현하며 추가해야 하는 기능들입니다. 물론 경험 많은 개발자들은 기획 리뷰를 하며 이러한 부분들을 바로 캐치할 수 있겠지만 테스트 코드를 작성하며 반복적인 리팩토링 작업을 하다보면 해당 기능을 구현하는 부분의 문제점과 반드시 극복해야하는 개발적인 이슈들이 눈에 잘 띄게 됩니다.

 

출처: pinterest

그렇다면 앞으로 모든 프로젝트는 TDD를 실행한다?!

 

그것은 프로젝트의 성격과 개발자 및 조직의 선택사항입니다. TDD가 훌륭한 하나의 개발 방법론이지만 이는 각자의 상황과 환경, 시간적인 부분이 충분히 고려되어야 할 것입니다. 위에서도 말씀 드렸지만 개발 시간은 일정부분 증가할 수 밖에 없습니다. 모델 인터페이스가 변경되거나 컴포넌트의 중요한 로직이 변경된다면 무조건 테스트 코드의 수정이 필요합니다.

 

한가지의 추가 사항 혹은 수정사항이 생길 때 최악의 경우, 그와 관련된 거의 대부분의 테스트를 손봐야 하는 경우도 생깁니다. 그렇기 때문에 TDD를 도입할 때에는 여러가지 상황이 고려되어야 할 것입니다. 아직 무엇도 확정된 것이 없고 주요 기능만 러프하게 주어진 상황에서 프로토타이핑을 할 경우 TDD를 도입하는 것이 맞는가를 생각해 본다면, 물론 답은 정해지지 않았지만, 저는 그렇지 않다라고 얘기할 것 같습니다.

 

어찌됐건 TDD는 좋은 개발 방법이자 코드베이스를 조금 더 깔끔하고 우아하게 관리하는 습관인 것 같습니다.

 

 

 

 

+ Recent posts