프로그래밍

[번역] 일급(First-Class) 테스트

Clean Code 블로그의 “First-Class Tests” 라는 글을 번역했다. 글을 참 짖궂게 쓴다. 개인적으로 엉클밥을 참 좋아하는데, 요즘은 뭔가 TDD교를 신봉하는 꼰대 아저씨의 느낌이 나려고 하는 것 같다. 하지만 TDD에 대한 이론이나 경험에 있어 엉클밥만큼 신뢰할만한 사람은 몇 안되지 않는다고 생각하고, 개인적으로 이 글을 통해 테스트에 대한 개념을 정리하는 데에 많은 도움을 받았다고 생각한다.

사실 프론트엔드 개발자로서, 나는 최근에 TDD에 대해 조금 회의적인 생각이 들기 시작했다. 좀더 균형잡힌 시각을 위해서는 이 글 뿐만 아니라 이 글의 소재가 된 블로그 글, 그리고 거기에 링크되어 있는 “Why most unit testing is a waste“도 읽어보면 도움이 될 것이다.

이 주제에 대해서는 조만간 생각이 정리되면 포스팅을 하도록 하겠다.


 

부적절한 학습의 희생양이 되어 단위 테스트를 포기하게 되는 사람들의 블로그를 찾아내는 건 어쩌면 나의 숙명일지도 모르겠다. 이 블로그도 바로 그 중의 하나이다.

저자는 모든 협력 객체(collaborator)를 모킹함으로 인해 단위 테스트가 얼마나 깨지기 쉬운 상태가 되었는지를 이야기한다. (한숨). 협력 객체가 변경될 때마다 모의(mock) 개체들이 변경되어야만 한다. 그리고 당연히 그로인해 단위 테스트는 깨지기 쉬운 상태가 된다.

더 나아가서 저자는 어떻게 단위 테스트를 버리고 대신 흔히들 말하는 “시스템 테스트”를 시작했는지에 대해서 이야기한다. 그의 어휘에 따르면 “시스템 테스트”는 단순히 “단위 테스트”보다 좀더 종단간 (end-to-end) 에 가까운 테스트이다.

자 먼저, 몇가지 정의를 내려보자. 거만하게 굴어 미안하지만, “단위 테스트”, “시스템 테스트”, “인수 (acceptance) 테스트” 등에 대해서는 수많은 정의가 존재하기 때문에, 누군가가 하나의 권위있는 정의를 제공해야만 할 것 같다. 이들 정의가 정착될지는 모르겠지만, 가까운 미래에 이 중의 몇가지는 그렇게 되길 바란다.

  •  단위 테스트 : 프로덕션 코드가 프로그래머가 기대하는 대로 동작하는지를 보장하기 위해 프로그래머가 작성하는 테스트 코드이다. (단위 테스트가 디자인을 도와준다는 등의 개념은 잠시 무시하기로 하겠다)
  • 인수 테스트 : 프로덕션 코드가 사업자가 기대하는 대로 동작하는지를 보장하기 위해 사업자가 작성하는 테스트이다. 이 테스트의 작성자는 사업부 인력이거나, 혹은 사업부를 대표하는 기술 인력이다. (예: 사업 분석가, QA)
  • 통합 테스트 : 시스템 컴포넌트들의 하부 조합(sub-assembly)이 제대로 작동하는지를 보장하기 위해 아키텍트나 기술 리더가 작성하는 테스트이다. 이 테스트는 Plumbing 테스트이다. (역: 기술적인 테스트라는 의미인 듯). 사업 규칙에 대한 테스트가 아니다. 이 테스트의 목적은 하부 조합이 제대로 통합되고 설정되었는지를 확인하는 것이다.
  • 시스템 테스트 : 통합된 모든 시스템의 내부가 계획한 대로 동작하는지를 보장하기 위해 작성하는 통합 테스트이다. 이 테스트는 Plumbing 테스트이다. 사업 규칙에 대한 테스트가 아니다. 이 테스트의 목적은 시스템이 제대로 통합되고 설정되었는지를 확인하는 것이다.
  • 마이크로 테스트 : Mike Hill (@GeePawHill)에 의해 만들어진 용어이다. 아주 작은 범위에서 작성된 단위 테스트이다. 단일 함수나 작은 그룹의 함수들을 테스트하기 위해서 작성한다.
  • 기능(Functional) 테스트 : 넓은 범위에서 작성된 단위 테스트이며, 느린 컴포넌트에 대한 모의 객체를 포함한다.

이들 정의를 통해 보면, 위 블로그의 저자는 잘못 작성된 마이크로 테스트를 포기하고, 대신에 잘못 작성된 기능 테스트를 작성했다. 왜 잘못 작성되었을까? 왜냐하면 저자의 설명에 비춰볼 때, 두가지 경우 모두 테스트들이 결합되어서는 안되는 것들과 결합되었기 때문이다. 그는 마이크로 테스트에서 너무 많은 모의 객체를 사용하고 있었고, 테스트 코드가 프로덕션 코드의 구현과 깊이 결합되어 있었다. 그러면 당연히 깨지기 쉬운 테스트가 될 수 밖에 없다.

기능 테스트를 살펴보면, 저자는 이들 테스트를 UI부터 데이터베이스에 이르기까지를 모두 포함하는 것처럼 설명하면서, 테스트가 느렸다는 사실을 언급했다. 그는 테스트가 가끔 15분내에 실행된다는 사실에 기뻐했다. 15분은 단위 테스트가 제공해야 하는 즉각적인 피드백을 위해서는 너무 긴 시간이다. TDD 개발자들에게 지속적 빌드 시스템이 테스트가 통과되었는지를 확인해줄 때까지 기다리는 습관같은 건 없다.

숙련된 TDD 개발자들은 마이크로 테스트이든 기능 테스트이든 (인수 테스트 또한 마찬가지로) 시스템의 구현과 결합되어서는 안된다는 것을 잘 알고 있다. 이들 테스트는 (위 블로그 저자가 강조했듯이) 시스템의 일부로써 어겨져야 하며, “일급 시민처럼 다루어야 한다 : 프로덕션 코드를 다루는 방식으로 다루어야 한다“.

위 블로그 저자는 시스템의 일급 시민은 결합되어서는 안된다는 점은 알지 못했던 것 같다. 자신의 테스트를 일급 시민처럼 다루는 사람은 이들 테스트가 프로덕션 코드와 강하게 결합되지 않도록 하기 위해 엄청난 노력을 기울인다.

마이크로 테스트와 기능 테스트를 프로덕션 코드로부터 감결합(decoupling) 시키는 것은 특별히 어려운 일이 아니다. 몇가지 소프트웨어 디자인 기술과 몇가지 감결합 기법에 대한 지식이 있으면 된다. 일반적으로 좋은 객체지향 디자인과 의존성 역전, 그리고 몇가지 디자인 패턴(파사드 혹은 전략 패턴과 같은)의 신중한 사용 정도면 대부분의 해로운 테스트들을 감결합 시키기에 충분하다.

불행하게도, 너무나 많은 프로그래머들이 단위 테스트에 적용되는 규칙은 다르며, 편한대로 급하게 만든 “쓰레기 코드”라도 상관없다고 생각한다. 또한, 너무나 많은 프로그래머들이 모의 객체에 대한 책을 읽고 모의 객체 도구들이 본질적이고 필요하며 단위 테스트의 일부라는 관념을 믿어오고 있다. 진실과는 한없이 멀게도 말이다.

내 경우에는 모의 객체 도구를 거의 사용하지 않는다. 만약 모의 객체(혹은 오히려 테스트 대역(Test Double))가 필요할 때면, 직접 작성한다. 테스트 대역을 작성하는 것은 그리 어려운 일이 아니다. IDE가 많은 도움을 준다. 게다가 테스트 대역을 스스로 작성하는 것은 정말 필요한 경우가 아니면 테스트 대역을 작성하지 않도록 도와준다. 테스트 대역을 사용하는 대신, 나는 마이크로 테스트에서 살짝 물러나서 좀더 기능 테스트에 가까운 테스트를 작성한다. 이 또한 내가 프로덕션 코드의 내부와 테스트 코드 사이의 결합을 감소시키는 것을 도와준다.

결국, 사람들이 단위 테스트를 포기하는 이유의 대부분은 위의 저자가 말한 조언을 따르지 않았기 때문이다. 그들은 테스트를 일급 시민으로 다루지 않았다. 그들은 테스트를 시스템의 일부인 것처럼 다루지 않았다. 그들은 다른 시스템에 적용하는 것과 동일한 기준을 갖고 테스트를 관리하지 않았다. 대신에, 그들은 테스트가 부패하고 결합되고 굳어가고 깨지기 쉽고 느리게 되도록 방치해왔다. 그리고 나서, 그들은 좌절하며 테스트를 포기한다.

교훈 : 테스트를 깔끔하게 유지하라. 테스트를 시스템의 일급 시민처럼 다루어라.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s