프론트엔드 개발자의 TDD 적응하기

2022년 11월 11일 | 기술 이야기

안녕하세요. 와디즈 FE개발팀 입니다.

저는 꽤 오랜 시간 TDD(테스트 주도 개발)에 관심을 가져왔는데요. TDD를 해보려 할 때마다 표현하기 힘든 막막함이 있었어요. 그래서 포기하고 다시 시도해보기를 반복했죠. 하지만, 얼마 전 그 막막함의 원인이 ‘관점’에 있다고 생각되었어요. 그래서 그 이야기를 풀어보려 합니다.

*TDD : Test-driven development

 

TDD, 관점 차이가 있죠

2003년 <다빈치 코드>라는 소설이 세상에 나오기 전까지, 레오나르도 다빈치의 <최후의 만찬> 벽화는 종교화였어요. 사람들은 이 그림을 종교적인 관점으로 보았죠. 하지만 <다빈치 코드>는 이를 전혀 다른 관점으로 해석합니다. 소설, <다빈치 코드>에서 <최후의 만찬>은 더 이상 종교화가 아니라 ‘비밀 메시지’였거든요.

그런데요. TDD에 대해서도 전혀 다른 두 개의 관점이 존재하는 걸 아시나요? 그건 바로 ‘폭포수 개발 방법‘식의 관점과 ‘애자일 개발 방법‘식의 관점이에요. 감을 잡으셨겠지만 우리가 지금까지 TDD를 시도하다 실패했던 이유는 폭포수 개발 방식의 관점에서 TDD를 시도했기 때문이라고 생각하게 되었어요.

좀 더 자세한 이야기를 시작해보도록 할까요?

 

소프트웨어 엔트로피

근본적인 관점 차이는 ‘소프트웨어 엔트로피를 어떻게 관리해 나갈 것인가‘에 대한 전략(또는 개념)에 있어요.

관점 차이를 이야기하기 전에 먼저 ‘소프트웨어 엔트로피’라는 용어에 대해서 말씀드려야겠군요. ‘소프트웨어 엔트로피’는 <실용주의 프로그래머>라는 책에서 나오는데요. 소프트웨어 시스템 내의 ‘무질서’한 정도를 의미해요. ‘열역학 법칙’에 따라 우주의 엔트로피가 지속적으로 증가하게 되는 것처럼, ‘소프트웨어 엔트로피’도 ‘요구사항’의 변화에 따라 증가하게 되는데요. 폭포수 개발 방식과 애자일 개발 방식은 ‘소프트웨어 엔트로피’를 대하는 근본적인 차이를 가지고 있어요.

폭포수 개발 방식은 소프트웨어 엔트로피 요소들을 대부분 관리할 수 있다고 보고 있어요. 그래서 시간을 들여서 소프트웨어를 개발하고 개발된 소프트웨어에 ‘소프트웨어 엔트로피’를 제어하는 책임을 맡기죠. 아래 그림에서 노란 테두리 있는 상자가 소프트웨어 출시 시점이에요. 하지만, 소프트웨어 엔트로피가 증가하는 건 막을 수 없어요. 그러니 유지보수 단계가 필요하게 되겠죠. 그러다가 소프트웨어 엔트로피가 제어하기 어려울 만큼 늘어나게 되면, 소프트웨어를 재개발해야 합니다.

폭포수 개발 방식의 소프트웨어 엔트로피

하지만, 애자일 개발 방식은 ‘소프트웨어 엔트로피 요소’를 한꺼번에 관리하려 들지 않아요. 할 수 있는 만큼씩 관리 영역으로 끌어오고, 소프트웨어가 출시할 수 있는 만큼 진화했을 때 소프트웨어를 출시하게 되죠. 그림으로 그리면 다음과 같겠죠?

애자일 개발 방식의 소프트웨어 엔트로피

폭포수 개발 방법이 댐을 지어서 물을 관리한다면, 애자일 개발은 물길을 내서 관리하려는 것과 같아요.

 

차이의 결과

이런 근본적인 관점 차는 다양한 차이를 만들어 내요. 특히 우리가 관심 있는 테스트에서는 ‘폭포수’는 ‘계약 검증’으로 ‘애자일’은 ‘진화를 위한 발판’으로 보게 되죠.

두 관점에 대해 몇 가지 항목을 정리해 보았어요.

TDD 방식 비교

이미 아시겠지만, TDD는 애자일 개발 방식에서 소프트웨어 엔트로피를 관리하기 위한 방식이에요. 진화를 위한 발판을 마련하는 단계죠. 하지만 폭포수 관점을 가지고 있는 개발자는 TDD를 계약에 대한 검증으로 여겨요. 그러니 TDD가 이해하기 힘들고 적용하기 어렵습니다. 제가 오랜 시간 TDD를 해보겠다고 노력했지만 결국은 포기하게 되었던 이유죠.

 

TDD를 하기 힘든 이유

우리는 애자일 개발 방식에 익숙하지 않아요. 오히려 폭포수 개발 방식의 관점에 익숙하죠. 그러니 TDD를 하려고 해도 폭포수 개발 방식의 관점으로 접근하게 돼요.

계약에 대한 검증으로 TDD를 보기 시작하면, TDD의 다른 요소들은 모두 사라지고 유닛 테스트만 남아요. 이때, 유닛 테스트를 작성하려고 노력하게 되는데요. 시장의 변화가 너무 빠르기 때문에 (그리고 다른 여러 가지 이유로 인해서) ‘계약’사항을 명확히 정하고 시작하는 프로젝트가 별로 없어요. 그러니 테스트를 작성하기 힘들어집니다.

게다가, 검증을 위한 테스트는 코드를 모두 작성하고 넣는 게 자연스러워 그렇게 작업하게 되는데요. 그러면 코드 자체가 테스트하기 어려운 것이 돼버리고 말아요. 또한, 이미 작성한 코드에 대해 검증을 하려 하니 시간이 두 배로 필요하게 됩니다. 그래서, 프로젝트를 시작할 때 주먹을 불끈 쥐고 TDD를 해보겠다 마음먹어도, 결국은 포기하게 되는 거죠.

그런데도 TDD를 놓을 수가 없어요.

 

요구사항 변경의 위험

<소프트웨어 스펙의 모든 것>에서 저자인 김익환 님은 ‘요구사항 오류 수정 비용’이 개발 프로세스 단계마다 다음과 같이 급격하게 증가한다고 밝히고 있어요. (스티브 맥코넬의 <소프트웨어 생존 전략>에도 비슷한 이야기가 나오는데요. 부정확한 요구사항이 50~200배의 비용을 지출하게 한다고 언급합니다.)

요구사항 오류 수정 비용의 증가

김익환 <소프트웨어 스펙의 모든 것>, P. 37

소프트웨어 개발이 끝난 다음 ‘유지보수’ 단계에 요구사항을 수정해야 한다면, 엄청난 수정 비용이 들어가게 되는데요. 이건 프로젝트의 위험 요소로 작용하겠죠. 이런 문제가 발생하지 않게 하려면, 어떻게 해야 할까요? 폭포수 개발 방식에서는 커뮤니케이션을 좀 더 정확하게 하려고 노력해요. 그래서 아주 체계적이고 세세한 문서를 만들어 내게 되죠. 김익환님의 <소프트웨어 스펙의 모든 것>을 읽어보시면 이 부분을 자세히 이해 하실 수 있을 거예요.

 

요구사항은 왜 변경될까요?

첫째, 사용자는 자기가 원하는 게 무엇인지 모를 확률이 높아요.

자동차의 왕이라 불리는 헨리 포드는 “고객이 원하는 걸 만들었다면, 더 빠른 말을 키우고 있었을 것.”이라고 말하기도 했어요. 스티브 잡스도 “사용자가 원하는 걸 만들었다면, 더 빠른 도스 터미널을 만들었겠지만, 나는 맥킨토시를 만들었다.”라고 이야기했고요.

사용자들은 기술을 잘 모르기 때문에 자신들이 원하는 가치를 충족할 방법을 설명하기 어려워요. 그러니, 더 빠른 말이나 더 빠른 도스 터미널이 필요하다고 말하죠. 하지만 정말 원하는 건, 말이 아니라 이동 수단이고, 도스 터미널이 아니라 쉬운 컴퓨터거든요.

둘째, 시장이 빨리 변하고 있어요.

소프트웨어 개발을 시작할 때와 마칠 때의 시장 상황이 다른 경우도 많죠. 그럼, 프로젝트 주관 부서는 요구사항을 바꿀 수밖에 없어요.

셋째, 솔직히 개발자들이 요구사항을 파악하는 것도 한계가 있어요.

앞의 두 가지 이유로 지속해서 변하는 요구사항을 개발자가 항상 파악하고 있기엔 한계가 있을 수밖에 없겠죠. 게다가 개발자들은 변화하는 기술도 따라가고 있어야 해요. 그래서 폭포수 개발 방식은 요구사항 변화를 통제하려고 해요. 통제하지 못하는 요구사항 변화는 위험 요소(리스크)로 보고요. 하지만 애자일은 요구사항 변화가 당연하다고 여기죠. 통제하지 않고 포용하려고 노력해요.

2000년대 초반에 알려지기 시작한 XP(익스트림 프로그래밍)은 애자일 개발 방식 중에 하나인데요. 켄트 백은 XP를 주장하면서 “변화를 포용하라”고 주문해요. 그리고 실천 방법을 제시하는데요. 거기에 TDD가 있어요. ( 강정배 외5  <정보처리기술사 이론과 전략 – 도메인2> , 82페이지 )

다시 말해서, TDD는 애자일 개발 방식이 요구사항 변화를 포용하기 위한 실천법이라 말할 수 있습니다.

 

그럼, TDD는 무엇일까요?

앞에서 말씀드린 것처럼, TDD는 변화를 포용하기 위한 실천법이에요. 그럼, TDD의 교과서라고 볼 수 있는 켄트백의 <테스트 주도 개발>이라는 책을 한번 들춰 볼까요?

예측 가능한 개발 방법이다. 끊임없이 발생할 버그에 대해 걱정하지 않고 일이 언제 마무리 될지 알 수 있다. 코드가 가르쳐주는 모든 교훈을 학습할 기회를 갖게 된다. 처음 생각 나는 대로 후딱 완료해 버리면 두 번째 것, 더 나은 것에 대해 생각할 기회를 잃게 된다. 당신이 만든 소프트웨어는 사용자의 삶을 향상시켜 준다. 동료들이 당신을 존경할 수 있게 해주며, 당신 또한 동료들을 존경할 수 있게 된다  작성하는 동안 기분이 좋다. 결함 밀도를 충분히 감소시킬 수 있다면, 품질보증(QA)을 수동적인 작업에서 능동적인 작업으로 전환할 수 있다. 고약한 예외 상황의 숫자를 충분히 낮출 수 있다면, 프로젝트 매니저가 정확히 추정할 수 있어 고객의 매일의 개발 과정에 참여시킬 수 있다.  기술적 대화의 주제가 충분히 분명해질 수 있다면, 소프트웨어 엔지니어들은 일일 단위 혹은 주 단위의 협력 대신 분 단위로 협력하면서 일할 수 있다.

론 제프리즈가 첨언해준 이야기 에요. 한마디로 TDD를 하면 다 좋아진다고 말하고 있죠. 론 제프리즈의 주장이 사실이라면, 우리는 TDD를 거부할 수 없어요. 이제 그 ‘좋아지는 것’에 대해 회사와 개발자 분야로 나눠서 나열해 볼게요.

 

회사가 TDD가 필요한 이유

첫째, 예측 가능성

훌륭한 소프트웨어 개발을 정의하면 ‘필요로 하는 것(요구사항)을 주어진 시간(일정)에 그리고 주어진 비용(인력)으로 제공하는 것’이라고 해요. (댄 필로네 외1 <Head First Software Development>) 다시 말해, 개발 일정을 지키지 못하면 훌륭한 소프트웨어 개발이라고 볼 수 없는데요. 폭포수 개발 방식은 70% 이상이 일정을 지키지 못한다고 하네요. 게다가 요구사항이 왜곡되어도 복구할 방법이 없는데요. 다음 카툰에서 이를 상징적으로 보여주고 있어요.

*이 그림의 원전은 작자 미상으로 처리하는 경우가 많은데요. 제가 알고 있는 가장 오래된 언급은 J House라는 컨설턴트가 블로그(2008년)에 인용한 거예요. 하지만 블로그에 언급된 Phil Hord의 링크는 현재 끊겨 있습니다. 위 링크는 2011년에 출간된 <소프트웨어 아키텍트가 알아야 할 97가지>라는 책에서 언급하고 있는 링크에요.

사용자가 원했던 건 나무에 타이어를 달아서 그네를 만드는 것이었지만, 설명을 첫 번째 그림처럼 했으며 프로젝트 결과는 예측할 수 없는 상태가 되었어요.

둘째, 비용 절감

로버트 C 마틴은 <클린 아키텍처>에서 실질적인 프로젝트를 예로, 일반적인 프로젝트의 비용이 증가하는 것을 설명하는데요. 개발자 인원이 증가하지만, 그만큼 생산성이 감소하고 코드 라인 당 비용이 증가했음을 보여주고 있어요. (보고서에 따르면, 8차례에 걸쳐서 프로젝트를 진행했는데 마지막에는 비용이 40배나 증가했다고 하네요.) 로버트 C 마틴은 TDD로 이런 비용 증가를 막을 수 있다고 설명해요.

셋째, 개발 시간 감소

역시 <클린 아키텍처>에서 나오는 이야기인데요. TDD를 하면서 개발 시간이 줄었다 합니다. 코딩하고 테스트까지 하니, TDD를 하면 개발 시간이 늘어나지 않을까 싶은데요. 이건 우리가 폭포수 개발 방식의 관점으로 TDD를 보기 때문이에요. 하지만 TDD를 제대로 하면, 코딩과 테스트가 다른 과정이 아님을 알 수 있습니다. 게다가 몇 가지 이유 때문에 TDD로 코딩하면 코딩 속도가 더 빨라지게 돼요. 이는 앞으로 상세히 말씀드릴게요.

와디즈의 5원칙 중에 두 번째는 ‘급변하는 물살을 즐기며 앞서 나갑니다’인데요. ‘변화를 포용’한다는 애자일 익스트림 프로그래밍(XP) 개발 방식과 맥이 닿아 있는 것 같아요. 와디즈 즉, 회사를 위해서 TDD는 정말 중요한 실천법이 아닐까 싶네요.

 

개발자가 TDD가 필요한 이유

2002년 애자일의 ‘플래닝 포커’를 개발한 제임스 그래닝은 <임베디드 C를 위한 TDD>에서 TDD의 장점을 다음과 같이 정리하고 있어요. ‘버그 감소’, ‘디버깅 시간 단축’, ‘부대 효과 side-effect 결함 감소’, ‘거짓말하지 않는 문서들’, ‘마음의 평온’, ‘더 나은 설계’, ‘진척 모니터’, ‘재미와 보상’. 하나씩 살펴볼까요?

1. 버그 감소

(명령형 패러다임으로) 프로그램을 단순하게 표현하면, ‘소프트웨어의 상태를 관리하는 명령의 절차’라고 할 수 있어요. 그러니 상태 관리가 복잡해지면 버그가 생기게 되죠. 따라서, 상태 관리를 단순하게 할 필요가 있는데요. 테스트를 먼저 넣으면, 테스트할 수 있게 하기 위해서 단 하나의 상태를 수정하는 함수를 만들게 되고 코드는 단순하게 됩니다.

2. 디버깅 시간 단축

디버깅 시간이 길어지는 이유는 버그의 위치를 찾기 힘들기 때문이에요. TDD를 하면, 버그가 생기자 마자 버그의 위치를 알 수 있거든요. 당연히 디버깅 시간이 줄어들죠. 그래서 <xUnit 테스트 패턴>의 제라드 메스자로스는 “회귀 테스트가 설치된 코드에는 코드 체크인을 하기 전, 테스트가 버그를 찾아주므로 더 이상 버그가 생기지 않을 것이다”라고 이야기했어요.

3. 화창한 날 테스트 (sunny day test)

<레거시 코드 활용 전략>의 마이클 페더스는 “이들은 여러 가지 특별 조건들을 테스트하지 않고 특정한 동작이 존재하는지를 검증할 뿐이다.”라고 말했는데요. 다시 말해서, 화창한 날 테스트의 의미는 코딩을 다 해놓고, 결과 테스트만 한다는 말입니다. 그렇게 하면 코드 내부에 결함이 있어도 찾을 수 없으니, 나중에 문제가 되겠죠. 화창한 날 테스트를 극복하는 방법은 TDD 뿐이에요.

4. 버그 개수 감소

산드로 만쿠소는 <소프트웨어 장인>에서 “테스트 주도 개발 방법론에 익숙해지고 레거시 코드에 테스트를 만들어 넣은 이후로는 코드 자체를 디버깅해야 하는 상황이 손에 꼽을 만큼 적었다”라고 말하고 있어요.

5. 좋은 코드

켄트 백도 <테스트 주도 개발>에서 “일상적으로 격리된 테스트를 하는 습관을 들이기 전까지는 어떻게 하면 이렇게 응집도를 높이고 결합도를 낮출 수 있는지 정확히 이해할 수 없었다”라고 하고 있죠.

6. 거짓말하지 않는 문서들

프로젝트를 진행하다 보면 문서를 많이 만들게 되는데요. 코드에 대한 문서는 의미가 없는 경우가 많아요. 문서는 잘 바꿀 수 없지만, 코드는 계속 바뀌기 때문이죠. 만약 TDD의 테스트 코드를 문서로 쓴다면, 코드와 함께 업데이트되기 때문에 항상 코드와 문서의 동기(sync)를 맞출 수 있게 됩니다.

7. 더 나은 설계

어떤 설계가 좋은 설계일까요? 그건, 유지보수하기 좋은 구조를 만드는 설계예요. 유지보수하기 좋은 프로그램 구조가 되려면, 테스트 하기 쉬어야 합니다. 그래서 테스트를 먼저 작성하는 TDD는 더 나은 소프트웨어 구조를 제시해주게 되죠.

<애자일 마스터>라는 책에서는 테스트를 설계를 구성하는 수단으로 사용한다고 했고요. <Head First Agile>이라는 책에서는 팀에 책임이 따르는 마지막 순간에 설계에 대한 의사결정을 내리라고 조언하고 있어요. 마지막 순간은 결국 구현 코드를 입력하는 시점이죠. 게다가 <엔터프라이즈급 애자일 방법론>에서는 “테스트하기 어렵다면 주로 설계 개선이 필요하기 때문이다”라고 말하는데요. TDD로 만든 테스트가 설계 구조의 리트머스 시험지 역할을 하는 것을 알 수 있어요.

8. 재미

코딩이 재미있는 이유는 뭘까요? 약 50년 전 프레드릭 브룩스는 <맨먼스 미신>이라는 책에서 코딩의 즐거움을 몇 가지 소개했는데요. 퍼즐을 푸는 즐거움이 있다고 했어요. TDD는 소소한 퍼즐을 푸는 느낌을 주거든요.

그럼 코딩이 재미없는 이유는 뭘까요? 복잡하기 때문이죠. 코드를 작성하는 순간 너무 많은 고민을 한꺼번에 해야 하기 때문에 복잡한데요. 마틴 파울러는 TDD를 하면 공 한 개로 저글링하는 것 같은 느낌이라고 말해요. 그만큼 코드가 단순해진다는 거죠. 게다가 남이 짠 코드를 읽을 때 코드가 복잡해서 파악하기 어려우면, 짜증이 납니다. TDD로 작업해서 단순하고, 테스트도 있는 코드라면 그럴 일은 없을 거예요.

9. 보상

‘1만 시간의 법칙’은 세계적인 저술가 말콤 글래드웰이 <아웃라이어>에서 언급해서 유명해진 용어예요. 전문가가 되어 성공하는 단계에 이르려면 적어도 1만 시간의 시간이 필요하다는 말인데요. 말콤 글래드웰이 이런 이야기를 하게 되었던 심리학 보고서를 작성한 사람은 안데르슨 에릭슨이지요. 에릭슨은 <1만 시간의 재발견>에서 1만 시간을 보내는 것보다 중요한 건, ‘목적의식 있는 연습’, ‘의식적인 노력’으로 1만 시간을 채우는 것이라고 이야기하고 있어요. 피아노를 접하고 1만 시간을 보낸 것이 아니라, 피아노 레슨을 받고 연습하며 1만 시간을 보내야 피아니스트가 되는 원리와 같아요.

그럼 개발자에게 ‘목적의식 있는 연습’, ‘의식적인 노력’이 무얼까요? 유지보수하기 쉬운 구조를 가지는 프로그램을 작성하는 연습 아닐까요? TDD를 하는 동안 개발자는 좋은 코드를 작성하는 ‘의식적인 노력’을 하게 될 테니, TDD를 한다는 것은 결국은 진정한 코딩 전문가가 되어가는 1만 시간을 채워가는 것이라 볼 수 있어요.

그러니 TDD를 해야겠죠.

 

TDD 시작하기

테스트 주도 개발(TDD)은 위 그림과 같은 사이클로 작동해요. 1) 먼저 실패하는 테스트를 만들고 2) 테스트가 성공하게 하는 코드를 구현하고 3) 리팩토링 하는 거죠.

TDD에 대해 구글링하면 금방 접하는 이야기인데요. 감이 잘 오지 않죠. 뭔가 더 깊은 설명이 듣고 싶어집니다.

저는 2019년 말 와디즈에 합류했어요. TDD에 대해 이야기 하고 싶었습니다. 더 깊은 이해를 할 수 있는 설명을 찾고 있었는데요. 2019년 12월 로버트 C 마틴이 트윗한 걸 우연히 보게 되었어요. (로버트 C 마틴은 클린 코드, 클린 코드 등의 클린 시리즈 책들로 유명한 분이죠) 트윗 내용을 보며, TDD가 무언지 좀 더 깊이 이해할 수 있었어요. 요약하면 아래와 같습니다.

  • 실패하는 테스트 없이 코드를 넣지 않기

  • 실패가 생기면 테스트 작성을 멈추기

  • 실패한 테스트를 넘기면 즉시 코딩 멈추기

  • 리팩토링 이후 반복

  • 한 사이클은 10~60초

저는 마지막 항목에서 뒤통수를 세게 맞는 느낌을 받았거든요. 10초 만에 실패 테스트와 성공 코드를 만들고 리팩토링을 할 수 있다면, 그건 지금까지 제가 생각해 온 테스트가 아니에요. 다시 말해 ‘계약을 검증하는 테스트’를 한다면, 10초 안에 한 사이클을 돌리는 건 불가능합니다.

 

TDD 준비물

10초~60초에 한 사이클을 돌리는 TDD를 하려면 무얼 준비해야 할까요?

첫째, 개발 프로세스와 테스트를 바라보는 관점이 바뀌어야 해요.

앞에서 이야기했던 것처럼, TDD는 단순한 유닛테스트가 아니에요.

둘째, 빠르게 테스트와 코드를 입력할 수 있는 도구가 필요해요.

물론 우리가 손으로 할 수도 있어요. 하지만 코드 템플릿을 쓸 수 있으면 더 빠르겠죠? 와디즈 FE개발팀은 VSCode를 많이 써서, VSCode의 extension을 추가할 수 있을까 찾아보았는데요. 의외로 extension에 대한 문서가 잘 되어 있어서, 조금만 공부하면 단순한 extension은 추가하기 쉬울 것 같더라고요. 그래서 한번 만들어봤어요.

extension 추가

이름만 정해주면 React 컴포넌트 디렉토리랑 파일을 만들어주는 extension이에요. 꼭 만들 필요는 없어요. 공개된 extension이 많거든요.

셋째, 테스트에 watch를 걸고, 계속해서 테스트를 보여줄 콘솔이 필요해요.

테스트나 코드를 입력하고 저장하면 바로 콘솔에 보이는 거죠. 대부분 React 환경들은 jest를 기반으로 test watch가 되게 되어 있어서, iTerm 같은 콘솔에 띄우기만 하면 되었어요. (코드를 저장할 때마다 한쪽 화면에서 테스트가 휘리릭 도는 걸 상상해보세요…)

넷째, 테스트 구문에 익숙해야 해요.

빠르게 테스트를 입력해야 하는데 뭐라고 넣어야 할지 모른다면, 10~60초 안에 한 사이클을 끝내기 어렵더군요. 게다가 코딩 작업은 항상 일정이 있잖아요. TDD 하겠다고 테스트 구문을 찾아보고 입력하면서 차근차근 하는 건 무리가 있었어요.

 

시작하기 전에

꽤 여러 책을 보면서 찾아보았는데요. 다음 3가지는 마음에 담고 시작하는 게 좋아요.

  • 최소 단위 테스트 : 테스트는 가장 작게 넣어야 해요.

  • 테스트 대상의 고립 : 테스트 대상은 다른 UI 컴포넌트에 의존성이 없어야 해요.

  • 테스트의 문서 역할 : 다른 문서가 없어도 테스트를 읽으면 우리가 작성한 코드를 다른 개발자가 바로 쓸 수 있도록 배려해야 하죠.

이번 프로젝트를 진행하면서, TDD를 함께 해보려고 마음을 먹었는데요. 테스트 구문, 명령이 익숙하지 않다 보니, 테스트 구문을 찾아보는 시간이 많이 필요했어요. 그래서 완전히 TDD를 하려면 일정 안에 일을 마칠 수 없겠다고 판단했죠. 그럼 뭘 해야 다음 작업 때는 TDD를 더 완벽하게 할 수 있게 될지 고민했는데요. ‘테스트 대상 고립’하는 방법을 많이 찾아 두면 좋겠더군요. 그래서 ‘테스트 대상 고립’에 대해 먼저 찾아보았습니다.

UI 컴포넌트가 의존성이 생기는 방향은 크게 3가지죠. 부모 컴포넌트, 자식 컴포넌트 그리고 훅(hook).

첫째, 부모 컴포넌트 의존성 해결

전 영역에서 영향을 미치는 부모 컴포넌트는 아마 redux와 react-query가 아닐까 싶은데요. 와디즈 FE개발팀은 redux를 쓰면서 react-query로 넘어가는 과도기예요. 그래서 다음과 같은 테스트용 부모 컴포넌트를 만들어 보았어요.

<ReactRedux.Provider store={store}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</ReactRedux.Provider>

react-router도 함께 넣을까 했는데요. 이 녀석을 사용하지 않는 UI 컴포넌트도 꽤 있고, 쓰는 곳에서 테스트하고 싶은 내용이 조금씩 차이가 있어서 넣지 않았어요.

그다음 고려해야 할 상위 컴포넌트는 Form 관련 컴포넌트였어요. 와디즈 FE개발팀은 react-hook-form을 많이 쓰는데요. react-hook-form의 form은 자식 컴포넌트들이 폼에 입력될 값을 나눠서 관리할 수 있도록 구성되어 있어요. 그래서 form에 의존성이 있는 컴포넌트들도 테스트할 방법이 필요했죠. 테스트를 넣다 보니, 결국 defaultValues 값을 테스트 의도에 맞게 넣을 수만 있으면 테스트 하기 수월해진다는 걸 알게 되었어요. 그래서 다음과 같은 코드로 테스트 대상 컴포넌트를 감싸게 했죠.

const methods = useForm({ defaultValues });
return (
<FormProvider {...methods}>
<form id={formId} onSubmit={methods.handleSubmit(jest.fn())}>
{children}
</form>
</FormProvider>
);

이 정도 준비하면 대부분 부모 컴포넌트 쪽 의존성은 해결되더군요.

둘째, 자식 컴포넌트 의존성 해결

자식 컴포넌트를 그대로 import해서 쓰면 자식 컴포넌트 내 의존성에 엮여서 테스트 코드가 복잡해지는 경우가 생기더군요. 그래서 자식 컴포넌트들은 jest.mock으로 만들었어요.

jest.mock('./components/CategorySelect', () => ({ CategorySelect: () => <></> }));

이렇게 코드를 넣고 테스트 대상 컴포넌트를 import하면 테스트 대상 컴포넌트 내에서 ‘./components/CategorySelect’를 import할 때 jest.mock 객체를 가져가게 되어 있어요. 자식 컴포넌트가 mock 객체가 되기 때문에, 테스트 대상 컴포넌트가 자식 컴포넌트와 연관성 있는 부분(예를 들어 props를 바꾼다든지, callback 호출을 감지한다든지 하는… )을 테스트하기 수월해져요.

셋째, 훅 의존성

리액트 훅 자체에 대한 설명은 하지 않을게요. 저는 훅을 테스트하는 방법이 있을까 해서 찾아봤는데요. 당연히 있더군요.

import { renderHook } from '@testing-library/react-hooks';

renderHook에 hook을 호출할 함수와 hook이 호출될 wrapper를 넣어주면 잘 동작했어요. renderHook을 사용하면 react-query가 호출할 API 함수들이나 mutation을 테스트하는 데도 유용했고요. API 호출은 저희 팀 프로님들이 MSW를 사용하고 계시고, 여러 예시도 MSW를 많이 사용해 저도 MSW를 사용해보기도 했어요.

 

어떻게 시작할까요?

테스트할 UI 컴포넌트를 고립할 방법을 찾았으니, 이제 테스트를 넣는 방법만 찾으면 됩니다. 어떻게 하면, 테스트를 넣으면서 코딩할 수 있을까요? 이 부분에서 ‘아웃라인 주석’이 꽤 유효하다는 걸 깨달았어요.

다음 그림은 켄트 백의 <테스트 주도 개발> 29장의 ‘테스트에 대한 짧은 아웃라인’ 예시입니다.

켄트 백의 <테스트 주도 개발> 29장의 '테스트에 대한 짧은 아웃라인' 예시

예전에 C언어로 코딩할 때, 가장 일반적인 코딩 방법은 프로그래밍하고 싶은 주요 기능의 함수를 더미로 만드는 것이었어요. 뭘 해야 할지 적어두면, 어떻게 나누고 구현해 가야 할지 보이거든요. 그러면 다시 더 나누고 …. 이렇게 하향식으로 구조를 나누어갔죠. 그러니, 뭘 해야 하는지 적어두는 ‘테스트의 짧은 아웃라인’은 테스트를 시작하는 출발점이 될 것 같아요. 그럼 코딩은 다음과 같은 순서로 하게 되겠습니다.

  1. 코드 생성기(vscode extension)로 기본 코드 생성

  2. 테스트 파일을 열어서, 만들 컴포넌트의 기능을 주석(짧은 아웃라인)으로 정리

  3. 주석을 하나씩 지우면서 실패하는 테스트 작성

  4. 테스트가 성공하도록 코드 입력

  5. 리팩토링

  6. 다시 3번으로 가서 반복

3~5번까지 10~60초 안에 해결하는 능력을 기른다면 우리는 진정한 TDD의 맛을 보게 될 것 같아요 😀

 

이제 어떻게 발전시켜 나갈까요?

CI/CD는 빌드 시스템이 아니에요

폭포수 개발 방식 관점이 낳은 다른 오해는 CI/CD를 ‘빌드 시스템’으로 보는 거예요. CI는 Continuous Integration의 약자로 ‘지속적 통합’을 말해요. 폭포수 개발 방식의 관점으로 보면 통합은 모든 작업을 마무리 하는 과정에서 조심스럽게 이루어지는 게 당연해요. 하지만 애자일 개발 방식 관점에서는 계속 통합해서 변화에 적응해 나가야 합니다.

<이펙티브 엔지니어>라는 책에서는 더 효율적인 개발 문화에 대해서 이야기하는데요. 여기에 페이스북의 CI에 대한 이야기가 나와요. 1,000명이 넘는 개발자가 하루 2번 코드를 배포할 수 있는 시스템을 갖추고 있다고 하네요. 더 나아가 ‘웰스프론트’라는 회사에 대해서도 소개하는데요. 10억 달러 이상의 고객 자산을 관리하는 회사인데도, 2014년 6월을 기준으로 매일 30회 이상 코드를 배포했다고 하더군요. 만약 제대로 TDD를 한다면, 테스트 코드 커버리지가 90%를 넘게 될 테고, 그런 상황에서 e2e 테스트까지 잘 갖춰져 있다면, 코드의 작은 변화는 자동화 테스트만 거쳐서 배포하는 게 가능하겠죠. 그러면, 하루에 30회 코드를 배포한다고 해도 안정적인 서비스를 제공할 수 있게 될 테고요.

 

뭘 할 수 있게 될까요?

엘리 골드렛은 <더 골>에서 ‘제약이론’이라는 걸 주장했어요. 생산 시스템 전체의 생산성은 사실상 ‘병목구간’에서 정해지는 것이기 때문에, 이를 개선하려면 그 구간을 찾아 개선해야 한다는 거죠. 이 논리는 사용자의 요구사항이 개발자들에게 전달되어 구현되고 배포되는 일련의 과정에도 적용해 볼 수 있어요. 시장의 변화가 사용자의 요구사항이 되어 개발자들에게 전달되면, 시장 변화 속도에 맞춰 배포까지 진행되어야 안정적인 시스템이라고 볼 수 있는데요. 한 구간에서 이를 해결하지 못하면 병목구간이라고 볼 수 있습니다.

여기서 ‘폭포수 개발 방식’을 한번 짚어 봅시다. 폭포수 개발 방식은 프로세스를 흐름으로 보지 않고 단계로 봅니다. 그래서 각 단계가 완료되기까지 프로세스가 멈추게 되는데요. 다시 말해, 폭포수 개발 프로세스를 모두 마칠 때까지 요구사항 변화가 생긴다면, 폭포수 개발 프로세스는 병목일 수밖에 없어요.

요구사항 변화를 빠르게 흡수할 수 있는 회사라면, 그만큼 빠르게 진화하고 더 나은 서비스를 제공할 수 있게 될 거예요. 그 대표적인 예가 케빈 시스트롬의 ‘버번’이 아닐까 싶어요. 2010년 케빈 시스트롬은 위치 공유 앱을 개발했어요. 스마트폰을 사용해서 특정 장소의 사진을 찍고 리뷰를 등록하는 앱이었죠. 하지만 이미 ‘포스퀘어’라는 위치 공유 앱이 나온 상황에서 사람들의 반응은 그리 좋지 않았어요. 그러던 중 ‘버번’ 개발자들은 특이한 점을 발견합니다. 사용자들이 버번의 위치 공유 기능은 꺼 놓고, 부가 기능인 사진 기능만 사용하고 있다는 거죠. 그래서, 버번팀은 앱을 바꾸기로 마음 먹었어요. 위치 공유 기능을 없애고 사진 기능만 남겨서 다시 배포했죠. 앱 이름도 “인스타그램”으로 바꾸고요. 이 회사는 2년 만에 페이스북에 1조 2천억 원으로 인수됐어요.

빠른 시장 변화에 적응하려면 그만큼 빠른 진화 능력이 필요해요. TDD는 개발팀이 그걸 할 수 있게 하는 가장 원초적인 기술이죠.

 

마치며

와디즈 FE개발팀은 자발적으로 기술 세미나를 진행합니다. 제가 이 글에서 설명한 TDD에 대한 이야기도 이 세미나 시간에 나누었는데요. FE개발팀 모든 프로님이 관심을 주셨어요. 그 덕분에 저도 이번 프로젝트부터는 적극적으로 시도해보고, TDD를 꼭 몸에 익히겠다고 마음먹을 수 있었죠.

TDD에 대한 관점을 바꾸고, 10~60초에 한 사이클씩 완수할 만큼 연습해 나간다면, 점점 더 better한 개발자가 될 수 있을 것 같아요. 저 뿐만 아니라 FE팀 프로님들과 함께 이렇게 조금씩 바꾸어 나간다면, 와디즈 개발팀 전체가 뛰어난 개발 문화를 가진 조직으로 나아갈 거라 기대도 되고요.

결국은 와디즈가 ‘급변하는 물살을 즐기며 앞서’ 나가는데 초석이 되지 않을까 싶네요!

궁금한 내용이 남아 있나요? 👀

개발자가 일하는 법 보러가기 👉 클릭

👇 태그를 클릭하면 같은 키워드의 글을 모아볼 수 있어요.

기술 이야기

기술 이야기

기술 조직

누구나 도전하고, 새로움을 경험할 수 있도록, 꼭 필요한 서비스를 만듭니다.