번역가 : 송준이 현재 로엔에서 빅데이터 관련 업무를 하고 있다. Hadoop, Elasticsearch와 같은 빅데이터 관련 업무에 관심이 많다. 한글화 프로젝트는 프로그래머의 다양한 지식 습득을 위해 KSUG, JBUG와 지앤선이 함께 하는 프로젝트 입니다.
|
테스트 하느냐 마느냐, 그것은 좋은 문제로다.
To Test or Not to Test? That’s a Good Question.
소프트웨어 개발 분야에서 영구 불변의 진리는 ‘영구 불변하는 것은 없다.’이다. 이는 테스트의 역할에 대해서 이야기할 때도 마찬가지이다.
과거에는 테스트는 우리(프로그래머)가 아닌 다른 사람의 몫이라고 생각할 때도 있었다. XP의 등장으로 많은 프로그래머의 생각이 바뀌었다. 테스트는 우리 모두의 일이며, 지속적으로 해야 한다고 믿게 되었다. 그러나 이는 “테스트를 작성할 수 있다면 반드시 해야 한다”는 테스트 지상주의를 맹신하게 만들기도 했다.
나는 테스트를 항상 작성해야 한다고 고집했는데, 이러한 경험을 통해 시간이 허락한다면 무엇이든 테스트를 할 수 있다는 사실을 배울 수 있었다. 또한 테스트는 믿을 수 없을 만큼 기술적, 심리적, 사회적, 경제적 가치가 있다는 사실을 알게 되었다. 하지만 테스트에 대한 이러한 내 전략을 뒷받침하는 가정이 하나 있는데, 이러한 가정이 맞는지는 최근까지도 분명하지 않았다.
소프트웨어 개발은 종종 긴 게임이 되곤 한다. 내 생각에 소프트웨어 사업 중 최고는 단연코 MVS PL/1 컴파일러다. 심지어 MVS PL/1 컴파일러는 고작 3명의 직원만으로 매년 300만달러를 벌어들인 때도 있었다는 소문까지 듣기도 했다. 이 정도까지 사업이 번창하려면, 인내심을 가져야 하며, 향후 수십 년 동안 소프트웨어가 유지될 수 있도록 투자해야 한다.
테스트에 대한 내 가정을 불분명하게 만드는 것이 바로 이 ‘종종’이라는 부분이다. 골프에서도 긴 게임과 짧은 게임이 있고, 각 게임마다 필요로 하는 기술은 서로 관련 있지만 완전히 같지는 않듯이, 마찬가지로 소프트웨어 개발에도 긴 게임과 짧은 게임이 있다. JUnit Max의 경우, 소프트웨어에서의 짧은 게임을 치르고 있다. 이 짧은 게임을 하면서 ‘관련은 있지만 완전히 동일하지 않은 기술’이 소프트웨어 개발에서 어떤 의미가 있는지를 깨우치고 있다.
두 가지 프로젝트
JUnit은 긴 게임이다. 사용자도 많고, 수입도 안정적이며(0$, 아!!!), 기능도 한정적이다. 우리는 모두 JUnit에 대해 알고 있다. 또한 사용자를 끌어들이고 유지할 수 있는 부분이 무엇인지도 이해하고 있다. 따라서 천천히 변경되는 요구사항들을 조금 앞서 해결하면 그만이다.
JUnit을 만들 때는 XP 실천법들 모두가 유용했다. 항상 테스트 주도로 개발했다. 그리고 가능하면 언제라도 리팩토링을 했으며, 마음에 드는 한가지 방법을 찾기 전에는 서너가지 방법을 시도한 적도 가끔 있었다.
JUnit은 지원 비용이 점근적으로는 0에 수렴해야 한다는 사실로 그 성공 여부가 판가름난다. JUnit을 실제로 사용하는 사용자의 숫자는 엄청나게 많았지만, 지원을 위한 예산은 없었기 때문이다. 따라서 성공하기 위한 방법은 명백했다. 천천히 진화시키고, 광범위하게 테스트하며, 릴리즈는 최소한으로 줄여야 했다.
JUnit Max를 개발하기 시작했을 때, 규칙이 바뀌었다는 사실을 천천히 깨닫게 되었다. JUnit Max가 살아남기 위한 핵심적인 질문은 “어떤 기능을 만들어야 비용을 지불할 사용자를 끌어들일 수 있는가?”였다(이다). 당연하게도 이 질문에 대해서는 대답할 수 없다. 만약 JUnit(또는 무료로 배포되는 다른 패키지)이 해당 기능들을 구현한다면, 어느 누구도 Max를 돈을 주면서까지 사용하지 않을 것이기 때문이다.
JUnit Max은 그 성공 여부가 자력으로 수입을 만들어내는데 달려 있다. 비용을 지불하는 사용자가 늘고, 사용자당 수익이 증가하며, 입소문을 통한 효과가 높아야 한다. 당연하게도 JUnit Max가 성공하기 위한 방법을 알지 못했기 때문에, 성공할 가능성을 높일 수 있는 방법이 무엇인지 알기 위해서 수많은 실험들을 하고, JUnit Max가 실제로 사용되고 적용된 피드백을 통합하는 것이었다.
테스트 하느냐 마느냐.
이러한 피드백 통합을 위한 한가지 방편으로 Max에서 발생한 내부적인 에러 모두를 중앙 서버로 전달했다. 긴 게임의 프로젝트와는 달리, 짧은 게임의 프로젝트의 경우 런타임 에러가 항상 나쁜 점은 아니다(이 주제에 대해서는 다음 글에서 다룬다). 반면 내가 알지 못하는 에러는 확실히 나쁜 점이다.
에러 로그를 찬찬히 살펴보면서, 나는 수정할 수 있는 두 가지 에러를 발견했다. 주어진 시간에 맞출 수 있는 실험들이 하나도 없었기 때문에, 두 가지 에러 모두를 고치기 시작했다.
첫 번째 결함은 간단했다. 닫힌 프로젝트가 예외를 발생시켰다. 테스트를 작성하는 일도 쉬웠다. 기존의 테스트를 복사한 후, Max를 실행하기 전에 프로젝트 닫도록 수정했다. 아니나 다를까 빨간 불이 떴다. 나중에 2개의 라인을 수정하니, 녹색 불이 떴다.
두 번째 결함의 경우, 딜레마에 빠지게 되었다. 문제를 해결할 방법은 알고 있지만, 자동화된 테스트를 작성하는데 필요한 것들을 배우려면 어림잡아 수시간이 걸릴 것 같았기 때문이다. 그래서 결정했다. 고치고 배포하자. 테스트는 없다.
나는 두 가지 결정 모두를 지지한다. 두 경우 모두에서 나는 수행할 수 있는 한 최대로 검증된 실험을 진행했다. 첫 번째 결함의 경우 테스트는 소프트웨어가 회귀하는 것을 방지하고, 스스로에게는 자신감을 심어주었으며, 미래에 개발할 작업들을 보완해 준다. 두 번째 결함의 경우 테스트를 작성하지 않았으므로, 새로운 기능을 개발할 시간을 벌어 주었다.
쉬운 정답은 없다
Max를 개발하기 시작했을 때 첫 한달 동안 나는 어떤 자동화된 테스트도 작성하지 않았다. 나는 모든 테스트를 수작업으로 진행했다. 몇 명의 초기 후원자가 나타난 후에야, 나는 이전 코드로 돌아가서 기존의 기능들에 대한 테스트를 작성했다. 이 경우도 마찬가지로, 나는 이러한 순서로 개발함으로써 단위 시간에 수행할 수 있는 검증된 실험들을 최대한으로 할 수 있었다고 믿는다. 코드가 없거나 적은 경우, 테스트를 작성하지 않으면 더 빠르게 시작할 수 있다(실제로 첫 테스트를 작성할 때 거의 일주일이나 걸렸다). 작성한 초기 코드가 나중에 유용하다는 판단이 들면(내 친구들 중 몇몇이 소프트웨어를 산다는 기준으로), 테스트를 만들어서 코드에 대해 빠르게 실험하고, 자신감을 얻을 수 있다.
자동화된 테스트를 작성하느냐 마느냐를 결정할 때 다양한 요소 사이에서 균형을 이뤄야 한다. 심지어 Max를 만들 때조차도 상당수의 테스트를 작성했다. 만약 테스트를 작성할 수 있는 값싼 방법이 있다면, 나는 모든 기능에 대해 인수 테스트를 먼저 작성했다. 특히 기능을 어떻게 구현할지 마땅한 방법이 떠오르지 않을 경우, 테스트를 작성함으로써 훌륭한 아이디어를 얻을 수 있었다. 반면 Max를 개발할 때 테스트를 작성하느냐 마느냐를 결정하는 기준은 테스트가 단위 시간에 더 많은 실험들을 검증할 수 있도록 돕느냐가 핵심이다. 돕는다면 테스트를 작성한다. 그렇지 않을 경우, 위험을 감수한다. 나는 Max가 순조롭게 출발할 수 있을 정도의 수입을 얻을 수 있는 기회를 최대화하려고 노력한다. 설계에 투자해야 하는지와 관련한 추론도 이와 비슷하게 복잡한데, 이 주제 역시도 다음 글에서 다루고자 한다.
언젠가는 Max도 긴 게임의 프로젝트가 될 것이고, 범위도 명확해지며, 오랫동안 지속할 수 있는 비용을 벌어들일 것이다. 유연성을 유지하면서 동시에 비용을 줄이는 것이 새로운 목표로 자리매김할 것이다. 테스트를 작성하는데 들인 시간들도 보상받게 될 것이다. 하지만 그때가 되기 전에는 나는 짧은 게임을 하고 있다는 사실을 잊지 말아야 한다.
'IT 이야기' 카테고리의 다른 글
[한글화 프로젝트] TDD는 죽었는가? (0) | 2015.06.02 |
---|---|
[한글화 프로젝트]스타트업이 비행하는 법 (0) | 2015.04.28 |
YouthSpark IoT Camp : 사물놀이 캠프 (0) | 2015.02.02 |
[Webinar]MAS를 이용해 구현하는 고가용/고확장성 서비스 (0) | 2015.01.28 |
[Webinar]아프리카TV 라이브 추천 시스템 (0) | 2014.11.10 |