효율적인 테스트 코드 작성법

세줄요약

  1. TDD 보다는 OOP를 제대로 공부할 것.
  2. 데이터 위주의 함수는 구현 후에 곧바로 테스트 코드 작성해도 됨.
  3. 그 외의 경우는 버그가 나오면 테스트 코드를 작성해서 재발을 막을 것.

유튜브로 보기

본문

예 안녕하세요 포프입니다.

가끔 이제 저에 대해 오해하시는 것 중에 하나가 제가 예전에 TDD 관련 비디오를 만들어서 그런지 몰라도 "포프는 테스트에 반대한다"라는 잘못된 말씀을 하시는 분들이 있는 것 같아요. 하지만 안 그렇습니다. 저는 누구보다 테스트를 많이 하고 누구보다도 자동화된 테스트를 굉장히 좋아하는 사람입니다. 그리고 자동화된 테스트로 안되는 거는 수동화된 테스트까지 열심히 하는 사람 중에 하나입니다. 제가 반대를 했던 거는 TDD라는 방법론이에요.

TDD라는 방법론은 사실은 뭐 테스트에 관한 게 아니라 "테스트 주도적으로 개발하는 거다"라고 얘기하는데 그거에 효용성은 이미 증명이 안됐고, 20년 30년이 지나도록 그리고 오히려 여러 가지 사람들의 행동을 봤을 때 제대로 작동할 수 없는 거다라고 저는 계속 누누이 말을 해 왔어요. 그리고 정말 TDD 진영에서 말하는 것처럼 TDD를 통해서 설계 능력이 높아진다 라는 이상한 소리를 한다면은, 그거는 프로그래머의 기본인 함수의 선조건 후조건조차 제대로 공부를 안 하고 뭐 체화를 못 시킨 사람들 그리고 개체 지향을 제대로 공부를 안 한 사람들이에요. 개체 지향 설계 부분을 그래서 그거는 컴공의 원칙으로 돌아가서 그거를 공부하면 되는 것이지, 그거를 제대로 공부를 안 해놓고 나중에 무슨 이상한 방법론을 가지고 그걸 하겠다는 것 자체가 될 수도 없는 거고, 그리고 시간낭비인 거고 회사 돈 낭비를 하는 과정이라고 저는 생각을 합니다. 그래서 제가 반대했던 거고 저는 어쨌든 테스트를 좋아해요 그리고 TDD가 열심히 약을 팔면서 과거에 많이 팔았었죠. 특히 북미 쪽에서 요즘은 한국에서도 좀 팔리는 것 같지만 예전에 많이 팔면서 TDD 프레임워크 정확히 얘기하면 유닛 테스트 프레임워크가 많이 발전하면서 이제 편해진 것들 있어요.

테스트 같은 경우에 테스트 자동화 같은 경우에 그래서 저는 유닛 테스트 프레임워크를 사용을 합니다. 단 유닛 테스트를 유닛 테스트처럼 반드시 사용하지도 않고, 그 프레임워크를 그리고 TDD에서 말하는 코드를 작성하기 전에 테스트를 작성한다. 이런 이상한 짓 하지 않습니다. 그러면 "저는 유닛 테스트를 어떻게 사용할까요."라는 걸 몇 가지를 얘기하면 좋을 것 같아서 몇 편의 비디오를 만들어 보려고 해요. 최근에 포프 tv 디스코드 대화방에서 누군가 물어봐서 제가 대답을 했던 거기도 하고요. 사람은 기본적으로 내가 어떤 실수를 하는지 모릅니다. 그거를 알고 있다면 버그도 안 나오고요 그리고 그걸 알고 있다면은 당연히 버그가 안 나오기 때문에 테스트 작성할 이유도 없어요. 하지만 내가 이미 코드에서 버그를 만들고 있다는 거는 테스트를 작성할 때 똑같은 버그를 만들기 때문에 제가 작성한 테스트에서 잡히지 않을 가능성이 높죠. 그래서 저는 기본적으로 새로운 기능을 만들 때 테스트를 작성하지 않습니다.

예외는 몇 가지가 있어요. 그 새로운 기능이 굉장히 수학적이고 데이터 입력과 아웃풋이 명백한 어찌 보면은 절차적 프로그래밍에서 함수의 인풋 아웃풋 뭐 결과적으로는 블랙박스 테스팅이죠. 사실은 그게 명백히 정의가 되어 있는 것, 어떤 이미 잘 정의가 되어 있는 수학적인 연산, 해쉬 함수 같은 경우 그런 경우에는 테스트를 이미 작성하고 코드를 작성하긴 합니다. 하지만 그런 경우는 많지 않아요. 사실 그렇기 때문에 어떤 기능을 만들 때는 그런 유닛 테스트 만들고 그거에 맞춰서 뭔가 하는 이런 쓸데없는 짓, 시간 낭비를 하기보다는 기능을 만들고 그 기능에 대한 테스트를 제가 손수 많이 합니다. 근데 그 테스트를 작성하는, 아니 테스트를 하는 동안에 요 기능을 누군가는 굉장히 자주 바꿀 거고 그래서 이게 실수로 뭔가 깨졌을 때 그걸 잡을 수 있으면 좋겠다. 그럴 때는 기능을 다 만든 다음에 유닛 테스트를 몇 가지만 놓습니다.

하지만 아까 앞에서 말했던, 뭐 굉장히 정형화된 함수를 작성을 했을 때는 그거를 바꿀 사람은 많지 않거든요. 그래서 그런 건 테스트에서 잡히는 건 많지 않아요. 기본적으로 테스트는 작성을 하지 않는다라고 보시면 맞아요. 그런데 저는 테스트 프레임워크를 사용해서 유닛 테스트를 한다 그랬어요. 그럼 언제 넣냐? 실제 라이브 서비스를 하던가 뭔가를 하다가 어느 순간 저희가 미리 잡지 못했던 버그가 나오는 경우가 있어요. 그게 우리가 생각하지 못했던 방식으로 누군가가 데이터를 넣었을 때,사용자가 뭔가 사용을 해갖고 데이터가 들어왔을 때, 아니면은 뭔가 우리가 테스트를 미처 못했던 레이스 컨디션 이런 게 생겼을 때, 아니면은 이 함수를 원래 그렇게 사용을 하면 안 되는데 다른 사람이 그렇게 사용하면서 뭐 잘못했을 때, 그 함수의 내부를 누군가 바꿨을 때, 뭔가 오작동을 하는 거죠 프로그래밍을 하다 보면 함수를 한번 만들고 나서 그 함수를 여러 번 바꾸고 개선해 나가는 과정이 많습니다. 그러다가 원래 있던 가정들이 깨져서 그런 버그들이 나오는 경우 가있는데, 그 가정이 깨졌다는 것을 미리 잡지를 못하는 경우들이 있는 거죠. 나름대로 어썰트도 열심히 박아두고 나름대로 테스트 몇 개 넣어 놨지만 실제 나중에 제품이 바뀌는 과정을 처음 코드 작성자가 다 보긴 어렵거든요. 그럼 어쨌든 버그가 나와요. 그럼 그 버그를 잡는 순간 버그를 고치자나요 근데 버그를 고치기 전에 보통 테스트를 작성을 합니다. 유닛 테스트처럼 인풋 아웃풋 정확히 만들어서 그래서 지금 실제 사용자가 어떤 데이터를 넣었기 때문에 뽀개진 거죠. 그럼 그 데이터를 실제 유닛 테스트로 만들고 뽀개지는 걸 확인을 해요. 오케이 뽀개지는구나 그러면 이제 코드를 고치면서 그 테스트 데이터가 패스하게 만드는 거죠. 그럼 패스가 됐으면 그 문제는 고쳐졌다는 겁니다. 그럼 그 순간에 이제 코드 다 집어넣고 끝나는 거예요. 그럼 이거의 장점은 뭐냐면 코드를 어쨌든 간에 버그를 고치는 도중에 테스트를 여러 번 해야 되는데 그 테스트를 여러 번 할 때 프로그램을 부팅하고 뭐하고 뭐하고 뭐하고 하는 과정 자체가 시간이 꽤 걸려요. 이 테스트를 한번 작성해 놓고 버그를 고치는 게 일단 훨씬 빠르고요.

또 하나는 이렇게 우리가 버그를 고치고 테스트를 남겨두면요. 프로그래머의 의도 원래 설계가 뭐였는지 이제 문서로 남는 겁니다.처음부터 이걸 다 문서화하면 당연히 좋겠고, 처음부터 테스트 만들면 당연히 좋겠지 근대 그거 언제 다하고 있어? 그리고 그거 한다고 해서 그 가정의 깨지지 않게 될지 모르거든 사실은 언제나 계속 바뀌는 거거든 하지만 따른 가정들이 바뀌었을 때 이 기능이 뽀개졌다 그럼 이 기능은 시간이 지났을 때에도 원래대로 작성하는 그대로 작동을 해야되는 건데, 실수로 깬 거고 그럼 여태까지 요런 식으로 작동을 한동안 해왔다면 앞으로도 그렇게 작동할 가능성이 좀 더 높죠. 원래 뭐든 간에 바뀌는 부분은 굉장히 많이 바뀌는 거고 안 바뀌는 부분은 잘 안 바뀌는 거거든요. 그래서 그걸 우리가 미리 코드 작성할 때 알 수가 없다는 거예요. 서비스를 하다 보면 않다는 거지 서비스가 하면서 버그가 하나 나오면 그 버그에 대해서 그냥 유닛 테스트를 작성을 해서 그거를 뽀개 놓고 고친다면, 고친 것 확인하고 유닛 테스트를 같이 넣는다는 겁니다. 그리고 당연히 유닛 테스트가 들어갔으니까 뭐 빌드를 할 때마다 그 유닛 테스트를 이제 하는 거죠. 내 로컬 빌드가 아니라 서버에서 빌드할 때마다 그래서 나중에라도 누가 이걸 실수로 깨 먹으면 그것을 곧바로 잡을 수 있다는 거죠.

또 재밌는 건 뭐냐면 버그를 한번 만든 부분은요 누군가 고쳐 놓으면은 또 비슷한 버그를 누군가 또 만들어요. 그 이유는 뭐냐면 처음 코드의 가독성이 약간 모자라거나 아니면 처음 코드가 일반적인 사람들이 생각하는 방식과 좀 다르게 짜였다는 원래부터 코드 설계 문제일 수 있다는 겁니다. 하지만 모든 코드가 완벽하게 나올 수 없고, 어쩔 때는 아무리 열심히 설계를 하고 아무리 열심히 코드를 작성하고 아무리 열심히 함수를 작성해도 애매한 것들이 있어요. 그런 것들을 결과적으로 잡는 게 뭐냐면 시간 지나서 버그가 나오면 버그 고치고 유닛 테스트 들 넣고 그 유닛 테스트 돌리는 거 그 유닛 테스트 수는 점점 늘어나게 되는 겁니다. 이런 식으로 코드를 작성하면요. 흔히 TDD에서 보는 유닛 테스트 만행이 별로 안 생겨요. 그게 뭐냐 TDD 할 때 처음 유닛 테스트 100개를 만들어요. 코드를 작성해요. 그리고 다음에 뭐 비즈니스 로직이 바뀌어서 뭔가 바꿔야 될 때 유닛 테스트 중에 한 20%가 깨져버려요. 그러면 그 유닛 테스트 20%를 지워버리거나 새로운 유닛 테스트를 만들거나 아니면 비즈니스 로직이 바뀌었는데도 불구하고 뭐를 어떻게 해야 되는지 몰라서 다시 원래 유닛 테스트대로 맞추다가 망가지는 경우들이 생기는 겁니다. 그래서 결과적으로는 코드가 바뀌는 만큼 유닛 테스트도 엄청나게 바뀌어요. 한 마디로 원래 로직만 작성해야 되는 그 시간에 거의 두 배 세 배의 일을 하고 있다는 겁니다. 시간낭비가 엄청나고 회사에서 싫어해요. 그래서

하지만 제 방식대로 하게 되면은 아까 말했듯이 변하는 건 자주 바뀌지만 안 변하는 건 자주 안 변하거든요. 그래서 이렇게 유닛 테스트를 막 지울 일들이 적어집니다. 점점 뭐 코드 커버리지 이런 얘기 있는데, 뭐 처음부터 코드 커버리지 100% 그거는 다 이상한 얘기고 이런 식으로 유닛 테스트를 작성하다 보면은 서비스가 1년 2년 지나다 보면 코드 커버리지가 막 높아지게 돼요. 그냥, 그럼 알아서 코드가 머츄어 됐고 이 코드는 잘 안 바뀌는 거고 이런 게 다 그냥 판단이 되거든요. 하지만 인간이 처음부터 코드를 작성할 때부터 무슨 비즈니스 로직이 안 바뀔 것부터 안다? 굉장히 어불성설이죠 그래서 어쨌든 간에 저희 회사에서 그리고 제가 유닛 테스트 프레임워크 자동화가 잘 되어 있으니까 그거를 통해서 하는 유닛 테스트 한 가지가 이겁니다. 버그가 나왔을 때 그 버그를 재현시켰던 데이터를 넣고, 유닛 테스트를 돌리고 그러면 버그 때문에 뽀개 졌으니까 그거를 고치는 코드를 만들면서 여러 번 유닛 테스트를 돌려보고 그러면 그 유닛 테스트를 통과했죠? 그 예전에 다른 유닛 테스트로 다 통과하겠죠? 그러면 다 고쳤다고 생각하고 코드 집어넣고 최종적으로 당연히 시스템에 올라가서 또 테스트를 합니다.

유닛 테스트로 만족할 순 없죠? 최종 테스트는 해야 되기 때문에 물론 그것도 캐바캐지만 그래서 그렇게 전 유닛 테스트를 늘려가고 있고 그래서 유닛 테스트 상당히 좋아하는 편입니다. 특히 pocu 아카데미에서 하는 많은 일들은 많은 것들이 데이터 드리븐이 굉장히 많아요. 그렇기 때문에 그 데이터 드리븐으로 판단할 수 있는 것들 테스트할 수 있는 것들 그런 것들은 굉장히 많이 합니다. 그래서 유닛 테스트를 저희가 할게 되게 많아요. 사실 그런 식으로 엄밀한 의미의 유닛 테스트는 아니겠지만 자동화 테스트 유닛 테스트 프레임워크를 이용한 유닛 테스트를 자동화 테스트를 저희는 그렇게 하고 있습니다. 그래서 혹시라도 "TDD 가 하면서 이상했다." "아 이 말이 되는 것 같으면서도 왜 안 할까?" 이런 고민 하셨던 분들은 이런 식으로 자동화 테스트를 약간씩은 만들어 가세요. 단 원래 4시간 안에 끝내야 될 일을 테스트 만들려고 이제 뭐 12시간이 걸립니다. 그런 순간 조만간 사표를 쓰거나 잘릴 거라는 생각을 하면서 열심히 사셔야겠죠. 예 포프였습니다.

img

제대로 대우받는 개발자 | 부족한 컴공지식 배우기 | MIT급 컴공인강

최저임금으로 고통받는 일회성 프로그래머는 그만! POCU 아카데미가 올해 연봉협상을 책임지겠습니다!