[Refactoring] 리팩터링 2판 Chapter.04 (테스트 구축하기)
카테고리: Refactoring
리펙터링에서 저자가 가장 강조하는 것 중 하나가 바로 테스트
입니다. 견고한 리펙토링에는 견고한 테스트 스위트(test suite)
가 뒷받침 되어야 합니다.
리펙터링 뿐만 아니더라도 테스트 케이스는 개발의 속도를 높여준다고 저자는 강조 합니다.
이번 챕터에서는 테스트 기법 중 단위 테스트(Unit Test)
에 대해 소개 합니다. 저는 아직 함수마다의 테스트 케이스를 작성하여 개발을 해본적은 없습니다. 제가 사용하는 유니티 엔진에서 씬을 따로 파서 어떤 모듈들을 개발하면 그 모듈이 정상적으로 작동하는지에 대해 테스트 하는것이 전부 입니다. 그래서 그런지 테스트에 대한 이야기들이 그렇게 공감이 되진 않았습니다. 이번 포스팅에서는 테스트의 의미와 예시가 나와있는데, 그중 인상깊었던 부분에 대해 적어봤습니다.
테스트의 자동화
제가 잠깐 스프링에 대해서 공부를 했을 때, 김영한
강사의 인강을 본 적이 있습니다. 이 분이 강의를 하실 때도 강조를 하던게 바로 테스트
입니다. 자바에서는 JUnit 이라는 테스트 프레임워크가 있습니다. 리팩터링 책에서도 해당 프레임워크를 소개하면서 테스트 도구의 시초라고 말을 하니 뭔가 반가웠습니다.
그리고 테스트를 할 때, 모든 코드를 직접 짜고, 이에대한 검증도 직접 하게 된다면 굉장히 부담스럽습니다. 이에 흥미를 느끼는 사람도 있을 수 있지만, 그렇지 않은 사람이 대부분 입니다. 저자는 이에 이 테스트를 자동화 하라고 말을 합니다. 역시 귀찮은 일의 끝은 자동화 이군요.
테스트
테스트 케이스를 작성하여 테스트를 돌렸을 때, 전부 통과 한다면 마냥 기분이 좋기 보단, 내 의도와는 다른 방식으로 코드를 작성한것이 아닌지 불안해 집니다. 이 상황에서 저자는 다음과 같이 이야기 합니다.
실패해야 할 상황에서는 반드시 실패하게 만들자.
수많은 예외처리들, 방어코드를 머릿속으로 예측하여 오류를 막는것 보다, 실패하게 만들어서 해당 실패지점들을 하나씩 수정해 나가는것이 더 현명한 것 같습니다.
또한, Assert
와 같은 단언 코드를 사용하여 코드의 오류를 미리 뽑아내 볼 수도 있습니다. 언젠가 한 유튜브에서 Assert
에 대한 이야기를 들은 적이 있습니다. Assert
는 개발자의 의도를 주석 대신 코드로 명확하게 전달할 수 있고, 실패지점을 추적할 수 있으며, 가장 매력적인 것은 빌드 시 Assert
는 빌드에서 빠진다 라는 것입니다. Assert
를 좋아하는 어떤 개발자는 코드 3줄당 Assert
를 한번 이상 적는것을 권한다고 합니다. 저도 이 말을 듣고나서, 제 코드에 Assert
문구를 하나씩 늘려 나가는 연습을 하고 있습니다.
테스트를 해야 하는 곳
그렇다면, 저자가 이토록 강조하는 테스트는 모든 코드에서 수행되어야 하는걸까요? 당연히 아닙니다. 모든것이 과유불급이라고 모든 곳에 테스트를 넣게되면 정작 기능개발에 속도가 붙지 않아 금방 프로젝트를 포기하거나, 주어진 기간 내에 프로젝트를 완료 할 수 없게 됩니다.
성능 최적화 에서도 말했듯, 테스트 역시 위험 요인을 중심으로 작성해야 합니다. 어떤 사람들은 public 필드들에 모두 테스트 케이스를 작성하는 사람이 있다고 합니다. 버그가 숨어들 가능성이 거의 없는곳에 에너지를 쏟는다면, 이는 바람직하지 않습니다.
이와 같은 맥락으로 경계조건 테스트
가 있습니다. 어떤 값을 넘겨받아 일을 수행하는 함수에게는 필수적이라고 할 수 있습니다. 정보처리기사 준비할 때 많이 들은 용어인데, 이렇게 책에서 만나니 반가웠습니다.
경계조건 테스트
의 예시로는 다음과 같은것이 있습니다. (예시가 완벽하지 않은것 같긴 하지만, 의미를 전달하는 목적으로 작성하였습니다.)
지금 예시는 C#
으로 작성되어 함수의 인자에 명확한 타입의 값을 받을 수 있지만, 리팩터링 책에서 선택한 javascript
와 같은 언어들은 구체적으로 타입이 명시되어 있지 않아, 해당 예를 구현하기 위해 억지로 인자에 object
를 끼워 넣었습니다.
private void Main()
{
int totalCost = AddInt(100);
}
private int AddInt(object _object)
{
return _object + 1;
}
위와 같이 totalCost
를 구하는 상황이 있다고 생각해 봅시다. 지금 제가 원하는 것은 정수끼리의 합을 계산하여 totalCost 에 넣는것 입니다. 하지만 object 형에는 string
등 여러가지 형태의 데이터가 들어갈 수 있기에, 해당 함수에 여러가지 데이터를 넣어보고 그 결과를 확인하는 작업을 거쳐야 합니다.
결론
해당 책은 리팩터링에 관련된 책 입니다. 그럼에도 불구하고 저자가 이토록 테스트에 관해 열정적으로 이야기 하는것은 바로 리팩터링의 필수조건이 테스트 이기 때문이라고 생각 합니다. 리팩터링의 뜻을 다시 생각해보면, 겉보기 동작은 그대로 둔 채, 내부 코드를 수정하는 작업 이라고 할 수 있습니다. 하지만, 리팩터링도 결국 사람이 하는것이다 보니, 내부코드를 수정하게 되면 반드시 생각지 못한 버그가 일어나게 됩니다. 해당 부작용을 막기 위해 테스트 케이스를 준비하고, 리팩터링 하라고 저자는 당부 합니다.
사실 이번 챕터가 크게 공감이 가지 않았던 부분 중에 하나는 위에서 설명하였듯, 제가 하는 프로젝트 규모에서는 테스트 케이스가 없어도 큰 문제 없이 프로젝트를 완료할 수 있었다는 점이 큰 것 같습니다. 오히려 테스트를 작성하면 시간이 지체될 것이라고 생각하는 부분도 없지않아 있는것 같습니다. 하지만 여러 위대한 개발자들이 강조하는 것이 바로 테스트
라는 점에서 조금은 억지로라도 테스트 케이스를 만들며 개발을 해봐야 겠다는 생각이 드는 챕터 였습니다.
댓글 남기기