3년간의 TDD 인생 회고

1막

어두운 방에 있는 영사기에서 빛이 새어나오는 사진

나는 글을 쓸 때, 우선 대충 글의 전체 구조를 완성한 후에 아주 여러 번 마음에 들 때까지 고쳐서 최종본을 완성하는 방식을 선호한다. 주제에 매력을 더 많이 느낄수록 공을 더 많이 들인다. 언젠가 한 번은 글을 발행하고 난 후에, 수정 발행을 무려 80번 넘게 한 적도 있었다. 코드를 작성할 때도 비슷하다. 대충 구현해서 의도한 결과를 빨리 만들고, 마음에 들 때까지 수정하기를 반복한다. 언제부터 이런 습관이 생겼는지는 잘 모르겠다. 신입 시절부터 그랬던 것 같은데, 이 과정을 리팩터링이라고 부른다는 사실을 이해한 건 2년 차쯤이다.

리팩터링을 한 후에는 당연히 제품에 문제가 없는지 테스트를 통해 확인한다. 프로젝트 초반에는 테스트하기가 쉽다. 구현한 게 적으니까. 코드량이 많아지면서 운동장 사정이 변한다. 작은 수정에도 얽히는 부분이 많다. 수정하고 나서 확인해야 할 지점이 너무 많다 보니 수동으로 테스트하는 과정이 매우 귀찮아진다. 귀찮아서 꼼꼼하게 테스트하지 않고 일부 과정을 생략했다가 미처 확인하지 못한 오류 때문에 곤혹스러운 경험을 여러 번 했다. 자연스레 테스트 자동화에 관심이 생겼다. 하지만 사후에 테스트를 작성한다는 건 밀린 방학 숙제처럼 지겹고 재미가 없었다. 테스트 작성이 구현과 별개의 과정으로 느껴졌다. 테스트 작성에 회의가 생길 때쯤 TDD를 만났다.

테스트를 먼저 작성한다는 게 이상했다. 이상해서 신기했다. 신기해서 멋있었다. 테스트 작성이 구현 과정과 함께 묶여있다. 테스트 작성을 미루지 않아도 되잖아? 뭔지는 잘 모르겠지만 테스트를 먼저 작성하면 당시 내가 겪는 문제를 해결할 수 있을 것 같았다. 그렇게 TDD에 입문했지만 책만 가지고 혼자 공부하기가 버거웠다. 배경 지식이 너무 없어서, 도통 무슨 이야기인지 잘 모르겠더라. 사실 테스트가 뭔지도 잘 몰랐다. 그저 관심과 호기심만 가득했다. 그렇게 뭣도 없는 TDD 인생의 1막이 끝났다.

2막 1장

2013년 즈음이었나, 선배 개발자 형한테 짝 코딩으로 TDD를 배울 기회가 있었다. 제대로 배워보려고 당시에 학습한 내용을 블로그에 글로 쓰면서 TDD 인생 2막을 열었다. 열심히 쓰다가 귀차니즘이 돋아서 시리즈를 마지막까지 완성하지 못한 게 아쉽다. 어쨌든 이날의 경험을 짧은 내 개발자 인생에서 가장 짜릿했던 일로 꼽는다.

2막을 열고 올해로 3년째지만, 정작 실전에서 프로젝트 막판까지 TDD를 놓지 않았던 건 불과 한 달 전이다. 프로젝트 초반에는 항상 테스트를 열심히 작성하지만 코드가 조금만 복잡해지면, 관리를 못 해서 허둥대다가 모든 게 망가졌다. 테스트는 복잡했고, 어려웠다. 일정에 쫓겨서 마음이 급해지면 가장 먼저 테스트를 생략했다. 약간만 코드를 수정해도 빨간색으로 징징거리는 테스트 코드가 되려 짐처럼 느껴졌다. 관리하지 못한 테스트는 결국 방치되다가 버려졌다.

TDD가 좋은 설계를 유도한다는 글을 많이 봤지만 내 설계는 조금도 나아지지 않았다. 답답했다. 그때는 몰랐다. TDD가 좋은 설계를 보장하지 않는다는 사실을. 망가진 테스트가 높은 커버리지를 보여주는 것도 웃겼다. 정작 별거 아닌 버그도 못 잡아내는 녀석인데. 그렇게 알쏭달쏭한 2막을 보냈다.

그럼에도 끊임없이 TDD를 시도했다. 지금 생각해보면 TDD를 배우고자 하는 동기가 강했다. 코드 품질에 관심이 많았던 터라, 코드를 변경할 때마다 안전을 보장해 줄 장치가 필요했다. TDD를 절대로 신뢰해서가 아니라, 다른 대안을 찾지 못 해서 매달렸다.

재미있게도 매번 실패하면서도 배우는 게 있었는데, 이 과정에서 조금씩 나아지는 느낌을 받았다. 그게 날 자극했다. 단위 테스트 작성에 대한 경험치가 쌓였다. 명세를 어떻게 작성할지 고민하면서 코딩하기 전에 문제를 먼저 정의하는 습관이 생겼다. 디버깅을 하는 요령이 생기면서 버그를 찾는 시간이 짧아졌다. 공용 인터페이스를 중심으로 객체를 설계하는 방식에 익숙해졌다. 테스트 코드와 구현 코드 사이의 의존성을 고민하면서, 부분의 문제를 해결한 후 전체 구조를 생각하는 과정이 자연스러워졌다. 무엇보다 리팩터링에 자신감이 붙었다.

2막 2장

그래서 지금은 TDD를 잘하느냐고 묻는다면 그렇지는 않다. 그동안 TDD에 투자한 비용을 이제야 조금씩 회수하고 있다는 느낌을 받는 정도랄까. 아직도 의식하고 행하지 않으면 구현 코드로 손이 먼저 간다. 아직도 명세를 작성하는 스타일이 자리를 잡지 못했다. 여전히 마음이 급해지면 테스트를 오지 않을 ‘나중’으로 미룬다. 그와중에 빈도가 줄었다는 건 좋은 일.

단위 테스트 자동화로 리팩터링에 자신감이 붙었지만, 역으로 테스트가 리팩터링에 장애물처럼 느껴질 때가 가끔 있다. 문제의 근본이 테스트 코드와 구현 코드 간의 의존성, 그리고 리팩터링 진행 방식에 있다는 걸 마틴 파울러의 “리팩터링”을 읽고 나서야 깨달았다. 나는 마구잡이로 리팩터링하는 좋지 않은 습관을 가지고 있었고 테스트는 내가 가지고 있는 문제를 알려줬을 뿐이다.

여전히 TDD를 ‘수련'하고 있다. ‘수련’이라는 표현에는 '끊임없는 연습을 통해서 지속적인 향상을 추구’한다는 의미가 담겨있다. 쉽게 얻을 수 없다는 뜻이 묻어있다. 쉽게 얻을 수 있다면 끊임없이 연습을 할 이유가 없으니까.

TDD를 향해 나아가는 길은 꽤나 더디고 지루하지만, 이 과정에서 얻는 열매는 분명 달콤하다. 이런 사실을 잘 모르고 덤빈다면 TDD의 매력을 맛보기도 전에 나락으로 떨어지기 십상이다. 내가 계속 실패하면서도 아직까지 TDD를 붙잡고 있을 수 있었던 이유는 동기가 분명해서다. 지속적으로 리팩터링하고 싶은 간절함, 뭐 그런 거. 그렇지 않았다면 TDD 적응에 처음 몇 번 실패했을 때, TDD를 욕하며 돌아서지 않았을까?

TDD를 통해서 좋은 경험을 하고 있음에도, 쉽사리 주변에 권장은 하지 않는 편이다. 단지 TDD에 대해서 물어오면 대답해주는 정도가 내가 반응하는 경계선이다. 동기라는 건 강제할 수 있는 게 아니거니와, 내가 완성하지 못한 지식을 누군가에게 강요한다는 게 조금 께름칙하기도 하고.

3막으로

지난주에는 TDD에 관심있는 동료들과 짝 코딩을 했다. 참여 인원은 5명. 짝 코딩이라기보다는 떼 코딩이 적절하겠다. TDD에 대한 생각을 손과 입으로 누군가에게 전달하려다 보니 좀 더 깊은 영역을 고민하게 되더라. 옆에서 길을 안내하는 입장으로서, 가능한 동료의 생각을 간섭하지 않으려 했는데 아주 많이 인내해야 했다. 나에게 TDD를 알려 준 선배 개발자 형의 당시 심정을 짐작할 수 있어서 작은 웃음이 나왔다. 나의 인내력은 그 형에 한참 못 미쳤다.

집에서는 작성한지 3년쯤 지난 레거시 코드에 테스트를 보완해서 리팩터링하고, 추가 기능을 TDD로 확장해나가는 연습을 하고 있다. 일종의 테스트 연습장인 셈이다.

한 발 나간다. 뒤를 돌아본다. 문제가 있다. 고친다. 다시 한 발 나간다. 반복. 마치 누군가의 인생 여정처럼. 그렇게 나의 TDD 인생도 2막에서 3막으로 무르익어 간다.