본문 바로가기

Stupid Computer/1. About Computer

[TDD][C/C++/JAVA]왜 테스트 코드를 먼저 작성해야하는가?

출처 : http://wrice.egloos.com/4862670


TDD(Test Driven Development)라는 검색어로 인터넷을 검색해보시면 수많은 글들을 보실수 있을겁니다. 쉽게 말해 코딩 시작하기 전에 테스트 코드를 먼저 작성하라는 것입니다. 이렇게 하는데에는 이유가 있는데 간략히 설명해보려고 합니다.

테스트 코드는 읽기가 쉽고 복잡도가 낮습니다.
예를 들어 소수(Prime numbers)를 구하는 프로그램을 만든다고 가정해봅시다.
인터넷을 검색해보면 소수 구하는 프로그램을 많이 볼수 있을텐데요, 이게 소수 구하는 프로그램이라는 걸 알고 코드를 봐도 어떻게 돌아가는지 이해하는게 쉽지 않습니다. 보통 재귀적(Recursive)으로 작성된 프로그램들이 읽기가 어렵다는 특징도 있고요.

하지만 같은 것을 테스트 코드로 보면 이해가 훨씬 쉽습니다.

assertEquals(2, prim_num(1)); // 1번째 소수값으로 2를 반환하는지 확인
assertEquals(3, prim_num(2)); // 2번째 소수값으로 3을 반환하는지 확인
assertEquals(5, prim_num(3));
assertEquals(7, prim_num(4));
assertEquals(11, prim_num(5));

물론 이런식으로 일일이 결과 값을 확인하는데에는 한계가 있지만, 첫번째 부터 대략 5개까지만 맞는지 확인해보면 그 뒤는 대충 맞을꺼라는 확신을 갖을수 있습니다. 여기에 어떤 값을 넣어보고 확신을 할수 있는지는 Input Space Partition접근법을 사용하면 보다 효율적으로 테스트를 할수 있구요.

요점은 테스트 케이스는 읽기가 쉽고 이해하기가 쉽습니다. 거기에 더해서 대부분의 테스트 코드는 if 문이나 for, whilte 문 같은 것을 쓰는 경우가 거의 없습니다. 직관적이고 일괄적입니다. 때문에 테스트 코드를 만드는 과정에서 실수가 생길 확율이 훨씬 낮습니다.

어떤 프로그래밍 라이브러리가 새로 개발되어 나왔을때 흔히 가장 먼저 찾는 것은 Tutorial 입니다. 튜토리얼은 하나씩 사용법을 기준으로 해당 라이브러리를 익혀가는 것이지요. 예를 들어 JavaScript를 가장 먼저 배울때에도 "Hello! World."를 화면에 찍는 코드를 먼저 보여주면서 시작하는 식이지요. 반면에 함수 이름이 죽~ 나열되고 각각의 기능이 문서로 정리된 (전화번호부 같은) 것은 Reference라고 부르면서 나중에 익숙해지면 찾게 되지요. 테스트 코드를 읽는 방식은 튜토리얼을 보고 기능을 파악하는 것과 같은 셈이고, 함수의 기능 설명 문서를 읽거나 구현을 직접 읽는 것은 레퍼런스를 읽고 있는 것과 같다고 생각합니다.

읽기 쉽고, 복잡도가 낮고, 작성이 쉽고, 오류도가 낮으면서, 원하는 기능을 정확하게 묘사할수 있는 방법이 바로 테스트 코드입니다.

여기에 더해서 리그레션 테스팅(Regression Testing)을 하는데에는 테스트 코드를 사용하는 방법 말고는 딱히 적절한 방법이 없습니다. 리그레션 테스팅이란, 어제 만들었던 프로그램이랑 오늘 만든 프로그램이랑 비교 했을때 오늘 만든 프로그램이 오히려 퇴화했는지를 확인하는 테스트입니다. 이 문제점은 흔히 발생하는데, 1주일쯤 전이나 한달전에 잡았다고 생각했던 버그가 오늘 보니까 어느세 다시 부활해있더라... 혹은 여기를 요렇게 고치면 다른데에서 문제가 생기지 않을까? 하고 불안 불안해서 관련된 코드의 논리를 모두 따라가면서 읽어서 확인해봐야하는 상황이 꼭 생기고 맙니다.

이럴때에 테스트 코드에 의존하면서 코딩을 한 프로그래머는 테스트 코드가 "문제 없다" 하고 말하면 그걸로 끝입니다. 코드 구현한 것 끼리의 연관성 같은걸 걱정할 필요가 없는 것이죠.

흔히들 TDD의 문제점으로 지적하는 것이 "실제 코드량과 테스트 코드량이 거의 같아져서 실제로는 2배의 프로그램을 만드는 셈이된다"는 것입니다. 이게 사실이라고 치더라도 여기에는 프로그램의 복잡도가 고려되지 않고 가독성과 정확도등이 고려되지 않은체 단지 코드량만 비교하는 오류를 범하고 있는 것입니다. 실제로 코드량이 는다고 해서 하는 일의 량이 비례해서 느는 것이 아닙니다. 테스팅 코드는 수작업으로 할 테스팅을 자동으로 해주는 프로그램인 셈이므로 일의 량을 줄여주는 역할을 하는 것이죠.

실제 테스팅 코드의 문제점은 "늘어나기는 쉬워도 줄어들기는 어렵다"는 점입니다. 테스트 코드는 특성상, 새로운 테스트 코드를 계속 덧붙이는게 쉬운 반면에 기존의 테스트 코드중 필요 없어진 것을 찾아서 삭제 하는 것은 상당히 어렵습니다. 기술적으로는 테스트 코드 각각의 성능을 측정하는 기술이 있어서 테스트 코드 성능이 낮은 것을 찾아서 자동으로 삭제 하는 것까지 이론적으로는 가능한데 실제로 사용할수 있는 툴은 존재하지 않습니다. 이 단점에 대해서는 나중에 다시 기회가 되면 자세히 적어보겠습니다.


테스트 코드는 또한 UML과 함께 쓰면 "코딩 한줄 안하고 프로그램을 만들 수 있"게 됩니다. UML 자체는 프로그램의 설계나 클레스 간의 통신 순서등을 표현하는데 강력하지만, 해당 메소드 하나하나의 속성을 구체적으로 표현하는데에는 한계가 있습니다. 때문에 메소드 하나하나에 대해서 말로 "이런 이런 기능을 하는거다"하고 "문서화"에 의존해서 기능을 기술하게 되는데, 여기에 부정확성이 발생하게 됩니다. 이부분을 보완해서 구체적인 기능을 명시하도록 도와주는 것이 바로 테스트 코드가 되겠습니다.

따라서 UML과 테스트 코드만 완성되면 그것만 가지고도 어느 프로그래머가 구현을 하건, 같은 프로그램이 완성될꺼라는 확신을 할수 있게 되므로 사실상의 프로그램 작성이 코딩을 시작하기 전에 끝나는 셈입니다. (물론 프로그램 작성하는 과정에서 테스트 코드가 더 추가 되겠습니다만...)

프로그램 개발 방법론을 공부하면서 느끼는 것은, 아직까지도 테스팅을 코딩 다음 단계의 과정에 두는 것을 기본으로 하는 분위기입니다만, Agile 방법론이나 Rational UP, XP 같은 최신 개발 방법론에서는 테스트 코드 작성을 먼저 하라고 강권하고 있습니다. 사실 제대로 UML 설계 단계를 거쳐서 개발하는 경우에는 Use cases를 통해서 사용자 "사용 시나리오"가 이미 작성이 되어나오기 때문에 그걸 가지고 테스팅 코드를 작성하는 것은 매우 쉽습니다. 테스트 코드를 작성을 먼저하지 않을 이유가 없는 것이죠.