8 분 소요

요즘 개발자에게 테스트의 작성은 어찌보면 당연해 졌다. 많은 회사들이 테스트 작성 역량을 필요로 해서 테스트를 작성하는 것이 당연해 졌는지, 테스트를 작성하지 않아서 겪었던 안좋은 경험들이 쌓여서 테스트를 작성하게 되었는지, 다들 테스트를 작성해야한다고 말하니까 자신도 함께 작성해야 한다고 생각하게 되었는지는 잘 모르겠다. 어찌되었든 테스트를 작성해야한다는 인식은 참 좋은 일이라고 생각되고 앞으로도 쭉 이러한 기조가 계속되었으면 하는 바램이다.

이렇게 당연하게 생각하는 테스트의 작성은 현업에서 도입되고 있을까? 모든 회사를 겪어보진 않아서 일반화 하긴 힘들지만 적어도 내가 겪었거나 지인이 재직중인 회사에서는 “그렇다”라고 대답하기 힘들었다. 심지어 개발 컨퍼런스나 테크 세미나를 열어 테스트의 중요성을 강조하고 있는 회사에서 조차 말이다. (여기서 “아니다”라고 말하지 않은 이유는 회사내에서 팀별로 테스트를 작성하는 팀이 있기도 하고 작성하고 있지 않은 팀이 있기도 하기 때문이다.) 그리고 테스트를 작성하고 있더라도 테스트가 관리되고 있지 않아 힘들어하는 팀도 존재했다.

2022년 프로그래머스 설문조사에 따르면 팀에 도입되어 있는 개발문화 중 가장 낮은 비율을 차지한 것이 바로 TDD이다.

이미 도입되어있는 개발문화 비교

그럼 왜 실무에서는 이렇게 중요한 테스트의 작성이 잘 이루어지지 않고 있는걸까? 이 글에서는 그 원인(또는 현상)들을 나열해 보고 이것들을 어떻게 해결해서 팀에 테스트 문화를 도입 시킬 수 있을지 이야기 해 보고자 한다.

Test 코드 작성이 익숙하지 않다.

코딩은 많은 연습이 필요하다. 현업 개발자가 되기 위해서 학교에서 혹은 학원에서 어플리케이션을 만들기 위해 많은 시간을 코딩에 할애한다. 테스트도 코딩의 일부분이다. 많은 연습이 필요하고 더 좋은 테스트를 작성하기 위해 고민하고 또 고민해야한다.

테스트의 필요성은 전체 개발의 역사에 비하면 많이 짧다. 국내에 테스트 작성의 필요성이 대두된 시기도 불과 몇년밖에 되지 않는다고 생각한다. 그러다보니 테스트 작성에 익숙한 개발자가 전체 개발자에 비해 적을 수 밖에 없다. 테스트 작성에 익숙하지 않으니 테스트 작성이 어렵게 느껴지고 시간이 많이 소요된다. 그러다보면 전체 작업 일정에 영향을 미치게 되므로 테스트를 작성할 시간이 없다는 소리가 나오게 되는 것이다. 일정에 영향을 미치게 되면 결과는 뻔하지 않을까, 테스트 작성을 포기할 수 밖에.

그럼 테스트 작성이 익숙하게 되려면 어떻게 하면 좋을까? 달리 방법이 없다. 연습을 해야지. 팀내에 테스트 작성이 익숙한 팀원이 있다면 함께 스터디를 하던 혼자서 연습해보고 코드를 보여주며 피드백을 받아보는 등의 활동을 하면 좋을 것 같다. 만약 팀내에 테스트 코드 작성이 익숙한 팀원이 없다면 모두가 익숙해지기 위한 스터디를 열심히 하거나 외부의 도움을 받을 수 밖에 없을 것이다.

여기서 중요한 부분은 “모두”이다. 특정인만 테스트 작성에 익숙하거나 도입을 위한 노력을 한다면 테스트를 작성하는 개발문화는 만들어갈 수 없다.

테스트를 작성하는 방법을 설명하는 책은 일반적이다.

테스트 주도 개발과 같이 테스트에 대한 내용이 담긴 책에서는 특정 프레임워크에 종속적인 내용을 담기가 힘들다. 이 책들의 목적은 테스트를 왜 작성해야하는 지와 테스트를 설계해가고 작성해나가는 일련의 과정에 목적을 두기 때문에다. 그렇다보니 일반적인 클래스나 함수에 대한 테스트 작성 방법을 보여줄 수 밖에 없다.

실무에서는 프레임워크를 사용하거나 라이브러리를 사용하는 경우가 많다. 그러다보니 책을 읽으면서 혹은 읽고나서 실무에 테스트를 어떻게 작성해야할지 막막한 경우가 많다. 어떻게 작성해야할지 잘 모르니 연습하기가 어려워지고 잘안되니 재미가 없어지고 그러다보니 앞에서 말한 실무 도입이 되지 않는 상황이 생기게 되는 것이다.

프레임워크나 라이브러리를 사용하는 경우 해당 프레임워크나 라이브러리에서 지원해주는 테스팅 가이드 문서를 참고하자. Spring Testing이나 React Testing Library를 예로 들 수 있겠다. 테스트에 대한 내용이 담긴 책에서는 어떻게하면 더 나은 테스트를 설계하고 작성해 가는지를 배우는데 초점을 맞추고 실제로 필요한 테스트는 각 라이브러리에서 배워서 연습하는 것이다.

TDD가 익숙하지 않다.

테스트 주도 개발을 읽어보았다면 무슨 말인지 이해할 수 있을 것이다. 필자도 읽어보았지만 솔직히 어려웠다. 지금도 실무에서 TDD를 실천하려고 노력하고 있지만 솔직히 이 책에서 말하는 방법대로는 실천하진 못하고 있다.

TDD로 개발하는 것이 어려운가? 그렇다면 TLD(Test Last Development)로(Test After Development와 같이 다른 단어들이 많이 있으니 의도만 가져가자) 작성해보자. TDD에서 추구하는 개발 방법은 이전에 테스트를 작성하지 않아왔던 개발자들에게는 참으로 생소한 작업이다. 테스트를 작성하는 것도 어려운데 개발 방법까지 바꾸려고 하는 것은 지나친 욕심일 수 있다. 그러므로 익숙한 방법대로 운영코드를 작성한 후 테스트를 작성해서 내가 작성한 운영코드가 원하는대로 동작하고 있는지 검즘해보자. 이 방법이 한결 더 쉽게 느껴질 것이다. 그리고 점점 익숙해져서 테스트의 작성에 능숙해 진다면 TDD로 개발하는 것으로 방법을 바꿔보자.

여기서 짚고 넘어가고 싶은 부분이 있다. TDD와 TLD는 결이 조금 다르다. TDD는 테스트를 통해서 우리가 어떤 기능을 만들어야 하는지 생각하고 작성할 코드를 디자인하고 완성해가도록 해준다. 즉 테스트가 코딩을 주도하는 것이다. 하지만 TLD는 그렇지 않다. 하지만 앞에서 TDD가 어렵다면 TLD로 테스트를 작성해보라고 한 이유는 “테스트 코드 작성”에만 초점을 맞추었기 때문이다.

레거시 코드에는 테스트 코드가 존재하지 않는다면?

새로운 코드만 작성할 수 있으면 얼마나 좋을까? 하지만 회사에 입사하면 레거시 코드부터 이해하기 위해 노력해야하고 요구사항은 레거시 코드를 변경하는 것이 대부분이고 장애는 레거시 코드에서 가장 많이 발생한다. 레거시 코드에 테스트 코드가 함께 있다면 얼마나 행복하겠냐만은 현실은 그렇게 아름답지 않다. 서두에 말했다시피 테스트에 대한 중요성이 부각되기 시작한지는 얼마 되지 않았다. 그래서 높은 확률로 레거시 코드에는 테스트가 없을 것이다.

여기서 우리는 선택을 해야한다. 하나는 레거시 코드에는 테스트를 넣지 않고 새롭게 작성하는 코드에만 테스트를 작성하는 것이고 다른 하나는 레거시 코드에 테스트를 작성하는 것이다.

레거시 코드에 테스트를 넣지 않고 새롭게 작성하는 코드에만 테스트를 작성하는 것은 쉽다. 높은 확율로 레거시 코드는 테스트 하기 어렵도록 작성되어 있을 것이기 때문에 테스트를 넣기도 쉽지 않을 것이니 포기하면 스트레스도 덜받고 내가 새롭게 작성하는 코드는 테스트 하기 쉽도록 설계할 수 있을 것이니 테스트도 쉬울 것이다. 그런데 앞에서도 말했지만 레거시 코드에 기능을 추가하거나 변경해달라는 요구사항이 많은 경우에는 어떻게 할 것인가. 레거시 코드에는 더이상 테스트를 작성하지 않기로 했으니 그럼 앞으로도 계속해서 테스트를 작성하지 않을 것인가?

레거시 코드에는 테스트를 넣지 않는 방법에 문제점을 느꼈다면 다른 방법인 레거시 코드에 테스트를 작성해보는 선택을 해보자. 물론 어렵다. 앞서 말한 테스트 작성에 익숙해져 있어야 한다는 전제가 있기도 하다. 어떻게 테스트를 작성할 수 있을까? 내가 생각하기에 가장 합리적인 방법을 소개해 본다.

  1. 기존코드의 현재 동작하는 기능을 성공시키는 테스트를 작성하자. 여기서 테스트를 작성하기 어려운 포인트는 바로 의존성으로 인해 테스트 작성 시 셋업 코드를 작성하는 것이다. 만약 셋업 코드가 너무 많아 유닛테스트가 어렵다면 기능테스트 또는 End-to-End 테스트로 테스트를 작성하자. API 서버를 예로 들자면 HTTP로 API를 호출하고 해당 API가 반환하는 JSON을 검증하는 것이다.

  2. 요구사항에 맞게 검증하는 코드를 변경하자. 예를 들어 API가 반환하는 json이 기존에 {"a": true} 였다가 {"a": false} 로 변경해야 한다면 검증하는 코드를 이와 같이 변경해주면 된다.

  3. 테스트가 성공하도록 운영코드를 변경하자. 테스트를 작성했다면 운영코드의 변경은 쉽다. 성공할때까지 코드를 변경하면 된다.

  4. 가능하다면 좀 더 테스트하기 쉬운 구조로 리펙터링까지 해보자. 테스트가 성공했다고 여기서 끝내버리면 아쉽다. 내 동료가 내가 겪은 고통을 더이상 겪지 않도록 하기 위해서 좀더 테스트하기 쉬운 구조로 리펙터링을 해보자. 다만 여기에 너무 많은 시간을 투자하지는 말자. 리펙터링은 작게 자주하는 것이 좋다. 전반적인 코드 변경을 유발하는 리펙터링을 할 수 밖에 없다면 과감하게 포기하는 것도 나쁘지 않다라고 생각한다.


앞서 언급한 내용들은 테스트 작성에 익숙하지 않거나 도입하지 않은 팀에 대한 내용이었다면 여기서부터는 어느정도 테스트 작성에 익숙해지고 이미 테스트를 작성하고 있는 팀에 대한 내용이다.

테스트도 자산이다.

테스트도 자산이다. 즉, 다른 코드(운영 코드)와 마찬가지로 관리되어야 하고 더 나은 코드를 작성하기 위한 노력을 아끼지 않아야 한다. 테스트가 작성되고 있지만 관리가 제대로 되지 않아 테스트에 대한 가독성이 아주 좋지 않거나 조금만 코드를 건드려도 테스트가 깨져서 테스트 코드가 오히려 기능 개발에 발목을 잡는 경우를 본적이 있다. 만약 테스트가 익숙하지 않은 개발자가 팀에 합류해 이런 상황을 겪는다면 테스트에 대한 안좋은 인식과 함께 앞으로 테스트 코드를 작성하지 않으려는 모습도 보게 될 수도 있다. 그러므로 팀원 모두가 테스트도 우리가 만들 어플리케이션의 자산 중 일부라고 생각하고 언제나 부채를 함께 청산하기 위한 노력을 기울여야 할것이다.

좋은 테스트 작성은 어렵다.

좋은 테스트는 무엇일까? 나는 기능이 변경되었을 때 빠른 피드백을 받을 수 있도록 해주면서 “좋은 코드”와 같이 변경하기 쉬운 코드가 좋은 테스트라고 생각한다.

TDD는 죽었다 라는 한때 엄청 핫했던 글이 있다. 제목이 좀 그렇긴 한데 내용을 읽어보면 더 나은 테스트를 작성해야한다는 작성자의 견해가 드러나있다. 이렇듯 테스트를 작성하는 방법은 사람마다 다르고 더 좋은 테스트라는 견해도 사람마다 다르다.

사람마다 견해가 다르다는 것은 정답이 딱히 없다는 것으로 느껴진다. 그래서 자신이 가진 좋은 테스트를 작성하기 위한 전략이 필요하다.

최근에 우리 팀에서는 유닛테스트에서 요구사항에 대한 모든 케이스를 테스트 케이스로 작성하고 통합테스트에서는 각 유닛들이 잘 연결되었는지만 테스트 하는 식으로 해서 테스트의 중복을 줄이면서 커버리지를 만족시키려는 전략을 가져가고 있다. 통합테스트에서 요구사항에 대한 모든 케이스를 작성한다면 테스트를 위한 셋업이 유닛 테스트에 비해 많이 필요하기 때문에 어쩔 수 없이 많은 중복이 생길 수 밖에 없고 테스트가 장황해 지기 때문이다.

물론 이 방법도 완벽한 전략이라고 하기 힘들다. 유닛테스트가 많을 수록 mock 테스트가 증가하게 되고 세부사항들을 테스트 하고 있기에 구조 변경 등의 리펙터링 작업 시 테스트의 변경이 많아진다는 단점들을 가지고 있다.

안티 패턴을 잘 숙지하자.

Wikipedia의 Test-driven development를 보면 Practices to avoid, or "anti-patterns" 섹션이 있다. 테스트를 작성할 때 안티 패턴들을 소개하고 이러한 안티 패턴으로 개발하고 있지 않은지 항상 경계하고 더 나은 테스트를 작성하기 위해 노력했으면 하는 바램에서 적혀있는 글이라 생각한다. 여기에 소개한 항목들을 소개해 보겠다. (내용을 그대로 번역한 것이 아니라 나름 해석해서 적어 보았다.)

  • 각각의 테스트가 다른 테스트에 영향이 미치는 테스트
  • 각각의 테스트가 서로 종속성을 가지는 테스트
  • 상호 의존적인 테스트
  • 정확한 실행 타이밍 또는 성능 테스트
  • 필요이상으로 검사하는 테스트
  • 구현의 세부정보를 테스트
  • 느린 테스트

테스트 안티페턴에 대한 글은 구글링을 해본다면 많이 찾아볼 수 있다. 아마 대부분의 예기가 위에서 언급한 내용에서 크게 벗어나지 않을 것이다.

테스트에 잠식되지 말자.

가끔 테스트 커버리지에 집착하는 개발자를 볼때가 있다. 테스트 커버리지 100%를 위하여 코드를 극한까지 내모는 모습을 보면 안타까운 마음뿐이다. 그 개발자는 커버리지 100%를 달성했다고 자랑할게 아니라 동료 개발자들에게 미안하다고 커피를 사줘야 할것이다.

테스트 커버리지가 높은 제품은 개발자들에게 높은 안정감을 선사한다. 그렇지만 모든것을 테스트 코드로 녹이려는 시도는 득보다 실이 클 수도 있다. 만약 테스트 하기 아주 까다롭거나 특정 상태(예를 들어 OS환경, 인프라 등등)에 종속적이어서 깨지기 쉬운 테스트는 작성하지 않는게 나을 수 있다. 여기서 아낀 비용과 시간을 좀 더 중요한 도메인의 비지니스 로직에 투자헤서 좀더 안정적인 테스트를 작성하기 위한 노력을 하는게 제품을 위해서도 더 나은 선택이다.

망치를 든 사람에겐 모든 문제가 못으로 보인다. 테스트 작성에 익숙해질때 쯤 내가 모든 문제를 테스트 코드로 녹이려는 시도를 하고 있진 않는지 경계하자.

테스트는 버그를 찾기 위한 것이 아니다.

테스트는 버그를 찾기 위한 것이 아니다.

실용주의 프로그래머에서 언급된 내용이다. 테스트의 주요한 이득이 테스트를 실행할 때가 아니라 테스트에 대해 생각하고, 테스트를 작성할 때 생긴다는 것이다.

그래서 나는 팀에서 현재는 코드 작성 후 테스트를 작성하고 있지만 종국에는 테스트 주도 개발을 하는 방향으로 가야한다고 생각한다. 갑자기 짜잔하고 테스트 주도 개발을 할 순 없다. 처음부터 완벽하게 테스트 주도 개발을 할 수도 없다. 아니 어쩌면 영원히 테스트 주도 개발을 이상적으로 할 수 없을지도 모르겠다. 하지만 그럼에도 불구하고 테스트가 코딩을 주도하도록 계속해서 연습하면 좋겠다.

테스트 문화가 필요하다.

자동화된 테스트, TDD는 특정 팀원만 도입하고 열심히 한다고 되는게 아니다. 팀원 모두의 합의와 노력이 필요하다. 그렇지 않다면 팀내에 테스트 도입은 일장춘몽이 될 수 밖에 없다. 우리가 테스트를 왜 도입하려 했는지 다시한번 생각해보자. 테스트가 없으면 우리의 제품은 고객이 테스트하게 될 것이다. CS가 하루가 멀다하고 유입될 것이고 새롭고 멋진 기능 개발보다 고통스러운 버그 수정 작업이 하루 일과의 대부분이 될 것이다.

TDD로 개발하는것이 팀내에서 너무나도 당연해서 새로운 팀원이 합류하더라도 자연스럽게 TDD에 익숙해지는 그런 팀 문화를 만들어가자.

마치며

2021년 프로그래머스 설문조사를 보면 가장 도입하고 싶은 개발문화가 바로 자동화된 테스트이고 TDD도 상위권에 속해있다.

도입하고 싶은 개발문화

반면 TDD는 도입 후 후회하는 개발문화 중 상위권해 속해 있다.

후회하는 개발문화

나는 이러한 결과가 어찌보면 당연하다고 생각한다. 이상과 현실에서 오는 괴리를 잘 인지하고 그 괴리를 줄이기 위한 노력을 기울이지 않으면 테스트의 작성은 하지 않게 되거나 작성하더라도 고통스러운 작업이 될 것이다.

실용주의 프로그래머에서 적혀있는 인상깊은 구절을 적으면서 이 글을 마무리 하려고 한다.

여러분이 테스트를 써야 할까? 그렇다. 하지만 테스트 작성 경험이 30년 정도 쌓였다면 테스트가 어떤 면에서 도움이 되는지 직접 실험을 해 봐도 좋을 것이다.

우리팀의 테스트 작성 경험이 30년을 넘어 새로운 테스트 개념을 도입하는 그날까지.