"TDD는 버그를 없애주나요?" 테스트주도개발에 대한 편견과 오해 바로잡기

#DevOps #tdd qa #backend tdd


tdd

TDD, 즉 테스트 주도 개발(Test Driven Development)에 대한 프로그래머들의 의견은 늘 엇갈립니다. TDD의 실효성을 업무로 경험한 사람들은 TDD를 더 효과적으로 실무에 적용하기 위해 고민하죠. 반면, 회사마다 일하는 방식이나 처한 업무 환경에 편차가 있다보니 일각에서는 실무에서 TDD를 사용하는 건 사실상 현실과 괴리감이 크다는 의견도 있습니다. 과연 누구 말이 맞는 걸까요?

테스트 주도 개발에 대해 이야기하기 전에, 우선 개발자가 회사에서 일하는 루틴에 대해 한 번 생각해봅시다. 보통은 이미 완성되어 배포된 제품이나 내부 시스템에 대한 버그 제보가 들어옵니다. 담당 프로그래머는 제보 받은 버그를 자리에서 재현해보겠죠? 대부분은 이렇게 말합니다. ‘제 자리에서는 되는데요?’

물론, 프로그래머의 자리에서도 버그가 동일하게 발생한다면, 그 다음 할 일은 코드를 수정하는 겁니다. 수정 후 버그를 다시 재현해보고, 해결되지 않는다면 다시 코드를 수정할 겁니다. 이 과정을 여러 번 반복하여 문제가 있는 코드를 개선해나가는 방식이 개발자의 루틴이라고 할 수 있습니다. 이는 TDD의 프로세스와 비슷합니다. 단지, 몇 가지 단계만 빠졌을 뿐이죠. 결국, 이미 많은 프로그래머들이 일하는 방식과 테스트 주도 개발 방법론은 유사한 점이 많다는 것을 알 수 있습니다.

그렇다면 TDD는 정확히 무엇일까요?

tdd

TDD가 정확히 뭔가요?

테스트 주도 개발(TDD)은 소프트웨어를 개발하는 여러 방법론 중 하나입니다. 제품이 오류 없이 정상 작동하는지 확인하기 위해 모든 코드는 프로그래머가 작성하고 나서 테스트를 거치는데요. TDD에서는 제품의 기능 구현을 위한 코드와 별개로, 해당 기능이 정상적으로 움직이는지 검증하기 위한 테스트 코드를 작성합니다. 이를 통해 테스트가 실패할 경우, 테스트를 통과하기 위한 최소한으로 코드를 개선합니다. 최종적으로 테스트에 성공한 코드를 리팩토링합니다.

tdd

TDD, 정말 효과가 있나요?

1. 코드가 내 손을 벗어나기 전에 가장 빠르게 피드백 받을 수 있다.

개발 프로세스에서는 보통 ‘인수 테스트’를 합니다. 이미 배치된 시스템을 대상으로 클라이언트가 의뢰한 소프트웨어가 사용자 관점에서 사용할 수 있는 수준인지 체크하는 과정인데요. 이미 90% 이상 완성된 코드를 가지고 테스트하기 때문에 문제를 발견할 수는 있습니다. 하지만, 정확하게 원인이 무엇인지 진단하기는 힘들죠.

반면, TDD를 사용하면 기능 단위로 테스트를 진행하기 때문에 코드가 모두 완성되어 프로그래머의 손을 떠나기 전에 피드백을 받는 것이 가능합니다.

2. 작성한 코드가 가지는 불안정성을 개선하여 생산성을 높일 수 있다.

켄트 백은 TDD는 불안함을 지루함으로 바꾸는 마법의 돌이라고 말한 적 있습니다. 앞서 말한 것처럼 TDD를 사용하면, 코드가 내 손을 떠나 사용자에게 도달하기 전에 문제가 없는지 먼저 진단 받을 수 있습니다. 그러므로 코드가 지닌 불안정성과 불확실성을 지속적으로 해소해주죠.

3. 프로그래머의 오버 엔지니어링을 방지한다.

TDD는 사실 지루한 작업입니다. 그렇다 보니, 프로그래머들은 간혹 계획하지 않았던 코드를 추가하여 오버 엔지니어링하는 경우가 있습니다. 하지만 TDD의 원칙 중 하나는, 테스트를 통과하기 위한 최소한의 코드만 작성 및 개선해야 한다는 겁니다. 기능 단위로 테스트를 진행하기 때문에, 문제가 발견되지 않은 코드에 영향을 줄 수 있는 오버 코딩은 하지 않게 됩니다.

4. 개발 과정이 테스트 코드로 남기 때문에, 과거 의사결정을 쉽게 상기할 수 있다.

TDD를 사용하면, 테스트 코드를 작성하는 과정에서 히스토리가 남습니다. 어떻게 보면 과거의 나 자신과 프로그래머가 협업하는 것을 용이하게 만들어준다고 할 수 있는데요. TDD를 통해 작성한 테스트 코드를 트래킹하면서 과거에 어떤 인과관계로 의사결정을 했는지 확인하기 쉽다는 점이 장점입니다.

TDD

TDD에 관한 편견 바로 잡기

Q. TDD는 무조건 해야 하나요?

그렇지 않습니다! TDD가 프로그래머에게 주는 이점에 대해 나열하면, TDD가 모두에게 필요한 것처럼 느껴질 수 있습니다. 하지만 프로그래머가 코드를 작성해 기능 하나를 추가할 때마다, 시간이 늘어난다고 본다면 TDD는 오히려 비효율적인 것처럼 보이기도 합니다. TDD를 사용했을 때의 초기 비용은 TDD를 사용하지 않았을 때보다 크기 때문입니다.

일단 운영 코드를 테스트하기 위한 코드를 프로그래머가 따로 작성해야 한다는 점만 보아도 그렇습니다. 그러므로 프로그래머는 하나의 프로젝트를 완성하는데 걸리는 예상 일정과 자원을 고려해서 TDD를 사용할지 말지 결정해야 합니다.

tdd

물론 TDD를 사용하면, 일정 시점을 지나면 TDD를 사용하지 않을 때와 비교하여 시간 대비 비용이 더 커지지 않고 일정하게 유지됩니다. 즉, TDD를 사용하기 시작하면 초기 비용은 더 많이 들 수 있으나 전체적으로 봤을 때 비용이 점진적으로 늘어나지 않는다는 걸 알 수 있죠.

따라서, TDD를 위한 환경세팅이 이미 잘 되어 있는 업무 환경이라면 TDD를 사용하는 편이 장기적으로는 효과적일 수 있습니다.

Q. TDD는 버그를 박멸해주나요?

그렇지 않습니다. TDD는 버그를 없애주는 도구가 아니기 때문입니다. 오히려 TDD를 사용하면 더 많은 버그를 사전에 발견할 수 있습니다. 프로그래머가 작성한 코드가 사용자에게 도달하기 전에, 혹은 전체 코드를 완성하기 전에, 기능 단위로 문제를 개선할 수 있게끔 빠른 피드백을 전달하는 것이 TDD의 역할이니까요. 그래서 버그를 빠르고 효과적으로 개선할 수 있도록 프로그래머를 도와줄 수는 있으나, 버그 자체를 없애주는 건 절대 아닙니다.

tdd

Q. TDD를 도입하면 업무 속도가 느려지지 않나요?

그렇지 않습니다. TDD를 업무에 사용할 때 업무 속도가 느려진다고 느끼는 것에는 여러 이유가 있는데요. 일단, TDD를 처음 도입하면 TDD를 사용하기 위해 필요한 초기 자원이 미진해서 속도가 나지 않는다고 느껴지는 겁니다. 회사에서 여러 프로그래머가 협업해야 하는 경우, TDD를 사용하기 위한 업무 프로세스가 익숙하지 않아 개발 속도가 느려지기도 합니다.

tdd

테스트 코드 작성에 대한 업무 부담감도 한 몫 합니다. 테스트 자동화를 위해 필요한 코드까지 프로그래머가 작성하고 관리해야 하므로, 업무가 오히려 더 늘어나 비효율적이라 여길 수 있습니다. 기억해야 할 것은 TDD 자체가 목적이 되어서는 안 된다 점입니다. TDD는 공동의 목표를 효율적으로 달성하기 위한 도구라는 것을 잊지 마세요.

그렇다면 테스트 기법으로는 어떤 것이 있을까요?

✔ 수동 테스트
✔ 테스트 자동화
✔ 인수 테스트
✔ 단위 테스트

1. 수동 테스트

회사에서는 보통 QA라고 부르는 전문 담당자들이 UI를 활용해 기능을 검증합니다. 사람이 직접 검증하기 때문에, 사용자와 가장 가까운 관점에서 테스트가 가능하죠. 소프트웨어의 모든 코드가 현장에 배치된 후에 테스트할 수 있습니다. 따라서 어떤 기능이 정상 작동하기 위해 필요한 모든 코드가 준비되어야 합니다. 가장 온전한 전체 코드를 검증하는 테스트 방식입니다.

tdd

단점은, 실행 비용은 높은데 결과 변동이 크다는 것입니다. 전문성을 지닌 담당자가 비용을 줄이기 위해 노력하지만, 휴먼 에러의 가능성을 완전히 없애기는 어렵습니다. 또한 시간이 흐르면서 추가되는 기능은 자연스럽게 증가하는데, 문제는 어떤 기능을 추가하거나 개선하기 위해 작성한 코드는 다른 코드에 영향을 미칠 수 있습니다. 그래서 테스트를 진행하더라도, 새로 추가된 코드로 인해 영향을 받을 기존 코드에 대한 테스트는 건너뛰거나 충실히 진행하지 못하는 경우가 많죠.

2. 테스트 자동화

수동 테스트가 지닌 한계를 보완하기 위해 등장한 도구라고 할 수 있습니다. 사람이 직접 테스트하지 않고, 어떤 기능을 검증하는 또 다른 코드를 작성하는 방식입니다.

tdd

단점은, 수동 테스트에 비해 프로그래머가 더 많은 코드를 작성해야 한다는 것인데요. 운영 코드를 테스트하기 위한 별도의 코드를 추가 작성해야 하기 때문에 그렇습니다. 테스트 코드의 작성과 관리가 프로그래머 개인의 역량에 달려 있어, 업무 자체가 허들이 될 수도 있습니다.

반면, 테스트에 드는 비용은 매우 낮아진다는 점이 장점입니다. 휴먼 에러의 가능성도 줄어들기 때문에, 수동 테스트에 비해 테스트 자체에 대한 신뢰도는 매우 높아질 수밖에 없습니다.

3. 인수 테스트

배치된 시스템을 대상으로 검증하는 방식으로, 주로 클라이언트가 의뢰한 소프트웨어를 최종적으로 사용할 수 있는 수준인지 점검하는 테스트입니다. 전체 시스템의 이상 여부가 없는지 확인하며, 사용자 관점으로 체크하기 때문에 신뢰도가 높은 테스트 방식입니다.

tdd

단, 비용이 매우 높습니다. 코드 작성부터 관리, 테스트 실행까지 자원이 많이 들어가는데요. 또한 프로그래머 입장에서 받을 수 있는 피드백의 질이 떨어진다. 문제는 파악할 수 있으나, 그 원인까지 단번에 알려주지는 못한다는 점이 단점입니다.

4. 단위 테스트

인수 테스트의 단점을 보완하는 검증 방식입니다. 단위 테스트는 시스템의 일부(하위 시스템)를 대상으로 기능을 검증하는데요. 전체 시스템을 배치해놓고 진행하지 않으므로, 비용이 상대적으로 낮은 편입니다.

tdd

인수 테스트와 비교해서, 단위 안에서 버그가 있다는 걸 상대적으로 자세히 알 수 있습니다. 따라서 프로그래머 입장에서는 문제 해결을 위해 필요한 피드백을 적절하게 받을 수 있죠. 하지만 전체 시스템의 이상 여부를 판단하는 신뢰도는 낮아집니다. 단위 테스트에서 문제가 없다고 판단되어도, 전체 시스템이 유기적으로 연결될 때 오류가 날 수도 있기 때문입니다.

실무에 적용할 수 있는 TDD 메인 프로세스

다음과 같이 3가지 단계로 나눌 수 있다.

1. RED : 테스트 실패
2. GREEN : 테스트 성공
3. REFACTOR : 리팩토링

1. RED : 테스트 실패

✔ 구체적인 하나의 요구사항을 검증하는 하나의 테스트를 추가한다.
✔ 추가된 테스트가 실패하는지 확인한다.

실패하는 것이 확인 되어야, 테스트가 검증력을 가진다고 신뢰할 수 있습니다. 실패의 이유는 운영 코드가 아직 변경되지 않았기 때문이어야 합니다. 테스트 코드의 문제이면 안 됩니다.

tdd

2. GREEN : 테스트 성공

✔ 추가된 테스트를 포함하여, 모든 테스트가 성공하게끔 운영 코드를 변경한다.
✔ 테스트의 성공은 모든 요구사항을 만족했음을 의미한다.
✔ 테스트 성공을 위한 최소한의 코드 변경만 진행한다.

TDD를 반복하다 보면, 지루함을 느끼는 프로그래머가 많습니다. 하지만 TDD에서는 테스트 성공을 위한 최소한의 코드 그 이상을 변경하거나 추가하면 안 됩니다. 테스트 되지 않은 코드가 중간에 추가되면, 이후 리팩토링 등의 다른 프로세스에서 어떤 부작용을 가져올지 알 수 없기 때문입니다.

tdd

3. REFACTOR : 리팩토링

✔ 코드베이스를 정리한다.
✔ 인터페이스 뒤에 숨어 있는 구현 설계를 개선한다.
✔ 가독성, 적용성, 성능을 고려한다.

tdd

지금 패캐머들이 읽고있는 BEST 아티클이 궁금하다면

이 글과 연관된 주제의 추천 강의