언제부턴가 테스트관련 서적과 refactoring 서적을 많이 읽고 있는 나 자신을 발견한다. 개인적으로 관심이 많기도 하고, 개발자로 먹고살려다 보니 반드시 필요하기때문이다.
테스트를 만드는 것과 refactoring을 하는 것은 많은 관련이 있다. refactoring을 하려면, 테스트를 해서 정상적으로 작동을 하는지에 대한 체크가 필요하다. 부지런한 사람(?)은 아마 코드를 고치고, 사용자 테스트로 직접 어플리케이션에 올려서 테스트를 한다. 반복된 행동을 하면서 짜증만 늘어가는 부지런한 사람이 되는 것이다. 겉으로 보기에는 부지런해보일지 모르지만, 속으로는 썩고 있는 성겨파탄자가 될수도 있다. 그래서, 테스트의 자동화는 매우 중요하다.
코드나 설계한 대해서 실행(Runtime)을 통해서 검증을 하는 것 만큼 확실한 방법은 없다. 나는 내가 할수 있는 한 빠르게 실행을 하려고 온갖 정성을 기울인다. 우리가 refactoring을 넘어서서 신규 코드를 작성을 할때도 가장 빠르게 실행환경을 갖출 수 있는 방법은 단위 테스트를 만드는 것이다.
이미 테스트의 중요성은 이미 여러군데에서 다루었기때문에 생략을 하고, 테스트를 잘 작성을 하려면 어떻게 해야 하는지 고려해보자.
단위 테스트를 만들다보면, 잘 안된다. 왜 잘 안될까? 코드를 테스트하기 어렵게 만들기도 하고, 테스트를 고려하지 않고, 작성을 하기때문이다. 특히 여러가지 환경이나, 책임들이 얽혀있는 경우는 더 어렵다. 그래서, 일부 선배 개발자들은 test first를 추천을 한다. 테스트가 가능한 시점부터 코드작성이 완료될 때까지 테스트가 가능한 상태를 유지하기에는 아주 좋은 방법이다. 코드추가후 테스트를 실행하는 것에 대한 interval만 조절을 잘하면, 그다지 많은 시간이 걸리지도 않는다.
실무 개발을 진행중에 테스트하면서 느끼는 어려움, 경험을 좀더 빠르게 알고, 해결방법을 찾고 싶다면 아래 책을 추천한다. 내가 겪었던 고통들을 이미 저자는 몇 가지 용어로 정의를 해서 도움이 될만한 이야기를 해준다.
운영 및 유지보수라는 단어의 의미는 너무나 다양하고, 광범위하게 쓰인다 소프트웨어 유지보수의 중요성을 보면서 다시 한번 유지보수에 대해서 생각을 하게 되었고, 테스트의 중요성에 대해서 생각을 해봤다.
시스템에 따라서 행위가 많이 달라지기 때문이다. 개발자들은 유지보수를 많이 해봐야한다. 유지보수를 해보지 않고, 신규 개발을 잘하기는 정말 어렵다. 시스템의 라이프싸이클이 개발하는 기간보다는 유지보수를 하는 기간이 훨씬 길다는 사실은 너무나 잘 아는 이야기이다.
나는 하나의 시스템을 가지고, 4년동안 유지보수를 해본 경험이 있다. 앞으로도 쉽게 가질 수 있는 소중한 경험이었다. 그 경험은 나에게 큰 도움이 되고 있다. 유지보수를 오래할 수록 안정적이게 된다. 물론, 그 도메인 영역에 대한 이해도가 높아져서이기도 하지만, 자주 있는 일에 대한 테스트를 하나씩 만들어서 추가를 하기때문이기도 하다.
제일 중요한 포인트는 어떤 방식으로던지 테스트가 가능해야 하다는 것이다. 테스트를 1초라도 빨리할 수 있으면 유지보수시에 잦은 변경사항을 적은 노력으로 버그를 최소화하면서 변경을 할수가 있다. 초보시절에는(junit을 사용하기전에는) 주로 웹서버에서 굵게주로 테스트를 하는 mock페이지를 많이 만들었는데, 몇 년전부터 지금까지는 junit을 이용해서 단위 테스트를 가능한 많이 만들고 있다.
단위 테스트만큼 빠르게 테스트가 가능한 것은 없을 것이다. 난 더욱더 테스트 중독에 빠지려고 한다.
상속을 통해서 클래스 갯수를 줄이고 있던 디자인이 있었다. 거의 7개 가량의 갯수를 줄이기 위해서 디자인이 깨지는 것을 보면서도 이게 실용적(?)이라고 생각을 하면서 클래스 갯수를 줄였지만, 영 마음이 불편했다. OOP의 원칙을 깨가면서 만든 디자인이었기때문이다.
불편한 마음을 없애기 위해서 과감하게 클래스 갯수를 늘렸다. 일단, 원칙을 지키면서 점진적으로 refactoring을 하기로 결심을 했다. 대략적인 윤곽은 마음 속에 있었지만, 사실 목적은 분명하지 않은채 점진적으로 변경을 꾀하면서 변경된 코드 안에서 좀더 나은 디자인으로 개선을 해나갔다.
대략의 과정을 설명을 하자면,
구현 상속으로 되어 있던 디자인을 delegate를 통해서 데코레이션 패턴(나..이 패턴 너무 좋아한다..^^;)으로 변경하고 , 인터페이스만을 wiring하는 클래스를 만들고, dependency injection을 spring config에서 설정을 통해서 인스턴스 생성과 Factory에 등록을 했다.
디자인을 변경 후 3가지의 장점을 가질 수가 있었다.
1. 테스트가 쉬워졌다. 구현상속은 많은 dependency를 가지게 된다. dependency를 하나의 인터페이스로 추상화가 가능했고, 하나의 mock을 생성해서 테스트를 할 수가 있었고, 확장하는 클래스에서 필요한 클래스들만 mo필ck으로 만들어서 테스트를 하면 되었기때문에 테스트를 작성하기 쉬웠다.
2. 코드의 이해가 쉬워졌다. 구현에 대해서 집착을 하지 않아도 되고, 클래스 자체만 봐도 이해를 하기 쉬웠다. 다른 클래스들과의 관계는 이 클래스를 이해하는데, 별다른 장벽이 되지 않았다.
3. 객체지향원칙을 지킬 수 있었다. 부모 클래스에서 확장되어지는 자식 클래스때문에 로직이 추가가 되면 안된다. 비즈니스 로직 특성상 피하기 어려웠지만, 최소한의 변경으로 디자인이 깨지는 것을 피할 수 있었다. 즉, 자식과 관련되서 if문이 증가하지 않았다.
아마 spring configuration을 사용하지 않았다면, 클래스 갯수를 7개정도 늘려서 DI를 해주는 상황이었다. 원칙에 따라서 디자인을 개선을 하고 나니 나름 볼만한 코드가 되었다. 클래스 갯수가 총 8개가 늘어나야 할 상황이었지만, 7개에 해당하는 dependency부분을 spring IoC가 해결을 해주었고, 인터페이스를 wiring하는 심플한 하나의 클래스만 늘어나게 되었다. spring config를 통한 IoC가 아니었어도, 클래스 갯수를 늘려서 확장하는 것이 좋은 선택이었을 것이다. 그 좋은 선택 안에서 spring IoC는 좀더 나은 기쁨을 준 것이다.
사실, 서비스되고 있는 코드를 refactoring하는 것은 쉬운 일은 아니지만, 계속된 찜찜한 마음의 짐을 떨치고 싶었다. 좀더 자신감을 가지고 변경을 꾀할수 있었던 것은 테스트가 된 구현 클래스들을 인터페이스 기반으로 조립하는 일이었기때문에 가능했다. 구현을 다시한 부분은 거의 없고, 클래스들 간의 관계를 바꾸는 디자인 개선작업이어서 좀더 편하게 변경을 가할수 있었다.