『effective unit testing』 - 맛보기

94

Upload: wegra-bokyeon-lee

Post on 15-Apr-2017

314 views

Category:

Technology


11 download

TRANSCRIPT

Page 1: 『Effective Unit Testing』 - 맛보기
Page 2: 『Effective Unit Testing』 - 맛보기

Effective Unit Testing : 개발자를 위한 단위 테스트

초판발행 2013년 11월 10일

지은이 라쎄 코스켈라 / 옮긴이 이복연 / 펴낸이 김태헌

펴낸곳 한빛미디어 (주) / 주소 서울시 마포구 양화로 7길 83 한빛미디어(주) IT출판부 전화 02 – 325 – 5544 / 팩스 02 – 336 – 7124등록 1999년 6월 24일 제10 – 1779호 / ISBN 978 – 89 –6848 – 062 –1 13000

책임편집 배용석 / 기획 박민아 / 편집 박민아

디자인 표지 강은영, 내지 강은영, 조판 김현미

영업 김형진, 김진불, 조유미 / 마케팅 박상용, 박주훈, 서은옥

이 책에 대한 의견이나 오탈자 및 잘못된 내용에 대한 수정 정보는 한빛미디어(주)의 홈페이지나 아래 이메일로

알려주십시오. 잘못된 책은 구입하신 서점에서 교환해 드립니다. 책값은 뒤표지에 표시되어 있습니다.

한빛미디어 홈페이지 www.hanb.co.kr / 이메일 [email protected]

Effective Unit Testing: A Guide for Java Developers by Lasse Koskela

Original English language edition published by Manning Publications. USA.

Copyright © 2013 by Manning Publications Co..

Korean–language edition copyright © 2013 by Hanbit Media, Inc. All rights reserved. 이 책의 한국어판 저작권은 대니홍 에이전시를 통한 저작권사와의 독점 계약으로 한빛미디어㈜에 있습니다.

신저작권법에 의해 한국 내에서 보호를 받는 저작물이므로 무단전재와 복제를 금합니다.

지금 하지 않으면 할 수 없는 일이 있습니다.

책으로 펴내고 싶은 아이디어나 원고를 메일 ( [email protected] ) 로 보내주세요. 한빛미디어(주)는 여러분의 소중한 경험과 지식을 기다리고 있습니다.

Page 3: 『Effective Unit Testing』 - 맛보기

개발자를 위한 단위 테스트

Page 4: 『Effective Unit Testing』 - 맛보기

4

라쎄 코스켈라는 Reaktor 소속의 코치, 트레이너, 컨설턴트, 프로그래머로서 고객이 성공적인

소프트웨어 제품을 만드는 데 이바지하고 있다. 그는 기업용 애플리케이션에서부터 미들웨어

제품까지 다양한 영역의 소프트웨어 프로젝트를 경험해왔다.

공인 스크럼 트레이너(Certified Scrum Trainer )이기도 한 그는 근래에는 고객팀의 생산성

을 개선하고 끊임없이 배워가는 문화를 길러주는 교육 프로그램을 제공하거나 직접 맨토링하

는 데 시간을 쏟고 있다. 또한, 리더와 관리자를 위한 컨설팅 외에도 소프트웨어 개발팀과 일하

며 프로그래밍하는 것 역시 좋아한다.

여가 시간에는 오픈 소스 프로젝트에 참여하거나 소프트웨어 개발 관련 저술 활동을 한다.

직접 블로그(http://lassekoskela.com/thoughts/)도 운영하며, 이번 『Effective Unit

Testing』은 2007년에 출간한 『Test Driven』에 이은 두 번째 작품이다. 그는 핀란드 애자일 커

뮤니티의 선구자 중 한 명으로 국제 컨퍼런스의 발표자로도 자주 얼굴을 비추고 있다.

옮긴이 이복연 [email protected]

『JUnit in Action: 단위 테스트의 모든 것』(인사이트, 2011 )을 번역했다. 고려대학교 컴퓨터

학과를 졸업하고 삼성 소프트웨어 멤버십을 거쳐 삼성 소프트웨어 연구소에 입사했다. 그동안

미디어솔루션센터에서 바다 단말 플랫폼과 소셜허브 개발에 참여했으며, 현재는 클라우드 기

반 서버/인프라 관련 업무를 진행 중이다. 소프트웨어 아키텍처 설계, 애플리케이션 라이프사

이클 관리(ALM ), 애자일 등의 분야에 관심이 많다.

지은이 소개

옮긴이 소개

Page 5: 『Effective Unit Testing』 - 맛보기

5

제품설계가 요구사항에 달아 소스코드로 서로 사맛디 아니할쌔

이런 전차로 어린 개발자가 검증하고저 홇배 이셔도

마침내 제 뜨들 시러 펴디 몯홇 하니라.

내 이랄 윙하야 어엿비 너겨 새로 책 한 권을 번역하노니

사람마다 해여 수비니겨 날로 쑤메 뼌한키 하고져 할 따라미니라.

처음 이 책을 접했을 때 가장 먼저 눈길을 사로잡았던 것은 바로 『Effective Unit Testing』이

라는 책의 제목과 테스트 가능 설계testable design라는 7장의 제목이었다.

과연 Effective라는 이름을 내걸기에 부끄럽지 않은 책일까? Effective로 시작하는 책 대부분은

유행에 흔들리지 않고 비교적 오랫동안 가치를 인정받는 듬직함을 보여준다. 단순한 따라하기

나 기교 중심의 어떻게how가 아닌, 주제에 관한 깊은 이해와 오랜 경험으로부터 우러나온 왜why

를 다루기 때문이다. 왜를 이해한다는 것은 명확한 목적의식을 가지고 행동하게 되며, 변화하

는 환경에서도 흔들리지 않는 근본적인 기준에 따라 판단하고 응용할 수 있게 됨을 의미한다.

이러한 관점에서 이 책은 전혀 부족하지 않다고 판단되었고 번역 제안을 수락한 결정적인 이유

가 되었다.

개인적으로는 2006년경에 테스트 가능 설계 안티 패턴을 정리하려던 적이 있었다. 홀대받는 테스

트가 사실은 제품 설계와 생산성에 큰 영향을 주므로 개발 초기부터 이를 고려하면 전반적인

개발 과정이 훨씬 효율적이고 부드럽게 진행된다는 점을 알리고 싶었다. 당시만 해도 개척할

게 많은 분야였던 터라 잘 다듬어서 저널이나 학술지에 내볼 요량이었다. 하지만 컨설턴트도

아니고 테스트가 주업도 아닌지라 원하는 만큼의 적절한 예제를 모으는 데 한계가 있었고, 결

옮긴이의 말

Page 6: 『Effective Unit Testing』 - 맛보기

6

국 어설픈 초안 수준에서 그만두고 말았다.1

이러한 개인적 경험 때문에 테스트 가능 설계란 주제는 큰 호기심과 아쉬운 감정을 동시에 불러일

으켰다. 그래서 이 책을 통해 내가 정리하던 것과 얼마나 유사한지, 혹은 더 발전했는지 확인해

보고 싶었다. 결국, 구성이나 내용 면에서는 많은 차이를 보였지만 큰 그림과 의도는 일맥상통

했다. 그래서 이렇게 다른 이의 결실에 편승해서라도 그때 하고자 했던 이야기를 세상에 들려

주고 싶다는 동기가 더해졌다.

좋은 설계임을 증명하려거든 자동화된 테스트를 만들어보라!

흔히 실력 좋다고 알려진 개발자 중 많은 수가 무척 복잡하여 어떻게 쓰는지 모르겠는 코드를 내놓거

나 겉은 깔끔하나 그 정도가 지나쳐서 내부 동작원리는 알기 어려운 코드를 만들어내곤 한다. 이런 코드

는 검증하기 어렵다는 치명적인 단점이 있다. 개발자 자신이 시연할 때에는 잘 돌겠지만 다른

모듈과 통합할 시점이 되면, 혹은 제품이 출시된 후 소비자에 의해 다양한 패턴으로 사용되면

서 뒤늦게 문제가 하나씩 터져 나오는 경우가 많다. 단기간에 가시적 결과를 보여줄 수는 있지

만 이렇게 발견된 문제는 개발한 당사자가 아니면 쉽게 손대지 못하니 골치 아픈 상황을 낳기

도 한다. 이들의 능력을 깎아내리려는 의도는 절대 아니다. 다만, 한 차원 높은 생산성과 안정

된 품질, 그리고 동료 개발자도 쉽게 이해하고 배울 수 있는 코드를 작성하는 쉬운 방법이 있으

나 잘 알지 못하는 듯하여 안타까울 뿐이다.

그들에게 필요한 것은 테스트에 대한 인식 전환이다. 테스트란 단순한 기능 검증 수단만이 아

닌 요구사항을 명시한 문장이요, 모듈과 API 설계 수단이며, 사용설명서이자, 최고의 인수인

계/유지보수 자료이기까지 하다. 특히 개발자의 설계역량 면에서 본다면, 디자인 패턴에만 의

지하기보다 테스트하기 쉬운 설계를 추구하는 쪽이 훨씬 탄탄한 기본기와 응용력을 길러줄 것

1 역자주_ http://wegra.org/blog/?page_id=514

Page 7: 『Effective Unit Testing』 - 맛보기

7

이라고 감히 단언할 수 있다. 여러분이 이 책을 테스트 지침서가 아닌 설계 지침서로 받아들이고

잘 따라준다면 어느덧 진일보한 자신의 실력에 놀라게 될 것이다.

아무쪼록 독자 여러분께 재미나고 유익한 책이 되길 기원하며, 책 내용 중 혹 궁금한 것이 있거

[email protected]으로 물어봐주시길 바란다. 여건이 허락하는 한 성심껏 답변하도

록 노력하겠다.

마지막으로 휴일까지 반납하며 이 책이 나오기까지 함께 고생해주신 한빛미디어 박민아님, 역

자를 믿고 번역을 맡겨주신 한동훈님, 그리고 이토록 훌륭한 지혜를 공유해준 원서의 저자 라

쎄 코스켈라에게 감사의 마음을 전한다.2

옮긴이_ 이복연

2 편집자주_ 더불어 이 책의 베타리딩에 참여해주신 한빛리더스 이정재님께 감사드립니다.

Page 8: 『Effective Unit Testing』 - 맛보기

8

2009년 6월 10일 밤, 매닝Manning의 크리스티나 러들로프Christina Rudloff로부터 한 통의 메일을 받

았다. 그녀는 로이 오쉐로브Roy Osherove의 저서 『The Art of Unit Testing in .NET』1을 자바Java

에 맞게 다시 써줄 만한 사람을 찾고 있었다. 기꺼이 내가 해주겠노라 답했다.

이건 꽤 오래전의 이야기로, 지금 여러분의 손에 들려 있는 이 책은 로이의 것과는 사뭇 다른

모습이 되었다. 설명을 해보자면 이렇다.

처음에는 .NET을 자바로 단순 변환하는 일로 시작했다. 달라진 플랫폼과 도구만 반영하고 독

자에게 꼭 필요한 부분만 다시 쓰면서 말이다. 1장을 마치고 2장을 마치고 3장까지 써내려 가

다가 문득 하나를 깨달았다. 문장 몇 개 정도가 아니라 한 장chapter 전체를 내가 새로 쓰고 있는

게 아닌가? 문체도 나와 맞지 않았다. 로이와는 다른 방식을 선호하거나 그의 설명에 동의하지

못할 때도 종종 있었다. 여러분에게 따로 꼭 하고 싶은 말이 있거나, 설명을 보충하거나, 기초

부터 확실히 다지고 넘어가고 싶다는 욕심이 강하게 생겨나기도 했다.

결국, 처음부터 새로 시작하기로 했다.

처음 의도했던, 자바 버전에 맞게 쓰는 일을 넘어선 시점이었다. 좋은 테스트를 작성하는 방법

과 주의 사항에 관한 심도 있는 통찰을 통해 자바 프로그래머의 테스트 능력을 키워주는 새롭

고 독특한 책으로 거듭난 것이다. 로이의 생각도 다양한 형태로 이 책에 스며있다. 예컨대 2부

를 구성하는 4, 5, 6장의 이름은 로이의 것을 고스란히 차용했고, 7장도 상당 부분은 로이의 책

『The Art of Unit Testing in.NET』의 관련 내용에 기초하여 썼다.

이 책은 자바 개발자를 위해 쓰였지만, 독자 범위를 인위적으로 제한하고 싶지는 않았다. 비록

2부의 예제 코드는 모두 자바지만, 내용 자체는 언어에 너무 국한되지 않게 하려 노력했다. 좋

은 테스트를 작성하는 방법은 언어와 무관하니 설령 여러분의 주 언어가 자바가 아니더라도 이

1 저자주_ 번역서 『.NET 예제로 배우는 단위 테스트』 (송인철/황인석 역, 인사이트, 2010)

지은이의 말

Page 9: 『Effective Unit Testing』 - 맛보기

9

책의 내용을 진지하게 읽어보기를 진심으로 권한다.

같은 맥락에서, 이 책을 JUnit 또는 개인적으로 선호하는 특정 Mock 객체 라이브러리를 배우

기 위한 튜토리얼tutorial로 만들고 싶지 않았다. 시시때때로 변하는 기술, 또는 출판 후 몇 달이

면 폐기될 지식은 배제하고, 나 자신이 정말 읽고 싶은 책을 쓰고 싶었다. 눈 감고도 사용할 만

큼 이미 익숙해져 버린 테스트 프레임워크나 쓰지도 않는 Mock 객체 라이브러리를 억지로 들

이대지 않는 그런 책 말이다. 그래서 특정 기술에 종속된 조언은 최대한 자제하였다. 물론 전혀

없다고 말할 순 없지만, 최선을 다했음을 알아줬으면 한다. 테스트 작성과 실행, 유지보수, 개

선에 빠져서는 안 될 기본적인 개념과 의미 있는 논의가 필요한 정도만을 담아내었다.

다시 한번 말하지만, 나 자신이 읽고 싶은 책을 쓰려고 노력했다. 여러분도 함께 즐겨주길 바라

고, 무엇보다 책 내용 중 일부라도 실천하는 독자가 나와준다면 더 바랄 것이 없다.

지은이_ 라쎄 코스켈라

Page 10: 『Effective Unit Testing』 - 맛보기

10

대상 독자

이 책은 개발 경험의 많고 적음을 떠나 단위 테스트의 품질을 높이고자 하는 모든 자바 프로그

래머를 위해 쓰였다. 부록에 JUnit 테스트 프레임워크 사용법을 넣긴 했지만, 책의 주목적은

이미 테스트 프레임워크를 하나쯤 사용할 줄 아는 자바 프로그래머가 더 좋은 테스트를 만들도

록 이끌어주는 것이다. 여러분이 지금껏 작성한 테스트가 아무리 많더라도, 더 발전할 가능성

은 아직 남아 있다고 확신한다. 더불어 이 책은 그동안 명확하게 정립되지 못한 채 여러분 머릿

속에만 떠돌던 많은 생각을 자극해줄 것이다.

로드맵

『Effective Unit Testing』은 하나로 아울러 다루기에는 적합하지 않은 다각적인 문제를 다루

고 있다. 그래서 여러 차례 실패를 거듭한 끝에 책을 크게 세 부분으로 나눠 구성하는 것이 가

장 좋겠다는 결론을 얻었다.

긴 여정의 시작을 알리는 1부는 우리가 이루고자 하는 목적과 이를 프로젝트 초기부터 고려해

야 하는 이유를 알려준다. 좋은 테스트를 작성하기 위해 꼭 필요한 도구와 간단한 지침을 포함

하여 총 세 개의 장으로 구성했다.

1장에서는 자동화된 단위 테스트의 가치를 역설한다. 프로그래머 생산성에 영향을 주는 여러

요소를 생각해보고, 잘 작성된 자동화된 테스트가 생산성에 어떻게 이바지하는지 혹은 기운 빠

지는 일을 예방해주는지를 설명한다.

2장에서는 좋은 테스트의 조건을 정의한다. 여기에서 살펴볼 속성은 2부에서 본격적으로 다룰

테스트의 가독성, 유지보수성, 신뢰성을 이해하기 위한 기초 개념이다.

잠시 숨도 돌릴 겸, 3장에서는 좋은 테스트를 작성하기 위한 필수 도구인 테스트 더블을 소개

한다. 테스트 더블을 그저 사용할 줄 아는 것보다는, 주의해서 올바르게 활용할 줄 아는 것이

목표다. 테스트 더블이 만병통치약은 아니기 때문이다.

이 책에 대하여

Page 11: 『Effective Unit Testing』 - 맛보기

11

2부에서는 주의해야 할 테스트 냄새1를 쭉 나열한다. 테스트 작성 시 경계해야 할 패턴을 설명

하고, 그런 낌새를 느꼈을 때 적용할 수 있는 해결책을 제시할 것이다. 세부 주제는 크게 3개

다. 가독성을 떨어뜨리는 냄새, 잠재적으로 유지보수 악몽을 일으키는 냄새, 마지막은 신뢰성

을 떨어뜨리는 냄새다. 사실 여기서 다루는 냄새 중 상당수는 세 범주 어디에나 속할 수 있지

만, 그중 가장 두드러지는 효과가 무엇이냐를 기준으로 배치했다.

4장에서는 테스트 의도나 구현을 모호하게 하는 냄새를 집중적으로 공략한다. 테스트 코드 속

의 판독하기 어려운 단언문, 적절치 못한 추상화, 흩어진 정보 등을 다루게 된다.

5장에서는 끊임없는 야근을 강요하는 테스트 냄새를 살펴본다. 엉망으로 작성한 단위 테스트

는 조금만 손보려 해도 끝도 없는 수정을 유발하기도 하고, 사소한 수정 하나가 수백 개의 다른

테스트까지 고쳐야 하는 상황을 초래하기도 한다. 그리하여 테스트에서의 코드 중복, 조건부

로직, 그리고 파일시스템을 건드렸을 때의 악몽을 자세히 설명한다. 테스트 속도를 느려지게

하는 냄새도 여기 포함된다. 시간은 돈이다.

테스트 냄새의 마지막인 6장에서는 잘못된 가정 때문에 고생했던 경험으로부터 깨달은 지혜를

공유한다. 테스트 코드에 잘못 쓰인 주석 때문에, 혹은 우리 자신의 의도를 분명하게 표현하지

못해서 생겨난 가정을 살펴볼 것이다.

3부의 제목은 자칫 고급 주제가 될 뻔했다. 하지만, 1부와 2부의 내용을 숙지하지 않았더라도 자

바 개발자가 테스트를 작성하며 언제든 살펴볼 수 있는 주제다. 결국 좋은 단위 테스트를 만드

는 조건은 거의 예외 없이 우리가 처한 상황에 따라 달라진다. 그래서 어떤 프로그래머에게 아

주 시급한 주제가 다른 이에게는 하찮은 문제라 해도 전혀 놀랄 일이 아니다. 테스트 클래스의

상속 관계나 테스트 작성에 쓰인 프로그래밍 언어 혹은 빌드 인프라가 테스트를 실행하는 방식

에 기인한 문제 등 그 어떤 것도 예외는 아니다.

1 역자주_ 테스트 냄새란 테스트 코드에서 주로 나타나는 코드 냄새다. 즉 테스트를 이해하고 관리하기가 어렵다거나 결과의 정확성이 떨어지는 등 개선의 여지가 있을 수 있다는 징후다.

Page 12: 『Effective Unit Testing』 - 맛보기

12

7장에서는 2장에서 미처 다루지 못했던 테스트 가능 설계를 만드는 요소로 꾸며보았다. 유용한

원칙 몇 개와 우리가 생각하는 이상적인 모듈화 설계에 관해서 간략히 훑고, 테스트 불가 설계

라는 기본적인 테스트 가능성 문제를 공부한다. 마지막으로는 테스트 가능 설계로 이끌어줄 간

단한 지침을 준비해두었다.

8장에서는 ‘단위 테스트를 자바가 아닌 언어로 작성하고 싶다면?’이라는 질문으로 시작한다.

오늘날의 자바 가상 머신에서는 자바 외에도 수많은 언어를 사용할 수 있고 순수 자바 코드와

연동하는 것도 가능하다.

9장에서는 서서히 느려지는 빌드 속도와 그와 함께 늦어지는 피드백 주기라는, 현실에서 흔하

게 겪는 상황을 고민해본다. 테스트 코드와 인프라, 즉 안팎 모두에서 돌파구를 찾아보았다. 코

드 측면에서는 실행 속도를 개선하는 방법을, 인프라 측면에서는 더 빠른 하드웨어를 도입하는

것, 혹은 기존 하드웨어를 그대로 두고 작업 할당 방식을 바꿔보는 방법 등을 살펴본다.

JUnit의 독보적인 명성과 업계 표준 단위 테스트 프레임워크라는 위치에도 불구하고, 모든 자

바 프로그래머가 이에 익숙한 것은 아니다. 그래서 JUnit의 진보된 기능을 충분히 활용하지 못

했던 독자를 위해 부록 두 개를 수록해뒀다.

부록 A에서는 JUnit을 이용하여 테스트를 작성하고 실행하는 방법을 간단히 설명한다. 이 부록

을 읽고 나면 JUnit API를 활용해서 테스트를 만들고 단언문을 쓰기가 한층 수월해질 것이다.

부록 B는 JUnit의 기능을 확장할 때 필요한 API라는 다소 깊이 있는 주제를 다룬다. JUnit의

상세 내용을 전부 다루겠다는 욕심은 없고, 단지 확장 시에 가장 일반적으로 쓰이는 두 가지 방

법인 규칙rule과 러너runner를 소개하고, 기본 규칙 몇 개를 선보이는 선에서 마무리하려 한다. 이

규칙은 그 자체로도 물론 유용하지만, 사용자 확장을 통해 어떤 일까지 더 할 수 있을지 감을

잡아줄 것이다.

코드 작성 규칙 및 소스코드 내려받기

이 책에 등장하는 코드 예제에는 자바 소스코드, 마크업 언어, 출력 리스트가 있다. 길이가 길

Page 13: 『Effective Unit Testing』 - 맛보기

13

어지면 이름(헤더)을 붙여 별도의 코드로 제공하고, 짧은 코드는 본문 안에 그대로 두었다. 모

든 예제는 고정폭 글꼴로 표기하여 본문과 구분했다. 긴 코드에서는 본문에서 참조하기 좋도록

번호 붙은 주석을 사용하기도 한다.

소스코드는 www.hanb.co.kr/exam/2062에서 내려받을 수 있다. 책에서 선별한 소스코드

만 담아 놨으니, 여러분이 그 이상으로 발전시켜보기 바란다.

예제 코드는 자바 6로 작성했으니 호환 버전을 설치하기 바란다. 필요한 자바 환경은 www.

oracle.com에서 받을 수 있다. 컴파일까지 해보려면 JRE가 아닌 JDK를 설치하는 걸 잊지

말자.

선호하는 IDE도 설치해두면 편하다. 최신 버전의 이클립스2나 IntelliJ 아이디어(IDEA,

www.jetbrains.com ), 넷빈즈3 중 가장 마음에 드는 것을 설치하자. 여러분 손에 익은 도구

라면 무엇을 선택하건 상관없다.

책을 다 읽은 후엔?

이 책에서 얻은 통찰로 여러분의 단위 테스트는 눈에 띄게 향상될 것이다. 물론 긴 여정이 될

것이고 우리가 예측하지 못했거나 완벽히 답하지 못한 질문이 한두 개 나올 수 있다. 다행히도

그 여정에는 수많은 친구가 함께할 것이고, 그중 많은 이들이 테스트 코드의 미묘한 차이를 주

제로 기꺼이 토론하고 공유할 것이다.

매닝에서는 매닝의 저자와 이야기할 수 있는 온라인 포럼을 마련해두고 있다. 이 책도 마찬

가지다. www.manning.com/EffectiveUnitTesting에 열어둔 저자 온라인 포럼을 방문해

보자.

Yahoo! Groups의 testdrivendevelopment와 extremeprogramming은 테스트 추종 프로

2 저자주_ Eclipse, www.eclipse.org

3 저자주_ NetBeans, www.netbeans.org

Page 14: 『Effective Unit Testing』 - 맛보기

14

그래머 간의 논의가 끊이지 않는 포럼이다. 단위 테스트만 다루는 포럼은 아니지만, 관련 주제

를 논하기에 훌륭한 장소다. 테스트 코드 외적인 새로운 생각도 접할 좋은 기회가 될 것이다.

개발자 테스트에 더 충실한 포럼을 찾는다면 http://www.coderanch.com의 CodeRanch

를 방문해서 Testing 포럼을 찾아보라. 이곳에서도 좋을 사람을 많이 사귈 수 있을 것이다.

하지만, 여러분이 짠 테스트 코드를 놓고 옆의 동료와 적극적으로 토론해보는 것이 그 무엇보

다도 더 효율적인 실천법임을 말해두고 싶다. 개인적으로도 옆에서 함께 고민하던 동료가 나의

코드에 대한 최고의 통찰을 일깨워준 경우가 많다.

Page 15: 『Effective Unit Testing』 - 맛보기

15

『Effective Unit Testing』 표지 그림의 제목은 크로아티아 달마티아 드리니스에서 온 사내A Man from

Drnis, Dalmatia, Croatia다. 이 그림은 니콜라 아르세노비크Nikola Arsenovic가 2003년 크로아티아의 민족

학 박물관Ethnographic Museum을 통해 출간한, 19세기 중반 이후 크로아티아인의 전통 복장을 담은

앨범에서 빌려 왔다. 그 책에는 크로아티아의 다른 지역의 복식과 일상생활에 관해서도 정밀

하게 채색된 삽화를 곁들여 설명하고 있다. 한편, 이 박물관은 AD 304년경 디오클레티아누스

Diocletian 황제가 퇴임 후 지냈던 궁전의 잔해가 위치한 로마의 중세 도시 안에 지어졌다.

드리니스drnis는 중세 고성의 잔해 위에 세워진 달마티아Dalmatia 섬의 작은 마을이다. 표지 그림

의 사내는 푸른색 울wool 바지와 하얀 섬유 셔츠, 지역 전통 복장이었던 색색의 자수가 놓인 푸

른색 울 조끼를 입고 있다. 손에는 긴 파이프를 들고 어깨에는 진홍빛 재킷을 대충 걸치고 있

다. 그리고 빨간 모자와 가죽 모카신moccasin으로 복장을 완성했다.

앨범 속의 시대 이후 200여 년이 흐르면서, 변화하는 복장 규정과 생활 양식에 맞춰 다채롭던

지역색도 점차 퇴색되었다. 이제는 몇 마일 떨어지지 않은 작은 마을이나 도시 사람은 말할 것

도 없고, 심지어 다른 대륙에 사는 사람 간에도 차이가 크지 않다. 우리는 아마도 문화와 외향

적 다양성을 더 다양하고 빠르게 변모하는 첨단 기술 생활이라는 개인의 다양성과 맞바꾸어 왔

으리라.

매닝은 2세기 전의 풍성했던 지역적 다양성을 빌려 컴퓨터 사업의 독창성과 창의력을 찬미하

고자, 이처럼 오래된 책과 모음집의 삽화를 다시 세상 밖으로 끄집어내어 표지 그림으로 활용

하고 있다.

표지 설명

Page 16: 『Effective Unit Testing』 - 맛보기

16

CONTENTS

옮긴이 소개 .................................................................................................................... 4

지은이 소개 .................................................................................................................... 4

옮긴이의 말 .................................................................................................................... 5

지은이의 말 .................................................................................................................... 8

이 책에 대하여 .............................................................................................................. 10

표지 설명 ..................................................................................................................... 15

PART 1 기반 다지기

CHAPTER 1 좋은 테스트의 약속

1.1 더 좋은 테스트를 작성하기 위한 현황 점검 ......................................................................... 29

1.2 테스트의 가치 ............................................................................................................... 30

1.2.1 생산성에 영향을 주는 요소 ...................................................................................... 34

1.2.2 설계 잠재력 곡선 ................................................................................................... 36

1.3 설계 수단으로써의 테스트 ............................................................................................... 37

1.3.1 테스트 주도 개발 ................................................................................................... 37

1.3.2 행위 주도 개발 ...................................................................................................... 39

1.4 요약 ............................................................................................................................ 41

CHAPTER 2 좋은 테스트란?

2.1 읽기 쉬운 코드가 유지보수도 쉽다 .................................................................................... 45

2.2 구조화가 잘 되어 있다면 이해하기 쉽다 ............................................................................. 47

2.3 엉뚱한 걸 검사하는 건 좋지 않다 ...................................................................................... 50

2.4 독립적인 테스트는 혼자서도 잘 실행된다 ........................................................................... 51

Page 17: 『Effective Unit Testing』 - 맛보기

17

2.5 믿음직한 테스트라야 기댈 수 있다 .................................................................................... 54

2.6 모든 일이 그렇듯 테스트에도 도구가 쓰인다 ....................................................................... 56

2.7 요약 ............................................................................................................................ 57

CHAPTER 3 테스트 더블

3.1 테스트 더블의 위력 ........................................................................................................ 61

3.1.1 테스트 대상 코드를 격리한다 ................................................................................... 62

3.1.2 테스트 속도를 개선한다 .......................................................................................... 64

3.1.3 예측 불가능한 실행 요소를 제거한다 ......................................................................... 64

3.1.4 특수한 상황을 시뮬레이션한다 ................................................................................. 65

3.1.5 감춰진 정보를 얻어낸다 .......................................................................................... 66

3.2 테스트 더블의 종류 ........................................................................................................ 68

3.2.1 테스트 스텁은 유난히 짧다 ...................................................................................... 68

3.2.2 가짜 객체는 뒤끝 없이 처리한다 ............................................................................... 70

3.2.3 테스트 스파이는 기밀을 훔친다 ................................................................................ 71

3.2.4 Mock 객체는 예기치 않은 일을 막아준다 .................................................................. 74

3.3 테스트 더블 활용 지침 .................................................................................................... 76

3.3.1 용도에 맞는 더블을 선택하라 ................................................................................... 76

3.3.2 준비하고, 시작하고, 단언하라 ................................................................................... 77

3.3.3 구현이 아니라 동작을 확인하라 ................................................................................ 78

3.3.4 자신의 도구를 선택하라 .......................................................................................... 79

3.3.5 종속 객체를 주입하라 ............................................................................................. 80

3.4 요약 ............................................................................................................................ 81

Page 18: 『Effective Unit Testing』 - 맛보기

18

PART 2 테스트 냄새

CHAPTER 4 가독성

4.1 기본 타입 단언 .............................................................................................................. 87

4.1.1 예시 .................................................................................................................... 87

4.1.2 개선 방법 ............................................................................................................. 88

4.1.3 정리 .................................................................................................................... 90

4.2 광역 단언 ..................................................................................................................... 91

4.2.1 예시 .................................................................................................................... 91

4.2.2 개선 방법 ............................................................................................................. 94

4.2.3 정리 .................................................................................................................... 96

4.3 비트 단언 ..................................................................................................................... 96

4.3.1 예시 .................................................................................................................... 97

4.3.2 개선 방법 ............................................................................................................. 98

4.3.3 정리 .................................................................................................................... 98

4.4 부차적 상세정보 ............................................................................................................ 99

4.4.1 예시 .................................................................................................................... 99

4.4.2 개선 방법 ........................................................................................................... 101

4.4.3 정리 .................................................................................................................. 103

4.5 다중 인격 ................................................................................................................... 103

4.5.1 예시 .................................................................................................................. 104

4.5.2 개선 방법 ........................................................................................................... 104

4.5.3 정리 .................................................................................................................. 108

4.6 쪼개진 논리 ................................................................................................................ 108

4.6.1 예시 .................................................................................................................. 109

4.6.2 개선 방법 ........................................................................................................... 111

CONTENTS

Page 19: 『Effective Unit Testing』 - 맛보기

19

4.6.3 정리 .................................................................................................................. 114

4.7 매직 넘버 ................................................................................................................... 115

4.7.1 예시 .................................................................................................................. 115

4.7.2 개선 방법 ........................................................................................................... 116

4.7.3 정리 .................................................................................................................. 117

4.8 셋업 설교 ................................................................................................................... 117

4.8.1 예시 .................................................................................................................. 118

4.8.2 개선 방법 ........................................................................................................... 119

4.8.3 정리 .................................................................................................................. 121

4.9 과잉보호 테스트 .......................................................................................................... 121

4.9.1 예시 .................................................................................................................. 122

4.9.2 개선 방법 ........................................................................................................... 123

4.9.3 정리 .................................................................................................................. 123

4.10 요약 ........................................................................................................................ 123

CHAPTER 5 유지보수성

5.1 중복 .......................................................................................................................... 127

5.1.1 예시 .................................................................................................................. 127

5.1.2 개선 방법 ........................................................................................................... 128

5.1.3 정리 .................................................................................................................. 131

5.2 조건부 로직 ................................................................................................................ 131

5.2.1 예시 .................................................................................................................. 132

5.2.2 개선 방법 ........................................................................................................... 133

5.2.3 정리 .................................................................................................................. 134

5.3 양치기 테스트 ............................................................................................................. 134

5.3.1 예시 .................................................................................................................. 135

5.3.2 개선 방법 ........................................................................................................... 136

Page 20: 『Effective Unit Testing』 - 맛보기

20

5.3.3 정리 .................................................................................................................. 138

5.4 파손된 파일 경로 ......................................................................................................... 138

5.4.1 예시 .................................................................................................................. 139

5.4.2 개선 방법 ........................................................................................................... 140

5.4.3 정리 .................................................................................................................. 142

5.5 끈질긴 임시 파일 ......................................................................................................... 143

5.5.1 예시 .................................................................................................................. 143

5.5.2 개선 방법 ........................................................................................................... 144

5.5.3 정리 .................................................................................................................. 146

5.6 잠자는 달팽이 ............................................................................................................. 147

5.6.1 예시 .................................................................................................................. 147

5.6.2 개선 방법 ........................................................................................................... 148

5.6.3 정리 .................................................................................................................. 150

5.7 픽셀 퍼펙션 ................................................................................................................ 151

5.7.1 예시 .................................................................................................................. 151

5.7.2 개선 방법 ........................................................................................................... 153

5.7.3 정리 .................................................................................................................. 157

5.8 파라미터화된 혼란 ....................................................................................................... 158

5.8.1 예시 .................................................................................................................. 158

5.8.2 개선 방법 ........................................................................................................... 162

5.8.3 정리 .................................................................................................................. 165

5.9 메서드 간 응집력 결핍 .................................................................................................. 166

5.9.1 예시 .................................................................................................................. 166

5.9.2 개선 방법 ........................................................................................................... 168

5.9.3 정리 .................................................................................................................. 171

5.10 요약 ........................................................................................................................ 172

CONTENTS

Page 21: 『Effective Unit Testing』 - 맛보기

21

CHAPTER 6 신뢰성

6.1 주석으로 변한 테스트 ................................................................................................... 177

6.1.1 예시 .................................................................................................................. 177

6.1.2 개선 방법 ........................................................................................................... 178

6.1.3 정리 .................................................................................................................. 179

6.2 오해를 낳는 주석 ......................................................................................................... 180

6.2.1 예시 .................................................................................................................. 180

6.2.2 개선 방법 ........................................................................................................... 181

6.2.3 정리 .................................................................................................................. 183

6.3 절대 실패하지 않는 테스트 ............................................................................................ 183

6.3.1 예시 .................................................................................................................. 184

6.3.2 개선 방법 ........................................................................................................... 184

6.3.3 정리 .................................................................................................................. 185

6.4 지키지 못할 약속 ......................................................................................................... 186

6.4.1 예시 .................................................................................................................. 186

6.4.2 개선 방법 ........................................................................................................... 189

6.4.3 정리 .................................................................................................................. 190

6.5 낮아진 기대치 ............................................................................................................. 191

6.5.1 예시 .................................................................................................................. 191

6.5.2 개선 방법 ........................................................................................................... 193

6.5.3 정리 .................................................................................................................. 193

6.6 플랫폼 편견 ................................................................................................................ 194

6.6.1 예시 .................................................................................................................. 194

6.6.2 개선 방법 ........................................................................................................... 195

6.6.3 정리 .................................................................................................................. 198

6.7 조건부 테스트 ............................................................................................................. 198

6.7.1 예시 .................................................................................................................. 199

Page 22: 『Effective Unit Testing』 - 맛보기

22

6.7.2 개선 방법 ........................................................................................................... 200

6.7.3 정리 .................................................................................................................. 201

6.8 요약 .......................................................................................................................... 201

PART 3 여흥거리

CHAPTER 7 테스트 가능 설계

7.1 테스트 가능 설계란? .................................................................................................... 207 7.1.1 모듈러 설계 ........................................................................................................ 208

7.1.2 SOLID 설계 원칙 ................................................................................................ 209

7.1.3 맥락을 고려한 모듈러 설계 .................................................................................... 211

7.1.4 모듈러 설계를 위한 시운전 .................................................................................... 211

7.2 테스트 불가 원인 ......................................................................................................... 212

7.2.1 클래스 생성 불가 ................................................................................................. 212

7.2.2 메서드 호출 불가 ................................................................................................. 213

7.2.3 결과 확인 불가 .................................................................................................... 214

7.2.4 협력 객체 대체 불가 ............................................................................................. 214

7.2.5 메서드 오버라이딩 불가 ........................................................................................ 215

7.3 테스트 가능 설계를 위한 지침 ........................................................................................ 216

7.3.1 복잡한 private 메서드를 피하라 ............................................................................. 216

7.3.2 final 메서드를 피하라 ........................................................................................... 216

7.3.3 정적 메서드를 피하라 ........................................................................................... 217

7.3.4 new는 신중하게 사용하라 .................................................................................... 218

7.3.5 생성자에서는 로직 구현을 피하라 ........................................................................... 219

7.3.6 싱글톤을 피하라 .................................................................................................. 221

CONTENTS

Page 23: 『Effective Unit Testing』 - 맛보기

23

7.3.7 상속보다는 컴포지션을 사용하라 ............................................................................ 223

7.3.8 외부 라이브러리를 감싸라 ..................................................................................... 223

7.3.9 서비스 호출을 피하라 ........................................................................................... 224

7.4 요약 .......................................................................................................................... 226

CHAPTER 8 제2의 JVM 언어를 활용한 테스트 작성

8.1 JVM 언어 혼용의 조건 ................................................................................................. 231 8.1.1 일반적인 이점 ..................................................................................................... 231

8.1.2 테스트 작성하기 .................................................................................................. 234

8.2 그루비로 단위 테스트 작성하기 ...................................................................................... 235

8.2.1 간소화된 테스트 셋업 ........................................................................................... 236

8.2.2 그루비로 작성한 JUnit 4 테스트 ............................................................................ 238

8.3 BDD 도구의 뛰어난 표현력 ........................................................................................... 240

8.3.1 easyb로 작성한 그루비 명세 ................................................................................. 240

8.3.2 테스트의 표현력을 높여주는 스폭 ........................................................................... 242

8.3.3 스폭의 또 다른 무기, 테스트 더블 ........................................................................... 244

8.4 요약 .......................................................................................................................... 246

CHAPTER 9 테스트 속도 개선

9.1 속도 개선을 위해서 ...................................................................................................... 251 9.1.1 더 빠르게! .......................................................................................................... 251

9.1.2 상황 속으로 ........................................................................................................ 252

9.1.3 빌드 프로파일링하기 ............................................................................................ 252

9.1.4 테스트 프로파일링하기 ......................................................................................... 256

9.2 테스트 코드 속도 높이기 ............................................................................................... 259

Page 24: 『Effective Unit Testing』 - 맛보기

24

9.2.1 피곤하지 않다면 잠들지 말라 ................................................................................. 260

9.2.2 덩치 큰 기반 클래스를 경계하라 ............................................................................. 260

9.2.3 불필요한 셋업과 티어다운을 경계하라 ..................................................................... 263

9.2.4 테스트에 초대할 손님은 까다롭게 선택하라 .............................................................. 265

9.2.5 로컬하게, 그리고 빠르게 유지하라 .......................................................................... 266

9.2.6 데이터베이스의 유혹을 뿌리쳐라 ............................................................................ 268

9.2.7 파일 I/O보다 느린 I/O는 없다 ................................................................................ 269

9.3 빌드 속도 높이기 ......................................................................................................... 272

9.3.1 램 디스크를 활용한 초고속 I/O .............................................................................. 273

9.3.2 빌드 병렬화하기 .................................................................................................. 274

9.3.3 고성능 CPU에 짐 떠넘기기 ................................................................................... 280

9.3.4 빌드 분산하기 ..................................................................................................... 283

9.4 요약 .......................................................................................................................... 288

Appendix 부록

Appendix A JUnit 기초

A.1 기본적인 JUnit 테스트 케이스 ................................................................................. 293

A.2 JUnit 단언문 ........................................................................................................ 295

Appendix B JUnit 확장하기

B.1 러너를 통해 테스트 동작 제어하기 ............................................................................ 301

B.2 규칙으로 테스트 꾸미기 .......................................................................................... 303

B.3 기본 규칙들 .......................................................................................................... 303

CONTENTS

Page 25: 『Effective Unit Testing』 - 맛보기

25

기반 다지기

Part

I1부는 여러분과의 첫 대면임을 생각해서 서로 간의 공감대를 확고히 형성하기 위한 내용으로 꾸며보았다. 이

책의 궁극적인 목표인 ‘좋은 테스트를 작성하는 능력 키우기’를 위한 첫 과정으로, 1장에서는 테스트를 작성해

서 얻을 수 있는 이점이 무엇인지 살펴본다. 이어서 프로그래머 생산성에 영향을 주는 요소를 알아보고, 그중

에서 특히 테스트와 테스트 품질이 미치는 영향에 관해 집중적으로 이야기할 것이다. 마무리로는 자동화된 테

스트와 밀접한 주제인 ‘테스트 주도 개발’( TDD)과 ‘행위 주도 개발’( BDD)을 소개하겠다.

2장에서는 좋은 테스트가 갖춰야 할 요건을 정의한다. 간단히 말해 가독성, 유지보수성, 신뢰성이 우수한 테스

트를 작성해야 한다. 이 주제는 2부에서 더 깊게 다뤄지는데, 원치 ‘않는’ 모습의 테스트라는 다른 관점에서 바

라본 예제를 검토하게 될 것이다.

1부의 마지막인 3장에서는 오늘날 프로그래머의 필수 도구로 떠오른 테스트 더블을 배워본다. 먼저, 테스트

더블로 할 수 있는 일을 알아본다. 예를 들어 테스트를 효율적으로 하기 위해 테스트 더블로 코드를 격리할 수

있다. 이어서 우리가 사용할 수 있는 테스트 더블의 종류별 차이점을 알아본다. 마지막으로는 테스트 더블의

장점을 최대한 살릴 수 있는 올바른 활용 지침을 준비하였다.

1부를 모두 읽고 나면 여러분이 작성하고 싶은 테스트의 모습과 그렇게 만들고 싶은 이유가 머릿속에 그려질

것이다. 더불어 그런 테스트를 만들 때 유용한 테스트 더블도 명확하게 이해하게 될 것이다. 이상의 기반을 토

대로 나머지 2부와 3부에서는 실무에 바로 활용할 수 있는 기술과 비법을 한 아름 안겨줄 것이다.

Page 26: 『Effective Unit Testing』 - 맛보기
Page 27: 『Effective Unit Testing』 - 맛보기

27

좋은 테스트의 약속

CHAPTER 1

이 장의 내용

● 단위 테스트의 가치

● 개발자 생산성을 향상하는 테스트

● 설계 도구로써의 테스트

Page 28: 『Effective Unit Testing』 - 맛보기

28 Part I 기반 다지기

내가 전문 프로그래머로 사회에 첫발을 내디뎠을 때만 해도 지금과는 사뭇 다른 세상이었다. 바

야흐로 10년도 더 지난 과거의 이야기다. 사람들은 이클립스 Eclipse나 넷빈즈 NetBeans, 아이디어 IDEA

와 같은 통합 개발 환경이 아닌 빔 Vim이나 이맥스 Emacs 같은 텍스트 편집기로 코드를 작성하고

있었다. 이맥스 매크로를 마구 휘둘러 수천, 수만의 System.out.println 문을 생성해대며 디

버깅하던 직장 선배의 모습이 아직도 눈에 선하다. 그리고 주문이 제대로 처리되지 않는 버그

덕분에, 그 많은 println이 내뱉은 로그를 해독하느라 고생하던 나의 모습은 더욱 생생하다.

프로그래머 대부분이 테스트를 코딩이 끝나면 다른 사람이 해주는 일 혹은 코딩을 다 끝냈다고

선언하기 전에 자신이 짠 코드를 이리저리 찔러보는 일 정도로 치부하던 시절이다. 그러다가

버그가 발견되면, 잘못된 곳을 찾기 위해 로그를 조금씩 추가하며 코드를 들쑤시는 모습이 친

근한 일상이었다.

자동화는 당시의 우리에게는 신선한 개념이었다. 컴파일과 패키징 정도는 메이크파일makefile로

자동화해뒀지만, 빌드할 때마다 테스트까지 함께 돌리지는 않았다. 그 대신에 한두 개의 테스

트 클래스를 실행하는 셸 스크립트를 여러 개 만들어 썼다. 테스트 클래스라고는 했지만, 사실은

제품 코드를 실행해서 결과를 화면에 뿌려주는 간단한 애플리케이션에 지나지 않았다. 실패한

단언 모두를 알려주는 오늘날의 테스트 프레임워크와 자가검증self-verifying 테스트와는 비교할 바

가 못 된다.

그 후로 많은 시간이 흘렀다.

좋은 테스트의 약속

CHAPTER 1

Page 29: 『Effective Unit Testing』 - 맛보기

29CHAPTER 1 좋은 테스트의 약속

1.1 더 좋은 테스트를 작성하기 위한 현황 점검

자동화된 테스트는 개발자가 직접 작성해야 하고, 그중 하나라도 실패하면 빌드 전체가 실패한

것이란 인식은 이제 꽤 널리 퍼져있다. 나아가, 테스트 선행 방식의 프로그래밍을 실천하는 개

발자 수도 점점 늘고 있다. 테스트 선행 방식에서는 자동화된 테스트를 단순한 오류 예방 목적

으로뿐 아니라, 코딩 전에 그 코드에 기대하는 동작을 정의하는 설계 보조 수단으로까지 활용

한다. 구현까지 다 끝나야 검증을 시작하는 게 아니라 애초에 설계부터 검증하고 들어가는 것

이다.

개인적으로는 컨설턴트로 활동하면서 수많은 팀과 조직, 제품, 코드를 접해왔다. 그런데 요즘 주

변을 둘러보면 자동화된 테스트는 이미 주류로 편입된 것이 확실하다. 자동화된 테스트 없이는

대부분의 소프트웨어 프로젝트의 상황이 지금보다 훨씬 열악했을 것이 분명하니 확실히 올바

른 길로 나아가는 중이다. 자동화된 테스트는 여러분의 생산성을 높이고 개발 속도를 빠른 상

태로 유지해준다.1

도와줘! 단위 테스트는 처음이란 말이야.

자동화된 테스트 작성에 익숙하지 않은 독자라면 바로 지금이 배울 수 있는 절호의 기회다. 매닝(Manning)에

서는 자바 단위 테스트계의 사실상 표준 프레임워크인 JUnit 관련 책을 다수 내놓았다. 그중에서도 『JUnit in

Action』1은 POJO( Plain Old Java Object)부터 엔터프라이즈 자바빈즈( Enterprise JavaBeans)에 이르는

온갖 자바 코드를 테스트하는 방법을 가르쳐주는 훌륭한 참고서가 될 것이다.

자바와 JUnit만 처음일 뿐 단위 테스트 자체는 좀 작성해보았다면 ‘부록 A - JUnit 기초’를 가볍게 훑어보는

정도면 이 책에 담긴 지혜를 모두 얻어가는 데 부족함은 없을 거다.

자동화된 테스트가 널리 퍼졌다고 해서 테스트 커버리지가 매우 높다거나 개발 생산성을 더 이

상 끌어올리기 어렵다는 의미는 절대 아니다. 사실 나는 지난 5년여 동안 다른 개발자에게 테

스트를 작성하게 하고, 구현에 앞서 테스트부터 만들게 했으며, 특히 더 좋은 테스트를 작성하

게 도와주며 많은 시간을 보냈다.

테스트를 더 잘 작성해야 한다고 이토록 강조하는 이유는 뭘까? 테스트 품질을 등한시하면 어

떤 일이 벌어질까? 이제부터 테스트가 주는 가치와 테스트 품질의 중요성에 관하여 본격적으

1 저자주_ 피터 타치브 등, 매닝, 2010. 번역서 『JUnit in Action 단위 테스트의 모든 것』 (이복연 역, 인사이트, 2011)

Page 30: 『Effective Unit Testing』 - 맛보기

30 Part I 기반 다지기

로 이야기를 시작해보겠다.

1.2 테스트의 가치

촉망받는 프로그래머 마커스를 소개한다. 그는 2년 전에 대학을 졸업하고, 지역 투자 은행의

IT 부서에 입사하여 온라인 셀프서비스 웹 애플리케이션을 개발해왔다. 입사 초기에는 팀의 막

내로서 은행 관련 지식을 쌓고 선배들이 일하는 방식을 파악하는 데 힘썼다.

입사 후 몇 달도 채 지나지 않아, 마커스는 팀이 프로그래머의 실수2를 바로잡기 위한 재작업에

상당한 시간을 허비하고 있다는 사실을 알아차렸다. 지금껏 수정해온 실수의 유형을 주의 깊게

살펴보니, 그 대부분은 간단한 단위 테스트만으로도 쉽게 잡을 수 있을 것 같았다. 그래서 마커

스는 실수하기 쉬워 보이는 코드를 찾아 이곳저곳에 단위 테스트를 작성해 넣기 시작했다.

테스트는 실수를 바로잡아준다.

시간이 흐르자 다른 팀원 역시 서서히 단위 테스트를 작성하기 시작했다. 마커스는 이른바 테스

트 추종자3가 되었고, 그가 작성한 코드 대부분은 자동화된 테스트로 매우 높은 커버리지를 달성

하였다. 오류 수정에 쏟는 시간이 늘어난 것도 아닌데 미해결 결함의 수는 점차 줄어들었다. 테

스트가 팀의 작업 품질에 가시적인 영향을 주기 시작한 것이다.

마커스가 코드베이스에 첫 번째 테스트를 추가한 이후로 근 1년이 흘렀다. 마커스는 크리스마

스 파티 장소로 향하며 지금까지 경험한 변화를 되짚어보았다. 그런데 팀 전체의 테스트 커버리

지는 계속 빠르게 상승해오다가 최근 몇 주에서 몇 달간은 98%를 정점으로 큰 변화가 없었다.

마커스는 한동안 이 수치를 100%까지 끌어올려야 한다는 생각에 사로잡혀 있었다. 하지만 최

근 몇 주 사이에 마음을 바꿔먹었다. 아직 채워지지 않은 테스트가 가져다줄 가치가 그리 크지

않아서 테스트 작성에 힘을 더 쏟아봤자 돌아오는 소득이 별로 없어 보였다. 아직 검증되지 않

은 코드는 마커스 팀에서는 사용하지 않는 인터페이스를 구현해야 하는 API가 대부분이었다.

이런 껍데기 메서드를 검사해서 무엇하겠는가?

2 저자주_ ‘오류( error)’, ‘결함( defect)’, ‘버그( bug)’, ‘문제( issue)’라고도 한다.

3 저자주_ 테스트 추종자(test-infected)란 용어는 1998년 Java Report에 실린 에릭 감마( Erich Gamma)와 켄트 벡의 기고문 『Test-Infected: Programmers Love Writing Tests』 (테스트 추종자: 테스트 작성을 사랑하는 프로그래머들)에서 처음 사용되었다.

Page 31: 『Effective Unit Testing』 - 맛보기

31CHAPTER 1 좋은 테스트의 약속

100% 코드 커버리지 달성이 중요한 게 아니다.

95%보다는 100%가 물론 듣기 좋다. 다만, 그 차이는 그리 크지 않을 수 있다. 테스트의 가치는 테스트가 확인

하지 못한 코드가 어떤 것인가와 테스트가 프로그래밍 실수를 얼마나 정확하게 잡아내는가에 좌우된다. 100%를 달성했다고 해서 결함이 없다고 보장해주는 건 아니다. 애플리케이션 동작이 올바른가와는 상관없이, 그저 모

든 코드를 한 번씩은 실행해보았다는 것만을 보장할 뿐이다. 그러니 커버리지에 대한 강박관념은 버리고 의미 있

는 테스트를 작성하는 데 집중하기 바란다.

그림 1-1 테스트 수가 많아질수록 새로 추가한 테스트의 가치는 떨어진다. 왜냐하면, 개발자

는 일반적으로 가장 핵심적인, 이를테면 가장 중요하고 위험 부담이 큰 코드를 먼저 검사하기

때문이다. 그와 달리 대부분의 테스트를 작성한 후에도 확인하지 않은 코드는 십중팔구 가장

사소하고 문제될 소지가 적은 부분이다.

테스트 작성에 쏟아 부은 노력

테스트의 가

마커스의 팀은 고지에 올라선 것이다. 고지란 [그림 1-1]에서 곡선의 평평한 윗부분을 말하는

데, 투자한 노력 대비 돌아오는 이익이 감소하는 구간이다. 개인적으로 자바 사용자그룹에서

만난 많은 팀이 이와 같은 현상을 경험해보았다고 말했다.

마커스의 생각을 바꿔준 사람은 세바스찬이었다. 그는 이전에 마커스가 일하는 투자 은행에도

컨설팅해준 바 있는 경험 많은 소프트웨어 아키텍트였다. 세바스찬은 셀프서비스 팀에 합류한

직후 마커스를 비롯한 새내기 팀원의 멘토가 되어주었다. 그는 웹 애플리케이션 개발에 많이

쓰이는 프로그래밍 언어에도 능숙했지만, 마커스가 단위 테스트를 작성하는 방식에 특히 많은

영향을 주었다.

지금까지 마커스는 새 코드 작성 시 단위 테스트 작성까지 마친 후 버전 관리 시스템에 체크인

하는 습관을 길러왔다. 그런데 세바스찬의 방식은 좀 특이했다. 실패할 게 뻔한 테스트를 먼저

하나 만들고, 그 테스트가 성공할 만큼의 코드를 작성했다. 그리곤 다시 실패할 테스트를 또 하

Page 32: 『Effective Unit Testing』 - 맛보기

32 Part I 기반 다지기

나 작성하는 식이다. 작업을 완료할 때까지 계속 이런 식이었다.

세바스찬과 함께하며 마커스는 자신의 프로그래밍 방식도 조금씩 개선됨을 느꼈다. 객체를 구

성하는 방식도 바뀌었고 완성된 코드도 어딘가 달라 보였다. 코드의 설계와 기능을 사용자의

시각에서 바라보기 시작한 까닭이다.

테스트는 실사용에 적합한 설계를 끌어내준다.

마커스는 마치 머릿속에서 전구가 ‘팍!’ 하고 켜진 것 같은 느낌이었다. 그는 달라진 프로그래밍

방식과 결과를 말로 표현하는 과정에서, 테스트가 단순한 품질 보증이나 실수 재발 방지 수단

만은 아님을 깨달았다. 테스트는 분명 코드를 설계하는 한 방식이기도 했다. 실패할 테스트를 거쳐

간 코드는 분명 필요한 기능을 모두 담고 있으면서도 이전 방식으로 작성한 코드보다 훨씬 간

결했다.

테스트는 원하는 동작을 명확히 알려주어 군더더기(gold-plating )를 없애준다.

이야기의 주인공인 마커스의 경험은 실제로 많은 테스트 추종 개발자가 테스트를 개발하며 깨

닫고 이해하는 과정을 그린 것이다. 처음에는 시간을 좀먹는 부끄러운 실수를 예방할 생각으로

자동화된 단위 테스트를 작성하기 시작한다. 그리고 시간이 지날수록 안전장치로써의 역할은

전체 그림의 일부일 뿐임을 깨닫게 된다. 오히려 테스트를 코딩에 접목하면서 경험하는 사고

과정이 더 가치 있을 것이다.

자동화된 단위 테스트는 개발자가 직접 작성해야 한다는 인식은 이미 널리 퍼져있지만, 어떤

테스트를 얼마나 많이 작성해야 하는가에 대한 인식은 여전히 부족하다. 개인적인 컨설팅 경험

에 비추어봐도 테스트의 필요성을 이해하지 못하는 프로그래머는 거의 없었다. 하지만 제품 코

드의 모든 경로를 훑고 있음을 의미하는 100% 코드 커버리지 달성을 주장하는 프로그래머는

거의 없었다.

여기서 얻을 수 있는 결론은 무엇일까? 테스트를 작성해야 한다는 데에는 누구나 동의하지만,

커버리지가 100%에 근접해질수록 테스트를 더 작성해야 하는가에 대해서는 확신이 줄어든다.

작성할 테스트를 거의 다 작성했다면 나머지 코드에서 문제가 발견될 가능성은 낮아지기 때문

이다. 이것이 바로 수확 체감 diminishing return이라는 것으로 [그림 1-1]의 곡선이 잘 나타내주고

있다.

결국, 신중히 고민해서 만든 첫 100개의 테스트로는 개선 효과가 피부로 느껴지겠지만, 이미

Page 33: 『Effective Unit Testing』 - 맛보기

33CHAPTER 1 좋은 테스트의 약속

30,000개나 갖춰진 상태에서는 100개를 더 만드는 것으로는 변화를 체감하기 어렵다. 하지만

마커스가 멘토를 통해 깨달은 것처럼 여기서 끝이 아니다. 우리 앞에는 아직 두 번째 고지가 남아

있다.

테스트를 작성해서 얻게 되는 가장 큰 수확은 테스트 자체가 아니다. 작성 과정에서 얻는 깨달음이다.

마커스 이야기에서는 테스트를 바라보는 두 가지 인식을 강조했다. 두 개의 고지란 바로 그 각각

의 인식을 기반으로 테스트를 작성했을 때 도달할 수 있는 최고 경지를 뜻한다. 물론 테스트의

잠재 가치를 모두 일깨우려면 [그림 1-2]의 고지 두 곳을 모두 점령해야 한다.

그림 1-2 처음 도달할 수 있는 낮은 고지에 이르면 테스트를 추가하고 개선해도 더 이상의 가

치를 끌어내기 어려워진다. 그 위의 두 번째 고지는 테스트가 단순한 검증 수단 이상임을 깨닫

는 사고의 전환을 통해야만 발견할 수 있는 더 높은 경지다.

테스트를 설계 수단으로이용할 경우

테스트를 품질 보증 수단으로만 이용할 경우

테스트 작성에 들인 노력

테스트의 가

테스트를 품질 보증 수단으로만 인식하는 한 얻을 수 있는 잠재적 가치는 [그림 1-2]의 낮은

곡선을 벗어날 수 없다. 사고의 틀을 깨고 테스트를 프로그래밍 도구, 즉 설계 도구로 인식하는

순간 품질 고지를 뛰어넘어 설계 고지에 도달하는 길이 열리게 된다.

불행하게도 테스트로 설계한다는 인식 전환 단계를 통과하지 못한 개발자가 훨씬 많아서 대부분

의 제품 코드는 첫 번째 곡선 어딘가에 머물러 있다. 마찬가지로 개발자는 커지는 테스트 스위

트 test suite 관리 비용에도 별로 관심을 두지 않는다. 이쯤에서 질문이 하나 떠오른다. 프로그래머

생산성에 영향을 주는 요소에는 무엇이 있고, 그 안에서 단위 테스트의 역할은 무엇일까?

Page 34: 『Effective Unit Testing』 - 맛보기

34 Part I 기반 다지기

1.2.1 생산성에 영향을 주는 요소테스트를 빠르게 작성하려면 무엇이 중요한가는 신경 쓰지 않고 열심히 타이핑만 하면 된다.

하지만 많은 사람들이 테스트를 작성하느라 시간을 쏟고, 중복을 없애고, 구조와 명료성은 물

론 유지보수에까지 신경을 쓰는 데는 그만한 이유가 있다.

테스트 코드는 제품 코드보다 간결한 게 보통이다.4 그렇다 해서 테스트 코드를 허투루 작성하

고 문제를 심어 놓으면, 나중에 분명 발목 잡힐 날이 올 거다. 중복도 많고 쓸데없이 복잡한 테

스트 코드는 생산성을 떨어뜨리고 테스트의 긍정적 효과마저 앗아간다는 걸 명심하자.

엉터리로 작성한 테스트 코드는 가독성만이 아니라 안정성과 신뢰성, 실행 속도에도 악영향을

준다. [그림 1-3]은 개발자 생산성에 영향을 주는 요소의 역학 구조를 테스트 관점에서 바라본

모습이다.

생산성에 직접 영향을 주는 피드백 주기와 디버깅 시간에 주목해보자. 경험상 이 두 가지가 가장

유력한 야근의 요정이다.5 실수하자마자 곧바로 알아차릴 수만 있다면 지루한 디버깅을 상당수

피할 수 있지만, 피드백 주기가 길어질수록 디버깅 시간도 길어지게 마련이다.

그림 1-3 테스트는 다방면에 걸쳐 생산성에 직간접적인 영향을 준다.

피드백 주기의길이

개발자생산성

디버깅결함

테스트신뢰성

테스트 결과의정확성 테스트

안정성

단축시킨다.

감소시킨다.

피하게한다.

예방한다.

예방한다.

유발한다.

향상시킨다.

향상시킨다.

감소시킨다.

테스트실행 속도

테스트가독성

4 저자주_ 예를 들어 조건문이나 반복문을 사용하는 테스트는 많지 않다.

5 저자주_ 생산성 저하의 끝판왕은 회의라고들 얘기하지만, 그에 관해서는 다른 책을 찾아보길 바란다.

Page 35: 『Effective Unit Testing』 - 맛보기

35CHAPTER 1 좋은 테스트의 약속

버그 수정 비용의 증가

버그 수정 비용은 평균적으로 얼마나 될까? 런던에서 열린 ‘XP Day 2009 컨퍼런스’에서 마크 스트리에벡

(Mark Striebeck)은 구글에서 결함을 수정하기 위해 지연된 시간을 비용으로 계산해서 공개하였다.

구글이 측정한 바로는 프로그래머가 버그를 만들자마자 즉시 수정한다면 $5를 쓴 것이다. 같은 결함을 프로

젝트 전체 빌드 때 발견하면 비용은 $50가 된다. 만약 통합 테스트까지 살아남으면 $500로 증가하며 시스템

테스트에 이르면 $5,000까지 치솟는다.

이 수치만 봐도 문제는 가능한 한 빨리 발견해야 한다는 건 이론의 여지가 없다.

테스트 실행 속도는 변경사항을 검증하고 확인validation and verification하기 위해 기다리는 시간에 직결

된다. 이 역시 주요한 요소라 그림에서도 굵게 강조해 두었다. 나머지 주요 요소 세 개는 모두

프로그래머가 해야 할 디버깅의 양과 관련이 있다.

가독성이 떨어지면 자연스럽게 분석이 더뎌지고 디버거를 사용해야 할 상황까지 만들 수 있다.

테스트 코드를 읽어봐도 무슨 말인지 이해하기 어렵기 때문이다. 이처럼 가독성이 안 좋으면

실수한 곳을 찾기 어려워 더 많은 결함이 만들어지고, 늘어난 결함은 디버깅 시간 증가로 이어

진다.

테스트 결과의 정확성도 발견하지 못하고 놓치는 결함의 수에 많은 영향을 준다. 실수로 결함을

만들어도 테스트 스위트가 찾아줄 거라며 믿고 의지하고 싶다면 정확성이라도 반드시 보장되

어야 한다. 생산성에 영향을 주는 나머지 요인인 신뢰성 trustworthiness과 안정성 reliability이 바로 이 테

스트 정확성과 직접 연관되어 있다. 테스트 결과를 믿고 싶다면 테스트가 약속한 것을 확실히

잡아내고 몇 번을 수행해도 항시 같은 결과가 나오도록 만들어야 한다.

이상의 생산성 역학 구조를 잘 이해하면 여러분도 품질 고지에 올라설 수 있다. 바로 이 요소들

이 개발자 생산성을 올려주는 핵심이기 때문이다. 또한, 생산적인 개발자가 되려면 마땅히 필

요한 도구도 능숙하게 다룰 줄 알아야 한다. 프로그래밍 언어의 기초를 다 익혔다면 표준 API

도 둘러보자. 문제에 대해 충분히 파악했다면 그 근본 원인을 파헤쳐보자. 바로 가독성, 안정

성, 신뢰성, 그리고 테스트 속도 말이다.

이들 원인은 책의 나머지 대부분에서 풀고자 하는 숙제이기도 하다. 즉 읽기 쉽고, 신뢰가 가고,

안정된 테스트 코드를 식별하는 능력을 키워주고, 그러한 테스트 코드를 만들고 관리할 수 있

도록 해주겠다.

Page 36: 『Effective Unit Testing』 - 맛보기

36 Part I 기반 다지기

본격적인 논의에 앞서 [그림 1-2]의 두 번째 곡선이 뜻하는 바를 생각해보자.

1.2.2 설계 잠재력 곡선제품 코드의 가장 대표적인 시나리오와 구조상 가장 치명적인 부분을 먼저 검사했다고 치자.

테스트 품질도 상급이고 철저한 리팩토링으로 중복까지 말끔히 제거해서 가볍고 유지보수도

쉬워졌다. 이어서 나머지 주요 기능을 모두 검사하고 getter/setter 등의 사소한 기능만 남았

다. 테스트 커버리지가 높은 것은 당연하다. 이 상태에서 마지막 남은 사소한 테스트는 작성해

봤자 큰 가치가 없다. 이는 수확 체감에 다다랐다는 신호로, 단지 테스트만 작성해서 얻을 수 있

는 가치의 한계점에 도달한 것이다.

이곳이 바로 일반적인 방식으로 도달할 수 있는 최고점인 품질 고지다. 그 한계를 뛰어넘어 한 차

원 높은 생산성을 맛보고자 한다면 환골탈태를 거쳐야 한다. 즉, 지금까지 가지고 있던 테스트

에 관한 사고의 틀을 깨야만 한다. 완전히 색다른 테스트의 가치를 찾아내야 숨어 있는 잠재력

을 끌어낼 수 있다. 실수가 반복되는 것을 예방한다는, 방어적이고 검증 지향적인 가치가 아닌,

더 창조적이고 설계 지향적인 가치가 바로 그것이다.

요약하면, 테스트의 잠재 가치를 전부 끌어내고 두 고지를 모두 정복하려면 다음처럼 하면 된다.

1 테스트 코드도 제품 코드를 다루듯 하라. 믿고 의지할 수 있을 만큼 철저하게 리팩토링하고 높은 품질을 유지하라.

2 테스트를 제품 코드가 목적과 쓰임새에 적합한 구조가 되게끔 이끌어주는 설계 수단으로 활용하라.

프로그래머 대부분이 잘 지키지 못하는 쪽은 첫 번째다. 테스트를 작성하고 관리하고 실행하는

비용을 최소화하는 동시에 고품질의 테스트 코드를 작성하는 걸 힘들어한다. 다행히도 이는 본

책에서 집중해서 다룰 주제인 좋은 테스트 작성하기의 영역이다.

달리 말하면, 설계 수단으로써의 테스트 쪽에는 많은 지면을 할애하지 않겠다는 뜻이다.6 그래

도 전반적인 역학과 작동 원리는 이해할 수 있도록 1.3절을 준비했다. 그럼 한번 시작해보자.

6 저자주_ 필자가 저술한 『Test Driven』 (매닝, 2007)은 이 주제에 딱 맞는 책이다. 스티브 프리먼(Steve Freeman)과 넷 프라이스(Nat Pryce)가 저술한 『Growing Object-Oriented Software, Guided by Tests』 (Addison-Wesley, 2009)를 참고해도 좋다.

Page 37: 『Effective Unit Testing』 - 맛보기

37CHAPTER 1 좋은 테스트의 약속

1.3 설계 수단으로써의 테스트

프로그래머가 작성한 자동화된 테스트는 전통적으로 크게 두 가지 목적을 위한 품질보증 수단

이었다. 첫째는 코드를 작성하는 즉시 정확하게 구현했는지 검사하는 것이고, 둘째는 그 후에

코드베이스가 커져도 계속 잘 동작하는지 지속해서 확인하는 것이다. 설계하고, 설계대로 코딩

하고, 생각대로 잘 구현되었는지 확인하는 것으로, 테스트를 검증 수단으로 활용하는 전형적인

모습이다.

자동화된 테스트를 설계 수단으로 이용하기 시작하면 모든 것이 뒤바뀐다. 테스트를 코드 설계

용으로 사용하면 익숙했던 설계, 코딩, 테스트의 순서가 테스트, 코딩, 설계로 바뀐다. 잘못 읽은 게

아니다. 테스트가 코딩보다 선행되고 설계로 마무리된다. 마지막의 설계 단계는 리팩토링이라는

이름으로 더 유명하다. 결과적으로 [그림 1-4]와 같은 테스트 → 코딩 → 리팩토링의 순서가 된다.

그림 1-4 테스트 주도 개발 프로세스는 실패하는 테스트를 작성하는 것으로 시작하여, 테스트를 성공하게 만드는 코드를 작성하고, 작성된 코드의 설계를 개선하는 리팩토링으로 마무리된다.

테스트 코딩

(반복)

리팩토링

지금 하는 얘기가 친숙하게 느껴진다면 아마도 테스트 선행 프로그래밍이나 테스트 주도 개발에 대해

들어봐서일 것이다.7 마침 우리도 그 얘기를 하는 중이니 원래의 이름대로 부르기로 하자.

1.3.1 테스트 주도 개발[그림 1-5]의 테스트 주도 개발 TDD은 간단한 아이디어에서 시작된 프로그래밍 훈련법이다. ‘코

드가 갖춰야 할 기능을 명시하는, 실패하는 테스트 없이는 코드를 작성하지 않는다.’ 이것이 바

로 테스트 선행 프로그래밍이라 불리는 이유다.

7 저자주_ 혹은 몇 페이지 전에 읽은 마커스와 세바스찬 이야기 때문일 지도 모르겠다.

Page 38: 『Effective Unit Testing』 - 맛보기

38 Part I 기반 다지기

그림 1-5 테스트 주도 개발에서는 실패하는 테스트 작성하기, 테스트 통과하기, 중복을 줄이고 의도를 명확하

게 하기 위한 코드 리팩토링을 반복한다. 그리고 도중에 문제가 생기지는 않았는지 확인하기 위해 매 단계 테스

트를 실행한다.

새로운 테스트를작성한다.

모든 테스트를실행한다.

모든 테스트를실행한다.

리팩토링이전으로복원한다.

모든 테스트를실행한다.

테스트 통과?

테스트 통과?제품 코드와테스트 코드를리팩토링한다.

테스트 통과?

시작

아니요

아니요

아니요테스트를만족시키는제품 코드작성한다.

테스트를 먼저 작성하면 소위 테스트로 설계한 제품 코드가 만들어진다. 여기에는 다음과 같은

긍정적인 효과가 따라온다.

● ‘사용 가능한 코드가 만들어진다.’ 즉, 제품 코드의 설계와 API가 활용 시나리오에 적합한 모습으로 거듭난다.

● ‘코드가 가벼워진다.’ 실제로 활용할 시나리오에서 요구하는 기능만을 담게 된다.

첫째, 프로젝트 진척도나 필요한 다른 컴포넌트, 클래스, 인터페이스가 갖춰져 있는지와 상관

없이 여러분의 손에는 항시 명확한 사용 시나리오가 딸린 설계만이 쥐어져 있게 된다. 더구나,

그 시나리오는 자동화된 단위 테스트라는 구체적이고 실행 가능한 형태로 구현되어 있다. 그래

서 테스트에 실패한다는 것은 테스트가 통과하도록, 아니 딱 통과할 정도만 제품 코드를 손봐

주라는 명확한 목표가 되어준다.

실행할 수 있는 테스트 코드로 시나리오를 그려내는 일은 분명 설계 영역에 속한 활동이다. 테

스트 코드는 제품 코드를 사용하는 고객의 입장이 되어 코드 한 줄 쓰기도 전에 설계가 올바로

되었는지 검증해준다. 요구사항을 구체화한 이 예제는 아주 강력한 검증 도구로, 오직 테스트

를 설계 수단으로 활용할 때만 얻을 수 있는 이점이기도 하다.

Page 39: 『Effective Unit Testing』 - 맛보기

39CHAPTER 1 좋은 테스트의 약속

둘째, 테스트를 통과할 만큼만 코딩한다는 규칙을 잘 따라주면 설계를 간결하면서도 목적에 딱

맞도록 유지할 수 있다. 시나리오를 갖추지 못한 코드가 단 한 줄도 없으므로 버릴 게 하나도

없다. 반면 우발적 복잡성accidental complexity은 코드 품질을 떨어뜨리는 가장 악명 높은 적이자 개발

자 생산성을 갉아먹는 주 요인이다.

우발적 복잡성이란 쓸데없이 복잡한 것을 말한다. 다시 말해 요구조건은 그대로 만족하면서도

더 단순한 설계로 대체할 수 있다는 뜻이다. 프로그래머는 종종 자신도 이해하기 어려운 복잡

한 설계를 만들어서 자신이 얼마나 똑똑한지 보여주고 싶어한다. 여러분 내면에도 이와 같은

원초적 본능이 꿈틀대고 있을 것이다. 하지만 설계가 복잡해질수록 생산성이 떨어지는 건 당연

하므로, 불필요하게 복잡하게 만드는 건 정말 쓸데없고 비생산적이다.

결함이나 빠진 기능을 명시하는 테스트, 테스트를 통과할 만큼의 코드만 작성한다는 규칙, 그

리고 간결한 설계8를 지향하는 철저한 리팩토링은 우발적 복잡성을 크게 줄여준다. 물론 만병

통치약이 될 순 없다. 최종 결과는 결국 프로그래머의 설계 감각과 경험에 크게 좌우되기 때문

이다.

TDD는 사실 책 몇 권은 거뜬히 써낼 수 있을 만큼 거대한 큰 주제다. 필자가 저술한 『Test

Driven』과 스티브 프리먼과 넷 프라이스가 최근에 쓴 『Growing Object-Oriented

Software, Guided by Tests』 등이 그 예다. 개인적으로는 제목부터 간결한 켄트 벡Kent Beck의

『Test Driven Development: By Example』9를 추천한다.

TDD 관련 자료는 주변에서 쉽게 찾을 수 있으니 본 책에서는 여기까지만 이야기할 테니, 더

깊이 있는 설명이 필요한 독자는 직접 구해보길 바란다. 다만 TDD 관련 영역 중에서도 우리의

핵심 주제인 좋은 테스트와 밀접한 행위 주도 개발은 그냥 지나칠 수 없었다.

1.3.2 행위 주도 개발 행위 주도 개발 BDD, Behavior-driven development이란 말을 들어봤는지 모르겠다. 사람들이 테스트 선

행 방식으로 프로그래밍하기 시작한 건 족히 수십 년은 되었지만, 우리가 알고 있는 테스트 주

8 저자주_ 간결한 설계( simple design)는 필요한 기능마저 수행하지 못할 만큼 지나치게 단순화한 설계( simplistic design)와는 전혀 다른 개념이다.

9 저자주_ Addison-Wesley, 2002. 번역서 『테스트 주도 개발』 (김창준/강규영 역, 인사이트, 2005)

Page 40: 『Effective Unit Testing』 - 맛보기

40 Part I 기반 다지기

도 개발은 1990년대에 와서야 이름이 생기고 체계를 갖추게 되었다.

약 10여 년이란 세월이 더 흘러, 댄 노스Dan North라는 런던의 컨설턴트는 차세대 테스트 선행 프

로그램을 주도하는 선두에 서게 된다. 그는 TDD에서 말하는 테스트라는 용어가 사람들을 잘못

된 길로 인도함을 깨달았다. 그래서 자신만의 TDD 방식을 만들고 행위 주도 개발이라는 이름

을 붙였다. 그는 2006년 『Better Software』에 기고한 글에서 행위 주도 개발을 다음과 같이

설명하였다.

문득 TDD에 대한 사람들의 오해 대부분은 테스트라는 단어 때문이라는 생각이 스쳤다.

테스트가 TDD의 본질이 아니라고 주장하는 게 아니다. 테스트 메서드가 코드의 동작을 보

장하는 효과적인 방법인 건 확실하다. 그런데 그 테스트가 시스템의 행위를 올바로 기술하

지 못한다면, 분명 잘못이 있는데도 개발자는 아무 문제 없다고 믿는 상황을 만든다.

나는 TDD에서 테스트가 들어가야 할 자리를 행위 Behavior로 바꿔 사용하기 시작했다. 그러자

어휘적으로도 쏙 와 닿을 뿐 아니라 다른 사람을 가르치며 자주 받았던 질문들이 거짓말처

럼 해결되었다. 나는 이제 TDD와 관련된 질문 몇 가지의 해답을 알게 되었다. 테스트의 이

름을 짓는 건 정말 쉬워졌다. 검사하려는 행위를 기술하는 문장이면 된다. 테스트할 범위를

정하는 문제의 답도 간단하다. 답은 한 문장으로 기술할 수 있는 만큼의 행위다. 테스트가

실패하면 어떻게 해야 할까? 앞서 기술한 방식만 잘 따르면 된다. 새로운 버그가 만들어졌

건 행위가 바뀌었건 테스트가 더는 유효하지 않게 되었건 달라질 건 전혀 없다.

생각의 초점을 테스트에서 행위로 옮기는 것이 대단한 발견임을 깨달은 후부터 나는 TDD

를 BDD 혹은 행위 주도 개발이라 부르기 시작했다.

댄이 BDD라는 용어를 들고 와 전파하기 시작하자 전 세계 소프트웨어 개발 커뮤니티의 개발자

가 예제 주도 example-driven, 행위명세 지향 specification-of-behavior oriented 등 댄의 BDD를 구체화한 아

이디어를 발전시켜 나갔다. 나아가 요즘에는 코딩 외의 분야에서도 BDD라는 용어가 사용되

고 있다. 대표적으로는 요구 사항 단계의 비즈니스 분석과 명세화 시에 BDD를 사용한다.

BDD 실천자가 보장하는 특이한 개념으로 인수 테스트를 통한 ‘외부로부터의’ 개발이 있다. 『The

Cucumber Book: Behaviour-Driven Development for Testers and Developers』의

저자인 매트 윈Matt Wynne과 애슬랙 헬러조이Aslak Hellesøy의 설명을 들어보자.

행위 주도 개발은 최고의 TDD 실천자들의 좋은 습관을 정형화하여 만들어낸 TDD 기반

Page 41: 『Effective Unit Testing』 - 맛보기

41CHAPTER 1 좋은 테스트의 약속

방법론이다. 최고의 TDD 실천자들은 외부로부터의 개발 방식을 사용한다. 즉, 그들이 가

장 먼저 작성하는, 실패하는 테스트는 시스템의 행위를 고객의 관점에서 서술한 인수 테스

트라고 할 수 있다. BDD 실천자로서 우리는 팀원 누구라도 쉽게 읽고 이해할 수 있도록 신

경 써서 인수 테스트를 작성한다. 이렇게 작성된 사용 예를 비즈니스 관계자에게 건네서 본

격적인 구현에 앞서 우리가 만들려는 방향이 올바른지 확인받는다.

이미 BDD 관련 도구와 프레임워크도 다수 만들어져 그 사상과 실천법과 규약 등을 소프트웨

어 개발 환경에 녹여내고 있는 모습을 보면 BDD가 발전하고 있는 것은 확실해 보인다. 그에

관해서는 8장에서 소개할 생각이다.

앞으로 좋은 테스트란 단어가 나올 때마다 댄의 깨달음을 상기하고 단어에 유념하기 바란다. 어

휘는 생각보다 중요하다.

1.4 요약

프로그래머가 마구잡이식 코딩에서 벗어난 지는 한참이 지났다. 개발자 테스트와 자동화된 테스

트는 이미 우리 일상으로 자리 잡았고, 표준화까지는 아니더라도 활발하게 논의 중인 주제임은

분명하다. 코드 전반을 보호해주는 자동화된 테스트 스위트의 가치를 더는 부정할 수 없다.

1장에서 여러분은 두 개의 고지가 무엇인지 배웠다. 이미 매우 높은 커버리지를 달성하여 테스

트를 더 만들어봐야 얻을 게 거의 없는 지점이 바로 첫 번째 고지다. 하지만 우리는 더 높이 오

를 수 있다. 테스트의 품질까지 신경 쓴다면 첫 번째 고지를 박차고 올라 더 위로 올라설 수 있

다. 전자가 테스트를 갖춰야 하는 이유라면 후자는 좋은 테스트를 갖춰야 하는 이유다. 이것이

여러분이 이 책을 산 이유고 2장 이후로 함께 도전할 과제다.

중도에서 포기하면 아무것도 얻을 게 없다. 함께 꼭 정상까지 올라가보자.

Page 42: 『Effective Unit Testing』 - 맛보기
Page 43: 『Effective Unit Testing』 - 맛보기

43

좋은 테스트란?

CHAPTER 2

이 장의 내용

● 무엇이 “좋은” 테스트를 만드는가?

● 테스트를 대하는 태도

● 신뢰할 수 있는 테스트의 중요성

Page 44: 『Effective Unit Testing』 - 맛보기

44 Part I 기반 다지기

지금 우리는 좋은 테스트에 관해 배우려 한다. 좋은 테스트를 구분하고, 좋은 테스트를 직접 만

들 것이다. 또 그저 그런 테스트라도 좋은 테스트로, 최소한 그에 근접한 모습으로 개선하는 방

법을 배울 것이다. 그렇다면 무엇이 테스트를 좋게 만드는 것일까? 어떤 비결이 숨어 있을까?

다음과 같은 고려 사항이 있다.

● 테스트 코드의 가독성과 유지보수성

● 프로젝트 안에서, 그리고 소스 파일 안에서 코드는 적절히 구조화되어 있는가?

● 테스트가 무엇을 검사하는가?

● 테스트는 안정적이고 반복 가능한가?

● 테스트가 테스트 더블을 잘 활용하는가?

2장에서는 이 모두를 자세히 살펴볼 것이다.

아쉽게도 이 목록은 아직 완벽하지 않다. 테스트 품질에 영향을 주는 요소를 모두 나열하자면

끝도 없기 때문이다. 더구나 어떤 요소는 상황에 따라 별로 중요하지 않을 수도 있다. 예를 들

면 실행 속도가 중요한 테스트도 있는 반면 작은 기능 범위만 집중적으로 검증하는 것이 더 중

요한 테스트도 있다.

또한, 테스트 코드의 품질을 평가하는 기준도 사람마다 다르다. 코드가 좋다는 건 보통 개인 취

향에 좌우되고 나 또한 그런 개인 중 한 사람일 뿐이다. 그래서 이 책도 나의 편견과 편애에서

완전히 벗어날 수 없는 것이 현실이다. 개인 취향과는 거리를 두려고 노력했지만, 한쪽으로 치

좋은 테스트란?

CHAPTER 2

Page 45: 『Effective Unit Testing』 - 맛보기

45CHAPTER 2 좋은 테스트란?

우친 부분이 있을지도 모르겠다. 그렇다고 이게 꼭 나쁘다고 생각하진 않는다. 결국, 내가 독

자에게 줄 수 있는 최선의 가치는 그동안 훌륭한 소프트웨어 전문가들과 함께하며 경험한 코드

(특히 테스트 코드)에 관한 나의 정직한 견해일 테니 말이다.

이러한 한계를 미리 밝혀두고, 이제 테스트 품질의 여러 측면을 이야기하면서 관심 가질 만한

주제를 찾아보자.

2.1 읽기 쉬운 코드가 유지보수도 쉽다

어제 컨설팅을 마치고 사무실로 돌아와, 곧 열릴 1K 경연대회에 참가하는 동료와 대화를 나눴

다. 데모파티에서는 꽤 역사와 전통이 있는 대회로, 컴퓨터와 침낭, 에너지 음료 등을 챙겨온

해커들이 넓은 공간에 모여 주말 내내 무언가를 시합하는, 일종의 괴짜들 잔치다. 역사적인 첫

대회 때부터 해커들은 온갖 정신 나간 기술을 구사해서 어설픈 3D 애니메이션을 만들어내곤

했다.

이런 대회에서는 보통 무엇인가의 크기를 제한한다. 내 동료가 준비 중인 대회 역시 1K라는 이

름이 말해주듯 컴파일된 실행파일의 크기가 최대 1K (1,024바이트)를 넘지 말아야 한다. 이

작은 공간에 의미 있는 프로그램을 욱여넣기 위해 참가자들은 동원 가능한 모든 꼼수를 고민해

야 한다. 코드를 꽉꽉 눌러 담을 때 자주 쓰이는 꼼수 중 하나로, 서로 다른 여러 변수에 똑같은

이름을 쓰는 방법이 있다. 결과코드의 압축률이 살짝 더 높기 때문인데, 한마디로 정상적인 코

딩 기술을 겨루는 대회는 아니다.

결과코드 역시 비정상인 건 마찬가지다. 1,024바이트 안에 마구잡이로 쑤셔 넣은 소스코드는

해독 불가능한 괴물이 된다. 어떤 프로그래밍 언어를 사용했는지조차 알아보기 어렵다. 말 그

대로 쓰기 전용 코드가 만들어진다고 할 수 있다. 압축하고 욱여넣는 작업이 한 번 시작되면 무

엇을 어디서 어떻게 수정해야 하는지 구분할 수 없게 된다. 즉, 더 이상의 기능 변경은 포기한

다는 의미다.

따끈따끈한 예를 보여주고자 최근 열린 JS1K 경연대회에 실제로 제출된 소스코드를 준비해보

았다.1 자바 스크립트를 사용하여 1,024바이트 이하로 제품을 만들어내는 대회다.

1 역자주_ 다른 작품이 궁금하다면 http://js1k.com에 방문해보기 바란다.

Page 46: 『Effective Unit Testing』 - 맛보기

46 Part I 기반 다지기

<script>with(document.body.style){margin="0px";overflow="hidden";}

var w =window.innerWidth;var h =window.innerHeight;var ca =document.

getElementById("c");ca.width=w;ca.height=h;var c=ca.getContext("2d"); m=Math;fs=m.

sin;fc =m.cos;fm =m.max;setInterval(d,30);function p(x,y,z){ return{x:x,y:y,z:z};}

function s(a,z){r =w/10;R =w/3;b =-20*fc(a*5+t); return p(w/2+(R*fc(a)+r*fs(z+2*t))/

z+fc(a)*b,h/2+(R*fs(a))/z+fs(a)*b); }function q(a,da,z,dz){var v=[s(a,z),s(a+da,z),s(a+da,z

+dz),s(a,z+dz)]

;c.beginPath();c.moveTo(v[0].x,v[0].y);for(i in v)c.lineTo(v[i].x,v[i] .y);c.

fill();}var Z =-0.20;var t =0;function d(){t+ =1/30.0;c.fillStyle = "#000";c.

fillRect(0,0,w,h);c.fillStyle ="#f00";var n =30;var a =0;var da = 2*Math.PI/n;var

dz=0.25;for(var z=Z+8;z>Z;z-=dz){for(var i=0;i<n;i++){ fog=1/(fm((z+0.7)-3,1));if(z<=2)

{fog=fm(0,z/2*z/2);}var k=(205*(fog* Math.abs(fs(i/n*2*3.14+t))))>>0;k*=(0.55+0.45*fc((i/

n+0.25)*Math.PI*5) );k=k>>0;c.fillStyle="rgb("+k+","+k+","+k+")";q(a,da,z,dz);if(i%3= =0){ c.

fillStyle="#000";q(a,da/10,z,dz);}a+=da;}}Z-=0.05;if(Z<=dz)Z+=dz;} </script>

그렇다. 이는 너무 극단적인 예라 일반적인 소프트웨어 회사에서 경험할 법한 상황은 아니다.

그래도 이 정도까지는 아닐지라도, 현업에서도 머리를 쥐어뜯게 하는 코드는 모두 접해봤으리

라. 그런 부류의 코드를 보통 레거시 legacy, 유산라 하는데, 다른 누군가로부터 물려받아 우리가 관

리해야 하기 때문이다. 너무 난해해서 이해해보려 할 때마다 머리를 혹사해야 한다는 점에서는

조금 다르지만 말이다. 이처럼 읽기 어려운 코드는 이해하는 데만 해도 너무 많은 에너지가 소

비되기 때문에 유지보수하기가 녹록하지 않다. 그뿐만 아니라 가독성과 결함 밀도는 반비례한

다는 연구 결과도 있다.2 즉, 읽기 어려운 코드일수록 결함 수가 많다.

자동화된 테스트는 결함을 효과적으로 막아주지만, 테스트 역시 코드인지라 가독성 문제에서

벗어날 수는 없다. 읽기 어려운 코드는 검증하기도 어렵고, 결과적으로 테스트를 조금만 작성

하는 사태로까지 이어진다. 또 그렇게 작성된 테스트는 우리가 생각하는 좋은 테스트와는 거리

가 멀다. 제품의 구조와 API가 테스트를 고려하지 않고 만들어졌다면 아무리 날고 기는 테스트

작성자가 와도 결국 끔찍한 구조의 이해하기 어려운 테스트밖에 만들 수 없기 때문이다.

코드 가독성이 코드 유지보수에 지대한 영향을 끼침을 확인했다. 그렇다면 테스트 코드는 가독

성 문제에서 자유로운가? 제품 코드와는 어떻게 다른가, 아니 과연 다르긴 할까? 우선 다듬어

지지 않은 난해한 테스트 코드를 하나 살펴보자.

2 저자주_ 레이몬드 버즈( Raymond Buse), 웨스틀리 와이머( Westley Weimer) 저. 『Learning a Metric for Code Readability』. IEEE 소프트웨어 공학 분과, 2009년 11월 9일. IEEE 컴퓨터 학회 전자 도서관, http://doi.ieeecomputersociety.org/10.1109/TSE.2009.70

Page 47: 『Effective Unit Testing』 - 맛보기

47CHAPTER 2 좋은 테스트란?

코드 2-1 복잡하지는 않지만 가독성이 떨어지는 코드

@Test

public void flatten() throws Exception {

Env e = Env.getInstance();

Structure k = e.newStructure();

Structure v = e.newStructure();

//int n = 10;

int n = 10000;

for (int i = 0; i < n; ++i) {

k.append(e.newFixnum(i));

v.append(e.newFixnum(i));

}

Structure t = (Structure) k.zip(e.getCurrentContext(),

new IObject[] {v}, Block.NULL_BLOCK);

v = (Structure) t.flatten(e.getCurrentContext());

assertNotNull(v);

}

이 테스트는 무엇을 검사하려는 것일까? 여기서 무슨 일이 벌어지고 있는지 자신 있게 이야기

할 수 있는가? 여러분이 팀의 새내기라면 이 테스트의 의도로 파악하는 데 얼마나 걸릴 것 같

은가? 만일 어느 날 갑자기 테스트가 실패하기 시작한다면 어떤 수단을 동원할 텐가? 개인적인

느낌으로는 이 코드를 살펴본 사람은 누구나 여러 가지를 뜯어고치고 싶어할 것 같다. 그리고

그러한 개선의 공통 주제는 바로 가독성일 것이다.

2.2 구조화가 잘 되어 있다면 이해하기 쉽다

영광스럽게도 소스 파일 간에 매끄럽게 이어지지 못하는 코드를 접할 기회가 여러 번 있었다.

다른 파일을 전혀 참조하지 않는 경우도 있었다. 호출되는 코드까지 소스 파일에 하나에 전부

들어 있었던 까닭이다. 소스 파일이 너무 커져서 텍스트 편집기가 감당하지 못하고 죽어버린

적도 있다. 한 번은 웹 애플리케이션이 계속 에러를 내뱉기에 원인을 살펴봤다. 자바 서버 페이

지 JSP, JavaServer Pages 파일이 너무 비대해져서 컴파일된 바이트 코드의 크기가 자바 클래스파일 명

세의 허용 범위를 넘어섰다. 어이없는 경험이었다. 구조화해두면 좋다는 점뿐 아니라 제대로

구조화하지 않으면 역으로 피해를 본다는 사실도 기억해둘 필요가 있겠다.

Page 48: 『Effective Unit Testing』 - 맛보기

48 Part I 기반 다지기

이처럼 끝나지 않는 소스는 누구도 손대고 싶어하지 않았다. 가장 간단한 개념을 수정할 때조

차 어디부터 손대야 할지 찾기 어려웠다. 기댈 수 있는 구조가 전혀 없었기 때문이다. 분할정복

divide and conquer마저 불가능하여 코드 전부를 머릿속에 집어넣고 씨름하는 것 외에 다른 도리가

없었다.

[그림 2-1]에서와 같이 아무 구조나 다 코드를 이해하는 데 도움이 되는 건 아니다. 이해할 수

있는 구조가 필요한 것이다. 즉, 우리 두뇌와 사고 모델이 지지고 볶을 수 있을 정도로 정리된

구조라야 한다. 깊은 생각 없이 코드를 여러 소스 파일, 클래스, 메서드로 흩어버려도 한 번에

들여다봐야 할 코드의 양이 줄어드는 건 사실이다. 단, 당면한 주제인 프로그램 로직을 분해하

고 이해하는 데는 별다른 도움이 되지 못한다. 그래서 제대로 된 구조가 필요하다는 것이다.

그림 2-1 구조화되어 있느냐 아니냐의 단순한 문제가 아니다. 쓸모있는 구조라야 한다.

코드가 구조화되어 있지 않다.

변경된 개념을 코드에투영하기 어렵다.

사소한 변경이라도악전고투를 유발한다.

아무도 손대려하지 않는다.

코드가 좀 더구조화된다. 하지만 우리는 손을

대야만 한다.작은 조각으로나눈다.

하지만 쓸모있는 구조인가?

끝나지 않는 통짜 소스와 마주쳤을 때의 가장 확실한 대처법은 작은 조각으로 나누고 코드 뭉

텅이를 메서드로 뽑아내는 것이다. 500줄짜리 통짜 메서드라면 클래스 10개에 메서드 수십 개

정도로 재편할 수 있을 거다. 그렇다면 각 메서드는 평균 10줄 미만이 된다. 최소한 컴파일러

관점에서는 더 구조화된 게 확실하다. 또한, 화면을 스크롤하지 않고도 메서드 목록 전체를 살

펴볼 수 있게 된다.

하지만 코드가 도메인 모델이나 머릿속의 개념과 맞지 않아서 나눌 수 있는 명확한 경계를 찾을

수 없다면, 차라리 나누지 않느니만 못할 수도 있다. 하나의 개념이 여러 개의 물리적인 조각으

로 나뉜 꼴이 되어 여러 소스 파일을 이리저리 오가느라 시간만 더 낭비되기 때문이다. 사실은

간단한 문제다. 고수준 개념을 구현된 코드에 빠르고 정확하게 대입할 수 있는 구조면 된다.

테스트 코드는 여기에 딱 들어맞는 예다. 애플리케이션 전체를 자동화된 테스트 한 개로 보호하

Page 49: 『Effective Unit Testing』 - 맛보기

49CHAPTER 2 좋은 테스트란?

고 있다고 가정하자. 이 테스트는 애플리케이션의 모든 로직과 동작을 단 하나의 초대형 테스

트 메서드로 검증하며 끝나는 데까지는 약 30분이 걸린다. 그런데 애플리케이션에서 메일 주

소 표현 방식을 수정하다가 어딘가 꼬여버렸고 테스트가 실패하기 시작했다. 버그다. 자, 이제

어떻게 대응할 것인가?

그림 2-2 피드백이 늦어지면 생산성이 극도로 낮아진다.

프로그래머의실수

앞으로 30분을 기다려야 한다.

초대형 테스트

어라! 뭔가잘못됐네!

프로그램 에러를 발견해낸 테스트 코드의 정확한 위치를 찾는 데만 해도 시간이 제법 걸릴 게

뻔하다. 테스트 코드에서는 무엇이 어디에 영향을 주었는지, 사용 중인 객체는 어디서 초기화

했는지, 문제 발생 시의 변숫값은 무엇이었는지와 같은 단서를 알려줄 만한 구조를 전혀 찾아

볼 수 없다. 마침내 실수를 깨닫고 바로잡는다 해도, 문제가 확실히 해결되었고 해결 과정에서

다른 실수를 범하지는 않았는지 확인하려면 30분이나 걸리는 테스트를 처음부터 다시 돌려보

는 수밖에 없다.

이야기를 계속 이어가 보자. 한 시간쯤 흘러 다른 한 곳을 더 손보려 한다. 앞의 실수를 거울삼

아, 뜻밖의 문제가 발생하지 않게끔 이번에는 현재의 구현 상태를 정확히 이해해두는 것이 좋

겠다. 어떻게 하면 될까? 코드를 읽어보면 특히 코드 동작 방식을 구체적으로 보여주는 테스트

코드를 정독하면 도움이 된다. 다만, 테스트 코드가 제대로 구조화되어 있지 않다면 어디를 봐

야 하는지 찾아내기 어렵다는 게 함정이다.

결국은 읽기 쉽고, 찾기 쉽고, 이해하기 쉽도록 한 가지 기능에 충실한 테스트가 필요한 것이

다. 그리하면 다음과 같은 이점이 생긴다.

● 현재 작업과 관련된 테스트 클래스를 찾을 수 있다.

● 그 클래스에서 적절한 테스트 메서드를 고를 수 있다.

● 그 메서드에서 사용하는 객체의 생명주기를 이해할 수 있다.

이상은 구조를 잘 갖춘 테스트가 안겨주는 이점이다. 물론 좋은 구조로 만든다는 게 말처럼 쉬

운 일은 아니다.

Page 50: 『Effective Unit Testing』 - 맛보기

50 Part I 기반 다지기

2.3 엉뚱한 걸 검사하는 건 좋지 않다

시스템 오동작의 원인을 찾고자 한참을 디버깅하다가 결국은 가장 처음에 살펴봤던 코드에서

원인을 찾았던 경험이 몇 번 있다. 이런 경험에서 가장 짜증이 났던 것은 바로 테스트 자체를

간과했다는 점이었다. 개인적으로 코드를 분석할 때는 가장 먼저 전체 테스트를 돌려보고 잘

동작하는 부분과 문제가 있는 부분을 확인한다. 이때 가끔은 테스트의 이름을 너무 믿어버리는

실수를 저지르곤 한다. 보통은 테스트의 이름을 보면 그 테스트가 검사하는 내용을 알 수 있는

데, 실제로는 이름과 전혀 관련 없는 것을 검사하는 경우가 종종 있다.

이는 구조를 잘 잡는 일과도 관련이 있다. 테스트의 이름이 검사 내용과 다르다는 건 마치 낭떠

러지로 안내하는 도로 표지판을 믿고 운전하는 것과 같다. 하지만 우리는 자신의 테스트를 신

뢰할 수 있어야 한다.

몇 해 전, 10년도 더 전부터 개발되어 온 제품의 코드를 검수한 적이 있다. 정말 거대한 코드

덩어리였는데, 오래된 코드와 최근에 작성된 코드를 구분할 수 있을 정도였다. 그 구분 기준 중

하나는 자동화된 테스트의 유무였다. 그러던 중, 이름만으로는 테스트가 검증하는 게 무엇인지

확신할 수 없다는 것을 깨닫고 안을 들여다보니 전혀 엉뚱한 걸 검사하는 게 아닌가? 원래는

다른 언어로 작성되었던 코드지만, 대표적인 예 하나를 골라 자바로 다시 작성해보았다.

public class TestBmap {

@Test

public void mask() {

Bmap bmap = new Bmap();

bmap.addParameter(IPSEC_CERT_NAME);

bmap.addParameter(IPSEC_ACTION_START_DAYS, 0);

bmap.addParameter(IPSEC_ACTION_START_HOURS, 23);

assertTrue(bmap.validate());

}

}

테스트 이름부터가 완전하지 않다는 건 보자마자 눈치챘을 거다. 좀 더 들여다보면 테스트는

mask가 Bmap에게 어떤 의미인지는 전혀 신경 쓰지 않고, 단지 파라미터 조합이 유효한가만을

확인한다는 걸 알 수 있다. 심지어 정상적인 입력마저 제대로 처리하지 못해도 테스트에 통과

할 수도 있다.

Page 51: 『Effective Unit Testing』 - 맛보기

59

테스트 더블

CHAPTER 3

이 장의 내용

● 테스트 더블로 할 수 있는 일

● 테스트 더블의 종류

● 테스트 더블을 효율적으로 활용하기 위한 지침

Page 52: 『Effective Unit Testing』 - 맛보기

60 Part I 기반 다지기

스텁 stub과 더미 dummy는 먼 옛날 우리가 소프트웨어 코드를 클래스와 메서드 단위로 구조화하기

시작했던 시기부터 사용되던 개념이다. 이들 유틸리티가 등장한 가장 큰 이유는 제품 코드가

온전히 준비되기 전까지 사용할 대용품이 필요했기 때문이다. 대용품이 있다면 주변 모듈을 다

갖추지 못했더라도 코드 일부만을 따로 컴파일하고 실행해볼 수 있었다.

오늘날의 개발자 테스트에서는 이들 객체의 쓰임새가 훨씬 다양해졌다. 종속 모듈 없이 컴파일

하고 실행하려는 애초의 용도는 그 비중이 많이 줄어들었다. 대신 테스트 추종 프로그래머에

의해 다양한 테스트 전용 장치가 만들어지면서 요즘은 테스트 대상 코드를 격리하고, 속도를 개

선하고, 예측 불가능한 요소를 제어하고, 특수한 상황을 시뮬레이션하고, 감춰진 정보를 얻어

내는 등의 용도로까지 쓰이고 있다.

이처럼 목적에 따라 비슷한 듯하면서도 다른 객체를 사용하는데 그 전부를 통틀어 테스트 더블

이라 한다.1

개발자가 테스트 더블로 선회해야 하는 이유부터 이야기보따리를 풀어볼 생각이다. 테스트 더

블을 사용해서 얻을 수 있는 잠재적 이점을 이해하고 그중 몇 가지는 직접 주물러보자. 마지막

으로는 약소하게나마 테스트 더블을 효율적으로 활용하기 위한 지침을 준비해보았다.

먼저, 자신에게 한 번 물어보자. “내게는 무슨 득이 있을까?”

1 저자주_ 테스트 더블이라는 용어를 내게 처음 알려준 이는 매닝의 또 다른 저자인 레인스버거( Rainsberger)라는 친구다. 하지만 이 용어 및 관련 체계를 개발자 커뮤니티에 널리 전파하는 데는 제라드 메스자로스( Gerard Meszaros)와 그의 책 『xUnit Test Patterns: Refactoring Test Code』 (Addison Wesley, 2007), 번역서 『xUnit 테스트 패턴』 (박일 역, 에이콘, 2010)의 공이 더 크지 않았나 싶다.

테스트 더블

CHAPTER 3

Page 53: 『Effective Unit Testing』 - 맛보기

61CHAPTER 3 테스트 더블

3.1 테스트 더블의 위력

마하트마 간디Mahatma Gandhi는 말했다. “세상이 변하길 바란다면 너 자신이 그 변화가 되어라.”

테스트 더블은 간디의 말을 몸소 실천하여 우리가 코드에 바라던 변화가 되어준다. 너무 비행

기 태우는 게 아니냐고? 자! 설명 들어간다.

코드는 덩어리다. 서로 참조하는 코드들이 그물처럼 얽혀있다. 각각의 조각은 약속된 동작을 수

행한다. 동작을 정의하는 건 우리, 즉 프로그래머다. 어떤 동작은 원자적atomic이라 클래스나 메

서드 하나 안에서 모두 처리되는 반면 어떤 동작은 다른 코드 조각과의 교류를 통해 완성된다.

종종 어떤 코드 조각이 원하는 동작을 올바로 수행하는지 검증하려 할 때, 주변 코드를 모두 교

체하여 테스트 환경 전반을 통제할 수 있다면 가장 좋다. 이렇게 테스트 대상 코드와 협력 객체를 잘

분리하면 [그림 3-1]처럼 될 것이다.

그림 3-1 테스트 더블을 이용하면 테스트 대상 코드를 주변으로부터 쉽게 떼어낼 수 있어 원

하는 대로 마음껏 검사할 수 있다. 본 그림은 총 네 개의 협력 객체 중 세 개를 테스트 더블로

대체한 모습이다. 이로써 테스트 범위는 대상 코드 자체의 동작과 남겨진 협력 객체 하나와의

연계 동작으로 정해졌다.

테스트대상 코드

테스트더블

테스트더블

테스트더블

진짜협력 객체

테스트하려는 코드를 주변에서 분리하는 것이 테스트 더블을 활용하는 가장 기본적인 이유다.

물론 다른 이유도 많다. 그럼 이쯤에서 3장의 서두에서 언급한 테스트 전용 장치가 필요한 이유

를 되짚어보자.

● 테스트 대상 코드를 격리한다.

● 테스트 속도를 개선한다.

● 예측 불가능한 실행 요소를 제거한다.

Page 54: 『Effective Unit Testing』 - 맛보기

62 Part I 기반 다지기

● 특수한 상황을 시뮬레이션한다.

● 감춰진 정보를 얻어낸다.

이상과 같은 효과를 발휘하는 테스트 더블에는 여러 가지가 있다. 어떤 테스트 더블을 사용하

더라도 대부분 효과를 얻을 수 있지만, 각각에 가장 잘 맞는 쓰임새가 있게 마련이다. 그 답은

3.2절에서 알아보기로 하고, 우선은 테스트 더블이 탄생한 원인과 용도부터 확실히 이해해

보자.

3.1.1 테스트 대상 코드를 격리한다자바와 같은 객체지향 프로그래밍 언어의 관점에서 보면 테스트 대상 코드를 격리한다는 것은

세상의 모든 것을 두 가지로 분류한다는 뜻이다.

● 테스트 대상 코드

● 테스트 대상 코드와 상호작용하는 코드

테스트 대상 코드를 격리하겠다는 것은 테스트하려는 코드를 그 외의 모든 코드에서 떼어 놓겠다

는 의미다. 그 결과로 테스트는 초점이 분명해지고, 이해하기도 쉬워지고, 설정하기도 간편해

진다. 그 외의 모든 코드에는 테스트 대상 코드가 호출하는 코드도 포함된다. 쉬운 예를 하나 준비

했다.

코드 3-1 테스트 대상 코드(Car)와 협력 객체(Engine과 Route)

public class Car {

private Engine engine;

public Car(Engine engine) {

this.engine = engine;

}

public void start() {

engine.start();

}

public void drive(Route route) {

for (Directions directions : route.directions()) {

Page 55: 『Effective Unit Testing』 - 맛보기

63CHAPTER 3 테스트 더블

directions.follow();

}

}

public void stop() {

engine.stop();

}

}

보다시피, 본 예제는 Car (자동차)와 Engine (엔진), 그리고 일련의 Directions (길 안내)로

이루어진 Route (주행 경로)로 구성된다. 이 중 Car를 테스트해보자. 총 네 개의 클래스 중

에서 테스트 대상 코드는 하나(Car )이고 협력 객체는 두 개(Engine과 Route )가 된다. 잠깐!

Car가 분명히 Directions의 메서드를 호출하고 있는데도 Directions가 협력 객체에서 제외

된 이유는 무엇일까? 이해를 위해 시나리오를 다른 관점에서 해석해보자. [그림 3-2]를 보면

개념이 더 잘 와 닿을 것이다.

그림 3-2 Car는 Engine과 Route를 직접 사용하지만, Directions는 Route를 통해 간접

적으로만 사용한다.

Car

Engine

Route

Directions

Directions

Directions

지금까지는 Car의 메서드에서 참조하는 클래스에 집중했는데, 추상화 수준을 끌어 올려 Car

의 시각에서 다시 바라보자. 그럼 [그림 3-2]에서처럼 Directions는 Route를 거쳐서 얻어낸

다는 사실이 눈에 보일 것이다. 이런 이유로 협력 객체로부터 Car를 격리하는 일은 Engine과

Route만 테스트 더블로 교체하는 걸로 충분하다. Route의 모조품을 직접 구현할 테니 Car에

전달할 Directions는 우리 마음대로 만들 수 있다.

이제 테스트 더블을 활용해서 제어권을 가져오는 기본 원리를 이해했을 것이다. 그럼 이렇게

하면 어떤 이득이 생기는지 알아볼 차례다.

Page 56: 『Effective Unit Testing』 - 맛보기

64 Part I 기반 다지기

3.1.2 테스트 속도를 개선한다테스트 더블로 협력 객체를 대체하면 원본을 그대로 사용할 때보다 빨라진다는 이점이 부수적

으로 따라온다. 그런데 때로는 이것이 주목적이 되기도 한다.

[그림 3-2]를 떠올려보자. Car가 이동할 최단경로를 구할 때 Route가 가중 그래프weighted-graph

검색 알고리즘을 이용한다고 가정해보자. 요즘같이 도로망이 복잡한 도시라면 계산 시간이 좀

길어질 수 있다. 그리 길지는 않다고 해도 테스트가 조금 지연되는 건 피할 수 없다. 만약 모든

테스트가 Route를 매번 초기화한다면 알고리즘 계산에만 수 초에서 수 분까지 허비될지도 모

른다. 그리고 즉각적인 피드백을 기대하던 개발자에게는 몇 분도 영원처럼 길게 느껴질 수 있다.

테스트 더블을 이용해서 사전에 계산해둔 경로를 반환하도록 하면 쓸데없이 기다리는 시간이

줄어들어 테스트는 눈부시게 빨라진다. 여러분도 이 느려터진 경로 검색 알고리즘이 올바른지

를 매번 확인하기보다는 별도의 테스트에서 따로 검증하길 바랄 것이다.

성능 개선은 언제라도 환영할 일이다. 다만, 가장 중요한 요소는 아닐 수 있다. 엉뚱한 길로만

주행한다면 아무리 빠른 차라도 별다른 도움은 못 된다.

3.1.3 예측 불가능한 실행 요소를 제거한다저명한 연설가인 토니 로빈스Tony Robbins가 언젠가 깜짝 파티에 관해 이야기한 적이 있다. 누구

나 깜짝 파티를 좋아한다고 말하지만, 실상은 자신이 바랐던 깜짝 파티만 좋아한다는 것이다.

생각해보면 정말 그렇다. 소프트웨어, 특히나 테스트 코드에서도 마찬가지다.

테스트란 동작을 정의하고 명세와 일치하는지 확인하는 작업이다. 대상 코드가 완벽히 결정적

이라서 불확정 요소가 전혀 없다면 꽤 간단하고 명확한 일일 것이다. 코드가, 물론 테스트 코드

도 결정적이 되려면 몇 번을 테스트하건 항상 같은 결과가 나오도록 해야 한다.

하지만 결과가 불규칙해야만 하는 제품도 드물지 않다. 한 예로 크랩스Craps 게임2을 만든다면

주사위의 값을 예측할 수 없도록, 즉 임의의 요소를 넣는 것이 좋다.

비결정적 요소의 가장 전형적인 예는 아무래도 시간과 관련된 동작이 아닐까 한다. Car가

2 저자주_ 크랩스란 주사위 두 개를 굴려 나오는 숫자에 판돈을 거는 주사위 놀이다. 영화에서 길거리 모퉁이에 쭈그리고 앉아 돈다발 위에 주사위를 던지며 시간을 때우는 갱 단원들을 자주 보았을 것이다.

Page 57: 『Effective Unit Testing』 - 맛보기

83

테스트 냄새

Part

II2부에서는 테스트 코드에 기생하는 문제를 식별하고 수정하는 능력을 길러보자. 이를 위해 목록catalog 형태로

꾸며보았다. 가독성, 유지보수성, 신뢰성을 떨어뜨리는 속성이나 특징 등, 테스트 코드를 작성하면서 자주 맡

을 수 있는 ‘테스트 냄새’를 나열한 목록이다.

우리는 올바른 지침보다는 잘못된 행태(안티 패턴)에 집중할 것이다. 경험상, 개발자에게 자신의 테스트에서

잘못을 찾고 바로잡게끔 가르치기에는 이 방식이 더 효과적이었기 때문이다. 좋은 테스트에만 익숙한 사람은

코드에 잠재된 문제점은 잘 발견하지 못하는 경향이 있다.

바스 보드Bas Vodde라는 친구는 눈에 띄는 테스트 냄새만 모두 제거해도 십중팔구 꽤 괜찮은 테스트 코드가 남

게 된다고 주장한다. 그뿐만 아니라, 좋은 코드를 작성하기 위한 설계 원칙과 지침 모두는 테스트 코드에도 고

스란히 적용된다. 다시 말해, 우리는 테스트 코드가 갖춰야 할 특성에 관한 기본 지식을 이미 제법 알고 있다는

뜻이다.

2부에서는 테스트 냄새 목록을 주제별로 크게 세 부류로 나누었다. 4장에서는 테스트의 가독성을 떨어뜨리는

냄새를, 5장에서는 유지보수 어렵게 하는 냄새를 다룬다. 마지막 6장에서는 변덕이 심해서 신뢰할 수 없는 테

스트 냄새를 만나볼 것이다.

테스트 냄새의 영향을 ‘가장 크게’ 받는 속성을 기준으로 분류했기 때문에 일부는 두 곳 이상에서 언급될 수도

있다. 또한, 제품 코드와도 밀접한 테스트 냄새도 많은데, 심지어 제품 코드에서 자주 목격되기도 한다. 이러한

연관성은 각 냄새를 설명하면서 그때그때 이야기할 생각이다. 서로 반대편 극단에 서 있는 냄새들도 있다. 극

단적이어서 좋은 예는 거의 없고 여기서도 물론 예외는 아니다. 이들 사이의 연관성도 확실히 꼬집어 주겠다.

좋다! 소매를 걷어붙이고 목록을 파헤쳐보자.

Page 58: 『Effective Unit Testing』 - 맛보기
Page 59: 『Effective Unit Testing』 - 맛보기

85

가독성

CHAPTER 4

이 장의 내용

● 단언문과 관련된 테스트 냄새

● 제품 코드에 흩뿌려진 정보와 관련된 테스트 냄새

● 과하거나 불필요한 세부 정보와 관련된 테스트 냄새

Page 60: 『Effective Unit Testing』 - 맛보기

86 Part II 테스트 냄새

테스트란 코드에 바라는 동작과 가정을 프로그래머 식으로 표현한 것이라 말할 수 있다. 따라

서 테스트를 읽은 프로그래머는 코드가 해야 할 일을 이해할 수 있어야 한다. 또한, 테스트를 실

행한 후에는 코드가 실제로 한 일이 무엇인지 말할 수 있어야 한다.1

테스트의 핵심인 단언문은 대상 코드의 올바른 동작을 규정한다. 사실 테스트의 모든 코드가

대상 제품에 기대하는 동작을 서술하는 것은 분명하지만, 그중에서도 정곡을 찌르는 것은 역시

단언문이다. 이 때문에 테스트의 성패를 판별하는 역할도 자연스럽게 단언문이 담당한다. 비슷

한 맥락에서, 테스트 프레임워크도 우리가 사용하는 것과 똑같은 단언문을 이용해서 가정을 검

증한다.

단언문의 역할이 중요한 만큼 4장의 냄새 중 상당수가 이와 관련된 것이다. 하지만 픽스처 fixture

준비나 대상 코드 호출 등 그 외의 테스트 코드도 가독성 측면에서는 똑같이 중요하다.

정리하면, 4장에서는 잘못된 테스트 코드가 풍기는 냄새와 그로 인해 생겨나는 문제를 집중 조

명한다.

결국, 테스트 코드는 읽기 쉬워야 한다. 테스트가 무슨 일을 하는지 파악하기 위해 머리를 쥐어

뜯는 일은 없어야 한다. 하지만 모든 게 우리 바람처럼 아름다울 수만은 없다. 살다 보면 수없

이 마주쳤던 끔찍한 코드를 다시 만나기도 한다. 겨우 10줄밖에 안 되면서 스와힐리어와 히브

리어가 섞여 쓰인 고대 성서처럼 어려운 코드 말이다.

그러한 테스트 냄새는 숱하게 많지만, 그중에서도 가장 흔한 냄새는 바로 기본 타입 단언이다. 이

는 여러분께 소개할 첫 냄새이니 설레는 마음으로 함께 만나보기로 하자.

1 역자주_ 프로그래머의 의무라는 뜻이 아니라 테스트가 그렇게 작성되어야 한다는 의미다.

가독성

CHAPTER 4

Page 61: 『Effective Unit Testing』 - 맛보기

87CHAPTER 4 가독성

4.1 기본 타입 단언

단언문은 가정이나 의도를 명시해야 한다. 또한, 코드의 동작을 서술하는 문장이어야 한다. 기

본 타입 단언 Primitive Assertion이란 단언하려는 이유나 의도가 의미를 알 수 없는 단어나 숫자에 가

려진 상황을 말한다. 이 경우 이해하기도 어렵고 단언을 올바로 기술한 것인지도 파악하기 쉽

지 않다는 문제가 생긴다.

다시 말해, 기본 타입 단언이란 검사해야 할 동작보다 더 기초적인 요소에 집착하는 것을 말한

다. 이 냄새를 모락모락 풍기는 예를 하나 살펴보자.

4.1.1 예시

다음은 그렙grep 유틸리티를 테스트하는 코드다. 기본적으로 그렙은 입력받은 텍스트를 한 줄씩

읽어가며 특정 문자열이나 패턴을 포함하는 줄을 걸러내는 작업이다. 테스트란 대상의 기능을

알려주는 구체적인 예제여야 한다. 그렇다면 다음 테스트가 그렙 유틸리티의 기능을 명쾌하게

설명해주는지 확인해보자. 기본 타입 단언 문제를 겪고 있지 않다면 말이다.

코드 4-1 인지능력에 부담을 주는 기본 타입 단언

@Test

public void outputHasLineNumbers() {

String content = "1st match on #1\nand\n2nd match on #3";

String out = grep.grep("match", "test.txt", content);

assertTrue(out.indexOf("test.txt:1 1st match") != -1);

assertTrue(out.indexOf("test.txt:3 2nd match") != -1);

}

자, 테스트에서는 무슨 일이 일어나고 있는가? 입력으로 쓸 문자열 상수content를 정의하고, 그

렙 유틸리티를 호출하고, 그렙이 출력한 결과에 대해 무언가를 단언한다. 여기선 마지막의 그

무언가가 문제다. 단언문이 너무 원시적이라 확인하려는 게 무엇인지 분명치가 않다. 마치 나머

지 코드와는 다른 외계어로 쓰인 것 같다.

단언문을 더 구체적으로 보면 그렙 결과에서 어떤 문자열 상수의 인덱스를 계산하고, 이를 -1과

비교한다. 인덱스가 -1이면 테스트는 실패한다. 반면, 테스트의 이름 outputHasLineNumbers과 [코드

Page 62: 『Effective Unit Testing』 - 맛보기

88 Part II 테스트 냄새

4-1]에서 grep() 메서드에 넘긴 입력 인자로 유추해보면, 출력 결과는 줄마다 그렙 대상 파

일의 이름과 줄 번호를 달고 있어야 한다.

인덱스를 구하는 이유는 무엇이고, 하필 왜 test.txt:1을 찾아야 하고, -1과 비교하는 건 또

왜인지, 인덱스가 -1이면 성공인지 실패인지, 어떤 경우에 -1 외의 값이 나올 수 있는지 등을

이해하려면 앞의 분석 과정을 모두 거쳐야만 한다. 로켓 과학처럼 어렵진 않지만, 불필요한 두

뇌 활동인 것만은 확실하다.

4.1.2 개선 방법

기본 타입 단언의 핵심은 단언 대상의 추상화 수준이 너무 낮다는 점이다. 이 점을 파고들면 앞

의 예를 개선할 방법을 몇 가지 유추해낼 수 있다.

먼저, 매직 넘버인 -1을 둘러싼 복잡한 로직을 제거할 수 있다. assertTrue와 != 비교문의 조

합을 JUnit 4의 assertThat 문법을 써서 더 명확하게 바꿔보자.

코드 4-2 단언문을 읽기 편하게 바꿔주는 JUnit 4의 assertThat

@Test

public void outputHasLineNumbers() {

String content = "1st match on #1\nand\n2nd match on #3";

String out = grep.grep("match", "test.txt", content);

assertThat(out.indexOf("test.txt:1 1st match"), is(not(-1)));

assertThat(out.indexOf("test.txt:3 2nd match"), is(not(-1)));

}

assertThat 문법과 함께 햄크레스트 매처 Hamcrest Matcher 유틸리티의 is와 not 메서드도 사용하

였다. 이 작은 변화만으로 인덱스가 -1이길 기대했는가를 분간해야 하는 부담을 없앴다. 드디

어 의도가 명확해졌다.2

이어서 개선해볼 것은 예제에서 사용한 자바 표준 API의 추상화 수준과 관련이 있다. 더 정확

하게는, 전체 문자열에서 주어진 문자열 조각의 위치를 찾는 방법을 재고할 필요가 있다. 그렙

2 저자주_ 햄크레스트(https://github.com/hamcrest/JavaHamcrest)는 JUnit 등에서 객체를 다양한 예상값과 비교할 때 이용할 수 있는 API와 매처(matcher)를 구현해놓은 라이브러리다.

Page 63: 『Effective Unit Testing』 - 맛보기

89CHAPTER 4 가독성

의 반환값에 주어진 문자열 조각이 포함되어 있는지 확인할 때 인덱스나 매직 넘버인 -1을 사

용하는 건 개념을 잘못 추상화한 예다. 제대로 추상화한다면 테스트의 모습은 다음 코드처럼

진화할 것이다.

코드 4-3 기대한 바를 알맞게 추상화하여 표현한 모습

@Test

public void outputHasLineNumbers() {

String content = "1st match on #1\nand\n2nd match on #3";

String out = grep.grep("match", "test.txt", content);

assertTrue(out.contains("test.txt:1 1st match"));

assertTrue(out.contains("test.txt:3 2nd match"));

}

여기서는 indexOf에서 추상화 수준을 한 단계 높인 java.lang.String의 contains 메서드를

사용하였다. 이렇게 해서 코드를 읽게 될 프로그래머의 인지 부하가 다시 한 번 줄어들었다.

마지막으로, 지금까지 살펴본 개선 방법 두 가지3를 동시에 적용할 수 있는지 고민해보자. 결과

는 다음과 같다.

코드 4-4 assertThat과 알맞은 추상화를 함께 적용한 모습

@Test

public void outputHasLineNumbers() {

String content = "1st match on #1\nand\n2nd match on #3";

String out = grep.grep("match", "test.txt", content);

assertThat(out.contains("test.txt:1 1st match"), equals(true));

assertThat(out.contains("test.txt:3 2nd match"), equals(true));

}

아무래도 두 번째 개선만 적용한 [코드 4-3]보다는 장황하지만, 첫 번째 개선만 적용했던 [코

드 4-2]에 비하면 의도가 더 명확하고 적절하게 표현되었다. 테스트 코드에서 어떤 방식으로

의도를 표현할지 결정할 때, 코드 중복이나 성능보다 가독성과 명료성이 더 중요함을 기억해두

자. 이유는 테스트 본래의 목적을 생각해보면 알 수 있을 것이다. 그리고 살짝 덧붙인 설명 덕

에 표현력, 가독성, 유지보수성이 몰라보게 개선되는 상황을 자주 볼 수 있을 거다.

3 저자주_ 산문적 느낌을 더해주는 assertThat과 추상화 수준을 높인 String#contains를 말한다.

Page 64: 『Effective Unit Testing』 - 맛보기

90 Part II 테스트 냄새

또한, 도구를 잘 쓰면 의도를 표현하는 게 한결 수월해진다. JUnit은 자주 쓰이는 햄크레스트

매처를 기본으로 포함하고 있는데, 그중 org.junit.JUnitMatchers#containsString()을

사용하면 본 예제를 다음과 같이 개선할 수 있다.

코드 4-5 햄크레스트 매처를 적절히 활용하여 더욱 개선한 모습

@Test

public void outputHasLineNumbers() {

String content = "1st match on #1\nand\n2nd match on #3";

String out = grep.grep("match", "test.txt", content);

assertThat(out, containsString("test.txt:1 1st match"));

assertThat(out, containsString("test.txt:3 2nd match"));

}

지금까지 보아온 것 중 어느 것이 가장 맘에 드는가? 각자의 취향에 맞는 방향을 택하면 된다.

다만 assertThat, assertTrue, assertFalse 혹은 assertEquals 중 어느 것을 선택하건 검

사하려는 기능과 단언문을 같은 언어와 어휘로 표현하길 바란다.

4.1.3 정리

테스트에서 != 나 == 등의 비교문을 사용하는 단언문을 발견하면 주저하지 말고 추상화 수준

이 적절한지 되짚어보라. 비교 대상이 -1, 0 등의 매직 넘버라면 더 생각할 것도 없다. 단언문

을 즉시 이해할 수 없다면 기본 타입 단언에 해당하며 리팩토링 대상이 될 가능성이 높다.

기본 타입 단언은 사실 기본 타입 강박관념 primitive obsession code smell이라는 코드 냄새의 쌍둥이다. 상

위 개념을 기본 타입으로 표현하는 냄새로, 전화번호는 String으로, 휴대전화 부가 서비스 사

용 여부는 byte로, 약정 기간은 Date로 표현하는 경우를 떠올리면 된다. 테스트를 작성할 때

는 이들 타입과 표현하려는 개념의 추상화 수준과 같은지 신경 써야 한다. 어떻게 구현되어 있

는지와는 다른 문제다.

기본 타입에 집착하는 것 외에, 세세한 내용까지 너무 집착하는 테스트와 마주칠 때도 있다. 사

소한 것 하나하나 꼼꼼하게 챙기는 관리자 같은 단위 테스트를 말한다. 이런 집착에 사로잡힌

테스트는 과하게 포괄적인 단언문을 사용하는 경향이 있다. 너무 넓어서 광역 단언이라 불릴

정도다.

Page 65: 『Effective Unit Testing』 - 맛보기

91CHAPTER 4 가독성

4.2 광역 단언

광역 단언 Hyperassertion은 검사하려는 동작의 아주 작은 하나까지도 놓치지 않으려는 집착의 산물

이다. 결과적으로, 어느 하나만 잘못되어도 바로 실패하게 되고, 본래 의도했던 것도 그 광활한

검증 범위에 묻혀 희석된다. 그래서 광역 단언과 마주치면 검사하려는 것이 무엇인지 정확히

이야기하기 어렵다. 또한, 잘 관찰해보면 그 테스트의 실패 빈도는 전체 테스트의 평균치보다

훨씬 높다는 걸 알 수 있다. 사소한 변경도 예상과 다른 결과를 만들기 때문이다. 그럼 광역 단

언으로 고통받는 구체적인 예제를 살펴보자.

4.2.1 예시이번에 살펴볼 예제는 의료 회사에 납품할 프레젠테이션 추적 시스템을 검증하는 테스트다. 고

객사는 판매원이 의사에게 제품을 설명할 때, 소개자료를 어떻게 활용하는지 그 패턴을 데이터

화하길 바랐다. 정확히 말하면, 어느 판매사원이, 어느 소개자료의, 어느 슬라이드를 몇 초씩

보여주는가를 기록해주길 바랐다.

이를 위해 컴포넌트 몇 개로 구성된 아키텍처를 도입했다. 소개자료 파일에는 플러그인을 추가

하여 슬라이드 시작 시각과 다음 슬라이드로 넘기는 시간 등을 이벤트로 알려주게 하였다. 간

단하게 각 이벤트에 타임스탬프를 넣는 식이었다. 모든 이벤트는 백그라운드 애플리케이션에

전달되어 로그 파일로 떨궈진다. 로그 파일은 포맷 변환을 거친 후 중앙 서버로 보내졌다. 여기

에서 포맷 변환이란 중앙 서버가 파일을 분석하고 수치화해서 데이터베이스에 기록하는 과정

을 간단하게 해주는 선행 작업이다. 어쨌든, 타임스탬프를 기준으로 슬라이드별 지속 시간을

계산하는 것이 핵심이다.

포맷 변환을 담당하는 객체는 LogFileTransformer였는데, 당시 테스트 추종자였던 나는 이

객체를 위한 테스트도 당연히 여러 개 작성해 두었다. 다음 코드는 그중 하나다. 물론 광역 단

언 냄새를 풍기고 있다. 잘 살펴보고 냄새의 발원지를 찾아보라.

코드 4-6 모호하고 쉽게 실패하는 테스트를 만드는 광역 단언

public class LogFileTransformerTest {

private String expectedOutput;

Page 66: 『Effective Unit Testing』 - 맛보기

92 Part II 테스트 냄새

private String logFile;

@Before

public void setUpBuildLogFile() {

StringBuilder lines = new StringBuilder();

appendTo(lines, "[2005-05-23 21:20:33] LAUNCHED");

appendTo(lines, "[2005-05-23 21:20:33] session-id###SID");

appendTo(lines, "[2005-05-23 21:20:33] user-id###UID");

appendTo(lines, "[2005-05-23 21:20:33] presentation-id###PID");

appendTo(lines, "[2005-05-23 21:20:35] screen1");

appendTo(lines, "[2005-05-23 21:20:36] screen2");

appendTo(lines, "[2005-05-23 21:21:36] screen3");

appendTo(lines, "[2005-05-23 21:21:36] screen4");

appendTo(lines, "[2005-05-23 21:22:00] screen5");

appendTo(lines, "[2005-05-23 21:22:48] STOPPED");

logFile = lines.toString();

}

@Before

public void setUpBuildTransformedFile() {

StringBuilder file = new StringBuilder();

appendTo(file, "session-id###SID");

appendTo(file, "presentation-id###PID");

appendTo(file, "user-id###UID");

appendTo(file, "started###2005-05-23 21:20:33");

appendTo(file, "screen1###1");

appendTo(file, "screen2###60");

appendTo(file, "screen3###0");

appendTo(file, "screen4###24");

appendTo(file, "screen5###48");

appendTo(file, "finished###2005-05-23 21:22:48");

expectedOutput = file.toString();

}

@Test

public void transformationGeneratesRightStuffIntoTheRightFile()

throws Exception {

TempFile input = TempFile.withSuffix(".src.log").append(logFile);

TempFile output = TempFile.withSuffix(".dest.log");

new LogFileTransformer().transform(input.file(), output.file());

assertTrue("Destination file was not created", output.exists());

assertEquals(expectedOutput, output.content());

}

Page 67: 『Effective Unit Testing』 - 맛보기

125

유지보수성

CHAPTER 5

이 장의 내용

● 인지 부하를 가중시키는 테스트 냄새

● 유지보수 악몽에 시달리게 하는 테스트 냄새

● 불규칙한 실패를 초래하는 테스트 냄새

Page 68: 『Effective Unit Testing』 - 맛보기

126 Part II 테스트 냄새

4장에서 테스트 코드의 가독성에 집중했었다면 5장에서는 유지보수성을 중심으로 이야기를

진행해보겠다.

코드는 쓰이는 횟수보다 읽히는 횟수가 훨씬 많다는 이야기는 많이들 들어보았으리라 믿는다.

천공카드 시절부터 시작된 이야기지만 지금도 달라진 것은 전혀 없다. 가독성을 그토록 강조했

던 이유다. 코드 읽기에서 한 걸음 내디디면 코드 작성이라는 세계가 열린다. 그리고 현실에서

의 작성의 대부분은 기존 코드를 수정하거나 확장하는 걸 뜻한다. 이를 유지보수라 하기도 하고

개발이라 부르기도 한다. 어느 쪽이건 코드 어딘가를 고쳐야 한다는 사실은 변함이 없다.

코드를 보면 손가락이 근질거리고 고급 프로그래밍 기법을 보면 통달하고 싶은 욕구가 끓어오

르는 이유는 코드라는 것의 본질이 지극히 유연한 동시에 망가지기도 쉽기 때문이다. 물리적

제약만 없다면 소프트웨어로 못 할 일이 거의 없다는 면에서 코드는 유연하다고 할 수 있다. 그

리고 아주 사소한 수정만으로도 전체를 완전히 망쳐버릴 수 있다는 면에서 코드는 망가지기 쉽

다. 코드는 서서히 마모되는 게 아니라 한순간에 멎어버린다.

테스트도 이와 같다. 테스트도 태생은 제품 코드와 다를 바 없는 코드인지라, 근본적으로 똑같

이 불안정하다. 자동화된 단위 테스트를 작성할 때도 이런 취약성에 주의하면서 관리해야 하는

이유다. 유지보수 악몽을 모르는 이는 없을 것이다. 그리고 여러분도 자신의 테스트 코드가 그 주

인공이 되는 건 원치 않으리라 믿는다.

5장도 4장과 같은 방식으로 구성했다. 일련의 테스트 냄새를 나열하며 그 예시와 해결책 혹은

유지보수성

CHAPTER 5

Page 69: 『Effective Unit Testing』 - 맛보기

127CHAPTER 5 유지보수성

개선 방안을 논할 것이다. 테스트 코드를 최신 상태로 유지하기 위한 비용을 증가시키는 가장

흔한 테스트 냄새를 선봉에 세우고, 더 특수한 상황에서 발생하지만, 유지보수 관점에서는 똑

같이 위험한 냄새를 뒤에 배치했다. 가장 처음에 논할 주제는 중복이다.

5.1 중복

규모 있는 컨퍼런스에 참가해서 임의로 10명의 프로그래머를 선별해서 모든 악의 근원인 무엇

인지 물어보라. 아마도 가장 많은 이가 도널드 크누스Donald Knuth의 오래된 발언을 언급하며 어설

픈 최적화라고 대답할 것이다.1 두 번째로 많은 대답은 분명 중복Duplication일 거다. 물론 애자일 소

프트웨어 개발자와 테스트 추종 프로그래머 그리고 깔끔한 코드를 추구하는 소프트웨어 장인

으로 북적대는 콘퍼런스였다는 가정하에서다.

그렇다면 중복이란 무엇인가? 우리가 말하는 중복의 의미는 무엇일까? 한마디로, 중복은 하나

의 개념이 여러 차례에 걸쳐 표현되거나 복제된 것이다. 즉, 필요 없는 반복이다.

가장 명백한 중복으로는 코드베이스 전체에 걸쳐 반복되는 특정 숫자값과 문자열을 들 수 있다.

코드베이스 곳곳에 흩뿌려진 하드코딩된 데이터나 값 말고도, 두 클래스나 객체 혹은 메서드의

역할이 겹친다거나, 모양은 다르지만, 기능이 같은 코드 조각 등도 모두 중복의 한 형태다.

중복이 나쁜 이유는 다음과 같다. 개념과 논리를 곳곳에 흩어놓아 코드를 이해하기 어렵고 불

투명하게 만든다. 더욱이 코드를 수정하려면 중복된 곳을 모두 찾아 일일이 손봐줘야 한다. 한

곳이라도 놓치면 곧바로 버그로 이어질 수 있다.

5.1.1 예시서론은 이만하면 충분하니, 이제 몇 가지 중복의 형태를 보여주는 다음 코드와 함께 본론으로

들어가보자.

1 저자주_ 도널드 크누스. 『Structured Programming with go to Statements』 (go to 문장을 활용한 구조적 프로그래밍) (Computing Surveys, 볼륨 6, 스탠퍼드 대학교, 1997)

Page 70: 『Effective Unit Testing』 - 맛보기

128 Part II 테스트 냄새

코드 5-1 여러 가지 중복을 포함한 짧은 테스트 클래스

public class TemplateTest {

@Test

public void emptyTemplate() throws Exception {

assertEquals("", new Template("").evaluate());

}

@Test

public void plainTextTemplate() throws Exception {

assertEquals("plaintext", new Template("plaintext").evaluate());

}

}

이 코드에는 어떤 유형의 중복들이 보이는가? 여기서 찾을 수 있는 가장 기초적인 중복은 두

단언문에서 쓰인 문자열 상수일 것이다. 즉, 빈 문자열과 plaintext가 두 번에 걸쳐 사용되었

다. 이를 상수 중복이라 부른다. 상수 중복은 지역 변수로 만들어서 제거할 수 있다. 이 테스트

클래스는 또 다른 유형의 중복도 가지고 있는데, 문자열 상수보다 더 흥미로운 녀석이다. 지역

변수를 추출하고 나면 그 모습이 더 도드라질 테니, 우선 주변 정리부터 시작해보자.

5.1.2 개선 방법일단은 먼저 찾아낸 중복된 문자열 상수를 제거하자. 이 문자열이 컴퓨터 프로그래밍 역사상

최악의 중복은 당연히 아니지만, 옅은 냄새를 걷어내면 종종 독한 냄새가 두드러지기도 한다.

지금까지의 결과는 다음과 같다.

코드 5-2 지역 변수를 추출한 후 숨어있던 중복이 드러난 모습

public class TemplateTest {

@Test

public void emptyTemplate() throws Exception {

String template = "";

assertEquals(template, new Template(template).evaluate());

}

@Test

public void plainTextTemplate() throws Exception {

Page 71: 『Effective Unit Testing』 - 맛보기

129CHAPTER 5 유지보수성

String template = "plaintext";

assertEquals(template, new Template(template).evaluate());

}

}

두 테스트 메서드의 실제 동작을 살펴보면, 문자열 상수 외에는 다른 점이 전혀 없다는 사실을

알게 된다. 차이가 나던 부분을 지역 변수로 추출한 후의 두 단언문은 그냥 닮은 정도가 아니라

완벽히 똑같다. 둘 다 Template 객체를 생성하고, 평가하고, 의도한 대로 만들어졌는지 확인한

다. 이처럼 데이터만 다를 뿐 처리 로직이 똑같은 상황을 구조 중복이라 한다. 두 코드 블록은 서

로 다른 값을 다루지만, 완전히 같은 구조를 가졌다.

이 중복도 제거하면 코드가 어떻게 변하는지 확인해보자. 구조적 중복을 추출하여 사용자 정의

단언 메서드로 밀어 넣으면 [코드 5-3]처럼 된다.

코드 5-3 중복된 단언을 사용자 정의 단언으로 추출한 모습

public class TemplateTest {

@Test

public void emptyTemplate() throws Exception {

assertTemplateRendersAsItself("");

}

@Test

public void plainTextTemplate() throws Exception {

assertTemplateRendersAsItself("plaintext");

}

private void assertTemplateRendersAsItself(String template) {

assertEquals(template, new Template(template).evaluate());

}

}

의미 중복

종종 맨눈으로는 찾기 어려운 중복도 있다. 상수 중복과 구조 중복은 비슷해 보이는 구조만 찾으면 또렷하게

눈에 띄지만, 의미 중복은 같은 기능이나 개념을 다른 방식으로 구현한 것이라 알아차리기가 만만치 않다.

여기 의미 중복의 예를 준비해보았다.

Page 72: 『Effective Unit Testing』 - 맛보기

130 Part II 테스트 냄새

@Test

public void groupShouldContainTwoSupervisors() {

List<Employee> all = group.list();

List<Employee> employees = new ArrayList<Employee>(all);

Iterator<Employee> i = employees.iterator();

while (i.hasNext()) {

Employee employee = i.next();

if (!employee.isSupervisor()) {

i.remove();

}

}

assertEquals(2, employees.size());

}

@Test

public void groupShouldContainFiveNewcomers() {

List<Employee> newcomers = new ArrayList<Employee>();

for (Employee employee : group.list()) {

DateTime oneYearAgo = DateTime.now().minusYears(1);

if (employee.startingDate().isAfter(oneYearAgo)) {

newcomers.add(employee);

}

}

assertEquals(5, newcomers.size());

}

group.list()를 호출하여 전체 직원 명단을 얻는 부분을 제외하면, 두 메서드 사이에서 상수 중복은 찾을

수 없다. 그렇지만 직원 명단을 어떤 기준에 따라 걸러낸다는 면에서 둘 다 꽤 비슷해 보인다.

의미 중복을 없애는 방법은 이렇다. 먼저, 구조 중복으로 바꾼다. 이 예제에서는 필터링 방식을 바꾸면 되겠다.

그다음 변수나 메서드를 추출하여 그 구조 중복을 제거하면 된다. 숙제로 남겨둘 테니 직접 풀어보기 바란다.

[코드 5-3]에는 더는 아무런 중복도 남아 있지 않다. 심지어 지역 변수마저 필요 없어져서 한

눈에 쏙 들어올 것이다. 혹시 템플릿 렌더링 API를 손보려거든 딱 한 곳만 수정하면 끝이다.

Page 73: 『Effective Unit Testing』 - 맛보기

131CHAPTER 5 유지보수성

5.1.3 정리중복은 가장 흔한 코드 냄새 중 하나다. 테스트 코드에서도 쉽게 찾을 수 있는데, 단위 코드의

동작을 여러 가지 측면에서 검사한다는 테스트의 본질을 상기하면 전혀 놀랄 일도 아니다. 중

복 때문에 테스트 코드가 분산되면 테스트 유지보수에는 훨씬 긴 시간이 허비될 것임은 자명하

다. 중복된 조각 정보 중 하나라도 놓치거나 잘못 수정해서 생겨난 기이한 버그를 사냥해야 하

기 때문이다.

다행히도, 테스트 추종 프로그래머를 위한 간단한 중복 퇴치 방법이 몇 가지 있다. 예를 들어

상수값을 지역 변수로 추출하는 방법은 눈앞의 데이터 중복을 제거함과 동시에 숨어 있는 구조

중복을 드러내는 멋진 기술이다.

단, 중복 제거도 과유불급이 될 수 있다. 우리의 최우선 목표는 코드를 읽기 쉽게 유지하여 읽

는 이에게 그 의도와 기능을 명확히 전달하는 것이다. 이 사실을 잊지 않는다면 가독성을 위해

일부러 중복을 남겨둬야 할 상황도 있다는 것을 이해할 거다. 특히 테스트 코드에서는 더 자주

겪게 될 상황이다.

다음에 소개할 테스트 냄새는 중복만큼 흔하지는 않다. 좋은 소식은, 한 번 알고 나면 쉽게 찾

아 피할 수 있다.

5.2 조건부 로직

자동화된 테스트는 여러 가지 용도로 쓰인다. 기본적으로, 우리가 코드를 망쳐버리면 경고해

주는, 즉 퇴행을 막아주는 용도가 있다. 코드에 기대하는 동작을 정할 때도 테스트를 이용할 수

있다. 코드가 하는 일 혹은 해야 할 일을 파악할 때에도 유용하다. 그런데 특히 마지막 용도의

경우, 조건부 로직Conditional logic을 포함한 테스트는 제 역할을 못할 가능성이 높다. 테스트 냄새다.

리팩토링 중에 모든 기능이 여전히 정상인지 확인하고자 테스트를 돌리는 상황을 생각해보자.

그때, 테스트 하나가 실패하여 살펴보니 좀 의아하다. 방금 변경한 코드와 관련 있다고는 전혀

생각되지 않는 테스트가 실패한 것이다. 스택 추적 내용을 확인하고 소스코드도 살펴봤지만,

우리가 깨달은 사실은 실패 당시에 테스트가 실제로 하던 일이 무엇이었는지 도통 모르겠다는

것이 전부였다.

Page 74: 『Effective Unit Testing』 - 맛보기

132 Part II 테스트 냄새

이런 사태를 예방하기 위해서라도 테스트 코드에서 조건부 로직은 피해야 한다. 구체적 예를

통해 문제를 짚어보자.

5.2.1 예시다음 코드의 테스트는 Dictionary 객체를 생성하고, 데이터를 넣고, 다시 내용물을 확인하여

앞서 넣었던 데이터가 온전히 들어 있는지를 확인한다.

코드 5-4 유지보수를 어렵게 하는 테스트 코드 속의 조건부 로직

public class DictionaryTest {

@Test

public void returnsAnIteratorForContents() throws Exception {

Dictionary dict = new Dictionary();

dict.add("A", new Long(3));

dict.add("B", "21");

for (Iterator e = dict.iterator(); e.hasNext();) {

Map.Entry entry = (Map.Entry) e.next();

if ("A".equals(entry.getKey())) {

assertEquals(3L, entry.getValue());

}

if ("B".equals(entry.getKey())) {

assertEquals("21", entry.getValue());

]

]

]

}

보다시피 Dictionary 객체의 사소한 기능 하나를 검사하는 코드가 매우 해독하기 어렵게 짜여

있다. ❶ 테스트는 먼저 원소 두 개를 Dictionary에 넣는다. 다음의 for 순환문과 if 조건문에

서는 ❷ Dictionary가 반환한 Iterator를 순회하면서, ❸ 키에 따른 원소의 내용을 확인한다.

이 테스트의 목적은 Iterator 속에서 앞서 넣어둔 두 원소를 찾는 것이다. 그렇다면 우리는 그

의도가 명확히 드러나게 하고, 차후 Dictionary 클래스의 리팩토링 등으로 테스트를 수정해

야 할 때 실수할 확률을 줄여보도록 하자. 코드 상의 조건문은 모두 거쳐 갈 테니 그 안의 단언

문도 당연히 실행될 거라 착각하기 쉽다. 그러나 실제로는 하나도 실행되지 않을 수 있다. 겪고

싶지 않은 상황이다.

❶ Dictionary를채운다.

각 키의 반환값이올바른지 확인한다.

내용물을 하나씩

둘러보며 ❷

Page 75: 『Effective Unit Testing』 - 맛보기

175

신뢰성

CHAPTER 6

이 장의 내용

● 코드 주석과 관련된 테스트 냄새

● 미흡한 기대치 관리로 인한 테스트 냄새

● 조건부 실행과 관련된 테스트 냄새

Page 76: 『Effective Unit Testing』 - 맛보기

176 Part II 테스트 냄새

우리는 모두 신뢰로 가득 찬 아름다운 세상을 바란다. 주변 사람을 다 믿을 수 있다면 삶은 훨

씬 평온해질 거다. 코드도 마찬가지다. 테스트를 작성하는 이유이기도 한 신뢰할 수 있는 코드

를 만들기 위해서는 테스트 자체도 믿음직해야 한다. 소프트웨어 개발에는 코드를 수정하고 개

선하고 관리하는 일이 빠질 수 없는데, 만약 테스트를 믿지 못한다면 아무 관련 없어 보이는 코

드라도 쉽사리 바꾸지 못한다. 방심했다가는 자칫 큰코다칠 수 있기 때문이다.

6장에서는 테스트의 신뢰성 trustworthiness에 누를 끼치는 주요 요인과 믿지 못할 테스트로 인한 문

제 및 관련 테스트 냄새를 하나씩 살펴보는 시간을 가져보겠다. 첫 주인공은 주석을 잘못 사용

하는 경우다. 이해를 돕기 위한 설명글을 코드에 적어 넣을 수 있다는 것은 참 요긴한 기능이지

만, 자칫 한눈파는 순간 엉터리 정보로 둔갑해 있는 주석을 발견하게 된다. 한술 더 떠서, 본문

전체를 주석 처리한 테스트는 실제로는 아무것도 안 하면서도 은근슬쩍 실행된 후 성공했다고

보고한다.

다음으로, 순진한 프로그래머를 잘못된 길로 안내하는 깨어진 약속 혹은 오해를 낳는 약속을

살펴보려 한다. 실패해야 할 상황에서도 절대 실패하지 않거나 약속과 전혀 다른 일을 하는 테

스트를 보게 될 것이다. 마지막으로는 테스트 내의 조건문이 일으키는 신뢰성 문제를 파헤칠

것이다.

내용이 많으니 서두르자. 개인적으로 가장 좋아하는 주제 중 하나인 테스트 코드 속의 주석에 관한

이야기를 빨리 나눠보고 싶다.

신뢰성

CHAPTER 6

Page 77: 『Effective Unit Testing』 - 맛보기

177CHAPTER 6 신뢰성

6.1 주석으로 변한 테스트

실행되지 않는 주석을 소스코드에 넣어보자는 생각을 제일 처음 떠올린 이가 누구인지는 모르

겠다. 혹시 아는 독자분이 있다면 알려주면 고맙겠다. 어쨌든, 주석은 코드를 이해하는 데 큰

도움이 되니 참으로 고마운 기능이라 생각한다.

마냥 고맙기만 할 줄 알았던 이 주석도 때로는 우리를 혼란에 빠뜨리거나, 심지어 오해를 일으

키는 원인이 되기도 한다. 테스트 분야에서도 주석과 관련된 재미난 실패 사례를 찾을 수 있는

데, 주석으로 변한 테스트 Commented-out Test가 그중 하나다. 아무런 설명도 없이 읽는 이에게 혼란만

을 안겨주는 경우다. 코드를 설명한다는 주석 본래의 용도가 아닌, 그저 어설픈 버전 관리 수단

으로 사용된 것이다.

자! 한 번 살펴보자.

6.1.1 예시 얼마 전, 5년쯤 전에 작성했던 코드를 살펴볼 일이 있었다. 워낙 오랜만에 보는 코드라 가볍게

훑으면서 아키텍처를 되새겨보았다. 수많은 객체와 익숙해지기 위해 언제나처럼 가장 처음 살

펴본 것은 테스트 케이스였다. 코드의 실제 동작을 알기 쉽게 설명한 문서로 테스트만 한 것이

없기 때문이다.

어떤 I/O 작업을 담당하는 클래스의 테스트를 살펴보던 중, 15개쯤 되는 테스트 중 하나가 주석

처리된 걸 발견했다. 그런데 소스 파일 어디도 주석 처리한 이유는 언급되어 있지 않았다.

코드 6-1 이 테스트를 주석 처리한 이유는 무엇일까?

// @Test

// public void recognizesFileBeingModifiedWhenItIs() throws

// Exception {

// File newFile = File.createTempFile(getName(), ".tmp");

// FileAppendingThread thread = new FileAppendingThread(newFile);

// thread.start();

// thread.waitUntilAppendingStarts();

// try {

// Thread.sleep(200);

// assertTrue(IO.fileIsBeingModified(newFile));

Page 78: 『Effective Unit Testing』 - 맛보기

178 Part II 테스트 냄새

// } finally {

// thread.interrupt();

// }

// }

주석이 된 테스트를 발견하고 얼마나 당황했는지 모른다. 주석을 일반 평문으로 취급해서 들여

쓰기를 무시해버린 IDE를 한 번 투덜거리고는 바로 내용을 훑기 시작했다. 하지만 안타깝게도

왜 주석으로 만들었는지는 끝내 알아내지 못했다.

@Test 애너테이션만 주석 처리해도 같은 문제가 발생한다. 코드는 여전히 건재하고 컴파일도

잘 되는 정상 테스트로 보이지만, 애너테이션이 없다면 JUnit이 테스트로 인정하지 않는다.1

운이 좋아 애너테이션이 빠져 있음을 알아차려도 문제는 여전히 남아 있다. 과연 왜 빠진 것일

까? 단순 실수였을까? 아니면 누군가 잠시 지웠다가 되살리는 걸 깜빡한 것일까? 알 수 없다.

6.1.2 개선 방법전체 혹은 일부라도 주석으로 변한 테스트를 발견했다면 필시 죽은 코드와 마주하는 중이다.

그 코드에도 한때 어떤 의미와 목적이 있었겠지만, 이제는 사라졌거나 잊혀 버렸다. 왜 주석으

로 변한 채로 그곳에 남아 있는지 알 길이 없다.

다행히도 이 상황에서 우리가 취해야 할 행동은 명백하다. 먼저, 그 테스트에 무슨 일이 있었는

지 알만한 사람을 찾아 물어보자.2 기억하는 사람이 없다면 더 고민할 것도 없다.

1 목적을 이해하려 노력해보고 검증해본다. 성공했다면 주석을 풀고 파악한 목적이 더 잘 표현되게끔 리팩토링한다.

2 실패했다면 미련 없이 지워버린다.

그렇다! 지워버리자. 주석 처리된 코드 블록은 코드베이스에 단 일 초도 더 남아 있을 권리가

없다. 실행되지 않는 테스트 코드는 구현이 올바른지 검증하지도 못할뿐더러 설계 지침으로써

의 역할도 전혀 수행하지 못하는 한낱 잡음에 불과하다. 주석으로 변한 코드를 읽고 있는 건 마

1 저자주_ @Test 애너테이션과 메서드 선언부만 남겨놓고 본문 전체를 주석 처리할 경우도 궁금할 것이다. 테스트 프레임워크는 이 테스트를 아무 불평 없이 실행하고 심지어 성공했다고 보고하지만, 실상은 아무것도 검사한 것은 없게 된다. 이 문제는 6.4절에서 다시 살펴보겠다.

2 저자주_ 버전 관리 시스템에서 단서를 찾을 수도 있다. 편한 방법을 사용하자.

Page 79: 『Effective Unit Testing』 - 맛보기

179CHAPTER 6 신뢰성

치 정신 나간 사람의 독백을 이해하려 애쓰는 모습과 같다. 나중에 필요해지면 버전 관리 시스

템으로부터 되살려 내는 걸 말릴 생각은 없다. 그렇지만 당장은 삭제하는 게 낫다.

[코드 6-1]의 경우 결과적으로 테스트의 목적은 파악할 수 있었다. 그리하여 주석을 해제하고

테스트가 성공하는지 실행해보니 잘 돌아갔고 성공했다. 하지만 주석으로 만들었던 이유는 여

전히 안갯속이었다. 그런데 후에 이 테스트를 윈도우즈에서 돌려봤더니, 어라! 실패하는 게 아

닌가! 시간을 다루는 방식이 맥과는 조금 다르기 때문이었다

여기까지 오는 데 많은 시간을 허비했다. 그리고서 내린 결론은 이렇다. 이 테스트를 미처 다

작성하기 전에 다른 일이 생겼을 것이다. 그래서 급한 대로 우선 주석 처리하여 버전 관리 시스

템에 체크인했을 것이다. 그리곤 까맣게 잊어버리고 있던 것 같다.

이번 경우엔 다행히도 테스트의 목적을 되찾을 수 있었다. 하지만 그 목적이 오래전에 이미 사

라졌거나 의도를 잘 표현하지 못한 엉터리 코드에 묻혀버려, 차라리 지워버리는 게 나을 때도

많이 있다.

6.1.3 정리주석으로 변한 코드는 절대 실행될 수 없는 죽은 코드다. 처음에는 분명 어떤 의도로 만들어졌

겠지만, 일단 주석이 되면 그 가치는 빠르게 소실된다. 필수 개발 도구인 버전 관리 시스템을

사용한다면 더욱 빠르게 없어질 것이다. 머지않아 개발자는 그 테스트를 주석 처리했던 이유를

유추해야 하고 계속 유지할지를 고민해야 할 테니, 자연히 개발 생산성 저하로 이어진다.

이런 상황에서는 실용성을 추구해야 한다. 주석 처리한 이유를 바로 알아내지 못했다면 앞으로

도 영원히 알아내지 못할 가능성이 높다. 그렇다면 그 테스트를 깨끗이 제거하는 게 차라리 낫

다. 만약 놔둬야 할 이유를 알아냈다면 그 의도가 더 명확히 드러나도록 추가 조치를 반드시 취

해놓아야 한다.

정리하면, 주석으로 변한 테스트는 주석 처리한 이유를 찾기 위해 프로그래머의 아까운 시간을

좀먹게 하는 고약한 냄새다. 이어서 살펴볼 테스트 냄새 역시 주석과 잊힌 왜에 관한 것이지만,

전혀 다른 시각에서 바라본 것이다.

Page 80: 『Effective Unit Testing』 - 맛보기

180 Part II 테스트 냄새

6.2 오해를 낳는 주석

주석으로 변한 코드는 대부분 쓸모가 없고 최악에는 혼란을 불러일으킨다. 그런데 코드를 설명

한다는 본래의 목적으로 쓰인 진짜 주석에서도 악취가 풍기기도 하는데, 그중 특히 고약한 녀

석이 바로 오해를 낳는 주석 Misleading Comment이다.

오해를 낳는 주석은 거짓말을 늘어놓는 제멋대로인 녀석이다.3 그 주석을 곧이곧대로 믿고 엉

뚱한 곳에서 헤매는 프로그래머를 종종 볼 수 있다.

순진한 프로그래머를 약 올리는 오해를 낳는 주석을 예제를 통해 만나보자.

6.2.1 예시이번 예제는 채권 추심 기관의 회계 시스템에서 가져왔다. 만기가 넘은 미납 채권이 있는가를

기준으로 Account (계정)가 양호한지를 검사한다. 코드를 읽어보고 테스트가 하는 일을 파악

해보기 바란다.

코드 6-2 순진한 프로그래머는 자칫 오해를 할 수 있는 주석

@Test

public void pastDueDateDebtFlagsAccountNotInGoodStanding() {

// 기본 계정을 생성한다.

Customer customer = new Customer();

DeliquencyPlan deliquencyPlan = DeliquencyPlan.MONTHLY;

Account account = new CorporateAccount(customer, deliquencyPlan);

// 만기가 도래하지 않은 부채를 등록한다.

Money amount = new Money("EUR", 1000);

account.add(new Liability(customer, amount, Time.fromNow(1, DAYS)));

// 계정의 상태는 아직 양호해야 한다.

assertTrue(account.inGoodStanding());

// 만기일이 지나도록 시간을 변경한다.

Time.moveForward(1, DAYS);

3 저자주_ 잠재적으로 모든 주석이 이렇게 될 수 있다. 코드와 달리 직접 실행되지 않으니 그만큼 변질되기도 쉽기 때문이다.

Page 81: 『Effective Unit Testing』 - 맛보기

203

여흥거리

Part

III3부의 원래 제목이었던 ‘고급 과정’은 다소 무겁고 강한 느낌 때문에 오해의 소지가 있다고 판단했다. 그래서

더욱 적합한 이름인 ‘여흥거리’로 변경했다. 고급 개발자가 되기 위한 ‘필수 과정’은 아니지만, 충분히 유용하고

흥미로운 여흥거리로 이뤄져 있기 때문이다.

3부에서는 독자의 능력을 최대치까지 끌어올려줄 주제를 다룬다. 7장에서는 ‘설계’라는 다소 복잡미묘한 영역

에 발을 디뎌 ‘테스트 가능’ 설계를 이끄는 혹은 방해하는 그림을 완성해볼 것이다. 실제로도 테스트를 얼마나

잘 작성할 수 있느냐는 결국 코드 설계 능력에 크게 영향받을 수밖에 없다.

방향을 180도 틀어서, 8장에서는 자바로 작성된 코드와 그 외의 JVM 프로그래밍 언어로 작성한 테스트가

뒤섞인 모습을 살펴본다. 진행 중인 프로젝트에 당장 써먹기에 좋은 생각일 수도, 그렇지 않을 수도 있다. 하지

만 자바 외에 JVM에서도 동작하는 언어가 있다는 것은 분명한 사실이고, 언젠가 그중 하나로 테스트를 작성

할 수 있음에 감사할 날이 올지도 모른다.

3부의 마지막은 느려터진 빌드라는 더 현실적인 주제를 이야기한다. 일반적으로 프로젝트가 진행될수록 빌드

는 점점 느려지며, 참기 어려울 만큼 피드백 주기가 길어지고 나서야 개선을 시도한다. 빌드 시간 단축을 주제

로 9장을 따로 마련한 이유다. 테스트 코드 최적화와 빌드 실행 방식 개선을 모두 다뤄볼 것이며, 특히 전자의

경우 빌드 시간 단축에 크게 이바지할 할 때가 많으니 잘 익혀두기 바란다.

즐길 준비 되었는가?

Page 82: 『Effective Unit Testing』 - 맛보기
Page 83: 『Effective Unit Testing』 - 맛보기

205

테스트 가능 설계

CHAPTER 7

이 장의 내용

● 테스트 가능 설계란 무엇인가?

● 테스트 불가 설계란 무엇인가?

● 테스트 가능한 설계는 어떻게 만드는가?

Page 84: 『Effective Unit Testing』 - 맛보기

206 Part III 여흥거리

『Implementation Patterns』1의 서문에서 켄트 벡은 프로그래밍을 제퍼디Jeopardy라는 미국 TV

쇼에 비유하였다. MC가 답변을 제시하면 참가자는 거꾸로 그 답변을 유도한 질문을 추측해내

는 쇼다. 예를 들면 이렇다.

● MC “책 마지막의 짧은 섹션.”

● 참가자 “에필로그란?”

● MC “정답!”

켄트는 다음과 같은 점에서 프로그래밍이 이 쇼와 유사하다고 이야기했다. 자바는 언어 구조라

는 형태로 답변을 제시한다. 그러면 프로그래머는 그 언어 구조로 풀 수 있는 문제와 질문이 무

엇인지 찾아내는 역할을 수행한다. 켄트가 제시한 예는 이렇다. “필드의 타입을 Set으로 선언

한다.”라는 답변이 있다면 적절한 질문은 아마도 “이 필드는 중복을 허용하지 않는 컬렉션이란

걸 다른 프로그래머가 알 수 있게 하려면?”일 것이다.

설계할 때도 마찬가지다. 우리는 다양한 교육을 통해서 그리고 특히 신입사원 시절에 많은 해

답을 배우게 된다. 회사 선배로부터 자신이 업무를 처리하는 방식을 하사받고, 참여할 과제의 코드

에서 코딩 규약을 찾아내고, 본 책과 같은 전문 서적을 통해서도 많은 것을 얻어낸다.

하지만 해답을 아는 것으론 충분하지 않다. 그 해답이 풀어내는 문제가 무엇인지도 배워야 한

다. 본 책에서 테스트 냄새를 다루는 이유도 여기에 있다.

1 저자주_ 켄트 벡, Addison Wesley, 2007. 번역서 『켄트 벡의 구현 패턴』 (전동환 역, 에이콘, 2008)

테스트 가능 설계

CHAPTER 7

Page 85: 『Effective Unit Testing』 - 맛보기

207CHAPTER 7 테스트 가능 설계

7장에서는 메서드에 static이나 final 키워드를 붙일지와 같은 설계상의 결정 때문에 자주 발생

하는 테스트 용이성 문제를 짚어 본다. 가장 먼저, 테스트 가능 설계 혹은 모듈러 설계 Modular Design

를 원한다는 말의 의미부터 알아볼 것이다. 특정 설계 원칙은 잘 지키고, 반대로 테스트 작성을

어렵게 하는 특정 구현 방식은 피해야 한다는 걸 알게 될 것이다. 그다음으로는 가능한 피해야

할 테스트 용이성 문제를 자세히 훑고, 마지막으로는 테스트 가능 설계를 위한 간단한 지침을

살펴볼 것이다.

7.1 테스트 가능 설계란?

테스트 가능 설계의 가장 큰 의의는 당연히 코드를 더 잘 테스트할 수 있도록 해준다는 것이다.

『The Art of Unit Testing with Examples in.NET』2의 저자인 로이 오셔로브Roy Osherove의 말을

인용하면 “제품 코드는 단위 테스트를 쉽고 빠르게 작성할 수 있도록 설계해야 한다.” 더 구체

적으로는, 테스트 가능 설계는 테스트 코드에서 클래스를 생성하고, 구현 일부를 대체하고, 다

른 시나리오를 시뮬레이션하고, 원하는 실행 경로를 선택하는 등의 작업을 쉽게 할 수 있도록

해준다.

테스트할 수 있느냐는 질문은 예 혹은 아니오로 딱 잘라 대답할 성격의 문제가 아니다. 이에 대

해 스콧 벨웨어Scott Bellware는 이렇게 말했다. “ 테스트 용이성 testability이란 테스트할 수 있는 소프트

웨어냐 아니냐를 설명하는 용어가 아니다. 그보다는 소프트웨어를 얼마나 쉽게 테스트할 수 있

느냐를 평가하는 용어다.”3 단위 테스트를 위한 시나리오 준비는 식은 죽 먹기여야 한다. 테스

트 용이성이 떨어질수록 테스트를 작성하는 프로그래머의 부담이 커진다. 그렇다면 테스트 가

능 설계는 어떤 요소로 이루어지고 어떻게 하면 만들어낼 수 있을까?

아직도 소프트웨어 개발 산업은 성숙기로 접어들지 못했다는 빈정 섞인 이야기를 가끔 듣겠지

만, 우리는 적어도 수십 년간의 지난 경험으로부터 다양한 프로그래밍 패러다임을 만들어 시도

해보며 소프트웨어 구축에 관해 많은 것을 터득할 수는 있었다. 모듈러 설계는 그렇게 터득한

미덕 중 하나다.

2 저자주_ 매닝, 2009. 번역서 『.NET 예제로 배우는 단위 테스트』 (송인철, 황인석 역, 인사이트. 2010)

3 저자주_ 스콧 벨웨어, “Good Design is Easily-Learned”, 2009년, http://mng.bz/IAMK.

Page 86: 『Effective Unit Testing』 - 맛보기

208 Part III 여흥거리

7.1.1 모듈러 설계제품은 특정 역할을 담당하는 독립 모듈로 구성된다는 그 본질만 잘 반영하면 자연스럽게 모듈

러 설계가 만들어진다. 제품의 전체 기능을 뚜렷한 역할로 나누고 그 역할 각각을 독립된 구성

요소에 맡기면 최종적으로 상당히 유연한 설계를 만들게 된다.

자신의 역할 완수에 필요한 모든 것을 갖춘 독립 모듈을 조합하여 전체 설계를 완성하려면 다

양한 모듈 간 연결 인터페이스가 필요하다. 그리고 그 인터페이스가 바로 유연성을 높여주는

원천이다. 이와 같은 프로그래밍 방식에서는 [그림 7-1]처럼 모듈 사이의 종속성을 최소한으로

줄이는 것을 목표로 한다.

그림 7-1 모듈러 설계는 모듈 간 종속성을 최소화한다.

작은 구성요소를 조합하여 소프트웨어를 구축하는 방식은 여러 개발자가 협력해서 큰 제품을

만들 때 특히 유용하다. 향후 기능을 변경할 때 영향을 받을만한 코드가 일부 모듈로만 국한될

가능성이 높기 때문이다. 이는 한 시스템을, 특정 기능을 전담하는 독립된 기능 요소로 나누어

구성하는 모듈러 설계의 큰 특징이다.

모듈 방식으로 설계된 구조에서는 기존 모듈을 교체하면 기능이 변하고 새로운 모듈을 추가하

면 없던 기능이 생긴다. 물론 모듈 간의 종속성이 낮고 역할 수행에 필요한 정보를 각자가 온전

히 가지고 있어야 하며, 전체가 잘 맞물려 돌아갈 수 있도록 각 기능이 제품 코드에 적절히 분

배되어 있어야 한다. 이와 같은 모듈러 설계의 특징은 자연스럽게 테스트하기 좋은 코드로 만

들어주므로 테스트 용이성 개선으로 이어진다.

오랫동안 수많은 프로그래머가 이러한 이상을 몸소 실천해보았고 열심히 시도해볼 만한 목표

임을 확인해 주었다. 모듈러 설계란 개념 자체는 다소 막연하지만, 그렇다고 걱정할 필요는 없

Page 87: 『Effective Unit Testing』 - 맛보기

209CHAPTER 7 테스트 가능 설계

다. 고맙게도 많은 선대 개발자가 힘겹게 터득한 값진 경험을 제법 구체적인 설계 원칙으로 잘

정리해두었으니 말이다. 감사하는 마음으로 함께 배워보자.

7.1.2 SOLID 설계 원칙

그간 수많은 설계 원칙이 만들어졌다. 그중 제법 많이 알려진 것으로 SOLID 원칙이란 게 있다.

여기서 SOLID는 로버트 마틴Robert C. Martin이 고안한 다섯 원칙의 앞글자를 따서 만든 약자다.

SOLID와 같은 객체지향 설계 원칙은 테스트 용이성과도 잘 어울린다. 그리고 코딩 시 이런 설

계 원칙을 잘 지켜주면 모듈러 설계가 될 가능성이 상당히 높아진다.

그럼 SOLID 원칙이 무엇이고, 어떤 식으로 테스트하기 쉬운 설계에 이바지하는지 알아보자.

단일 책임 원칙

단일 책임 원칙 SRP, Single Responsibility Principle은 “클래스를 수정해야 하는 이유는 오직 하나뿐이어야

한다.”는 원칙이다. 다르게 말하면, 클래스는 작고 한 가지 역할에만 충실하고 응집력이 높아야

한다. 여기서 끝이 아니다. 메서드를 수정해야 하는 이유 역시도 하나뿐이어야 한다. 이것이 바

로 산드로 맨쿠소Sandro Mancuso가 내면 inside view이라 부르는 속성이다.4

단일 책임 원칙을 지키며 작성한 코드는 이해하기 쉽고 원하는 부분을 빠르게 찾을 수 있다. 자

연스럽게 테스트하기도 쉬워진다. 테스트라는 것의 본질이 기대하는 동작을 설명하고 코드가

풀려는 문제를 이해해서 서술하는 활동이기 때문이다. 나아가 바로 전에 언급한 내면까지 잘

챙겨주면, 개별 메서드도 짧고 간결해져서 이를 검증하는 테스트의 복잡도도 같이 낮아진다.

개방 폐쇄 원칙

개방 폐쇄 원칙 OCP, Open-Closed Principle이란 “클래스는 확장을 위해서는 개방적이되 수정에 대해서

는 폐쇄적이어야 한다.”는 원칙이다. 쉽게 말하면, 코드 수정 없이도 클래스의 기능을 변경할

수 있도록 하자는 얘기다. 필요에 따라 다른 전략으로 변경할 수 있는 전략 패턴 strategy pattern이

대표적인 예다.

4 저자주_ “SRP: Simplicity and Complexity”, 2011년 7월 26일, http://mng.bz/08ks 참고

Page 88: 『Effective Unit Testing』 - 맛보기

210 Part III 여흥거리

자신의 역할 일부를 다른 객체에 위임하도록 만들어진 클래스라면 테스트에서도 그 기능을 테

스트 더블로 교체하여 원하는 시나리오를 시뮬레이션할 수 있다.

리스코프 치환 원칙

리스코프 치환 원칙 LSP, Liskov Substitution Principle이란 “상위 클래스는 하위 클래스로 대체될 수 있어

야 한다.”는 원칙이다. 한마디로 클래스 A의 인스턴스를 사용하는 코드에 A의 하위 클래스인

B의 인스턴스를 넣어도 문제없이 동작해야 한다는 뜻이다.

리스코프 치환 원칙을 지키면 정당한 근거로 만들어진 클래스 계층 구조만 남게 된다. 즉, 코

드 재사용을 편하게 할 요량으로 만들어진 계층 구조는 사라진다. 테스트 용이성 입장에서는

SOLID 원칙 중 중요도가 가장 떨어지는 건 사실이지만, 이 원칙을 따르지 않은 구조는 테스트

추종 프로그래머에게 불리할 수 있다. 리스코프 치환 원칙이 잘 지켜진 클래스 계층 구조라면

계약 테스트 Contract Test가 가능하여 테스트 용이성이 높아진다. 계약 테스트란 인터페이스에 정의

된 기능을 제공하겠다는 계약을 그 구현체가 제대로 지키는지 검증하는 것을 말한다. 즉, 인터

페이스 동작을 확인하는 테스트 스위트 하나로 그 인터페이스를 구현한 클래스 모두를 검증하

는 걸 말한다.

인터페이스 분리 원칙

인터페이스 분리 원칙 ISP, Interface Segregation Principle이란 “하나의 범용 인터페이스보다 쓰임새별로

최적화한 인터페이스 여러 개가 낫다.”는 원칙이다. 한마디로 인터페이스는 작고 한 가지 목적

에 충실하도록 만들어야 한다는 뜻이다.5

인터페이스가 작으면 테스트 더블도 쉽게 작성할 수 있어 테스트 용이성도 같이 좋아진다. 예

를 들어 협력 객체 A의 스텁, 협력 객체 B의 가짜 객체, 여기에 협력 객체 C의 스파이까지 필

요한 테스트가 있다고 가정해보자. 이들 세 협력 객체의 인터페이스가 모두 작다면 테스트 더

블도 쉽게 만들 수 있다.

의존 관계 역전 원칙

의존 관계 역전 원칙 DIP, Dependency Inversion Principle이란 “코드는 구현체가 아닌 추상 개념에 종속되

5 저자주_ 익숙한 이야기일 것이다. 혹자는 인터페이스 분리 원칙을 “단일 책임 원칙의 인터페이스 버전”이라 부를 정도니 말이다.

Page 89: 『Effective Unit Testing』 - 맛보기

211CHAPTER 7 테스트 가능 설계

어야 한다.”는 원칙이다. 극단적으로 보면, 의존 관계 역전 원칙에 따르면 클래스는 협력 객체

를 직접 생성하지 말고 인터페이스로 건네받아야 한다.

협력 객체를 외부에서 넘겨줄 수 있다는 말은 테스트 대상 코드를 오버라이딩하지 않고도 기능

을 변경할 수 있다는 말과 같다. 테스트 작성자에게는 굉장히 반가운 소식이다. 이와 같은 종속

객체 주입 방식을 적용하면 협력 객체를 마음대로 교체할 수 있음은 물론이고, 제품 코드가 사용

하는 방식 그대로 테스트할 수 있기 때문에 테스트 용이성이 크게 좋아진다.

자주 쓰이는 설계 원칙을 이해하고 어떻게 구현할지 그려보고 그렇게 구현했을 때의 영향을 미

리 파악해내는 능력은 프로그래머에게 상당히 중요하다. 장차 여러분도 많은 원칙을 익히고 코

드에 적용하게 될 것이다. 그런 경험과 지식은 멋진 설계를 만드는 데 큰 밑거름이 될 것이다.

7.1.3 맥락을 고려한 모듈러 설계

맥락을 고려한 모듈러 설계는 그리 간단하지 않다. 비록 주어진 문제에 대한 해법을 찾는 능력

은 뛰어날지 몰라도, 그 해법이 더 거대한 전체 시스템과도 잘 어울리는지까지 함께 고려하는

건 훨씬 어렵기 때문이다. 월드와이드웹WWW, World Wide Web의 창시자인 팀 버너스 리Tim Berners-Lee

는 다음과 같은 설계 원칙을 제시했다.

모듈을 조합하여 시스템을 구성할 수 있게 하는 것은 물론 중요하다. 하지만 당장은 그 시스템

이 아무리 크고 멋있어 보이더라도 언젠가 더 큰 시스템의 일부로 될 수 있도록 설계해야 한다.6

다시 말해, 여러분이 당장 사용하려는 방식은 물론 향후의 확장까지 고려해야 좋은 설계가 완

성된다. 그래도 여러분은 운이 좋은 편이다. 일반적인 설계 원칙을 따르면서 더 큰 맥락에서도

문제없는 모듈러 설계 방법을 여기 준비해 두었으니 말이다.

7.1.4 모듈러 설계를 위한 시운전모듈러 설계를 익히는 가장 빠른 방법은 역시 자신의 코드로 직접 시운전해보는 것이다. 제품

코드보다 테스트를 먼저 작성하게 되면 확실히 API의 사용자인 고객의 관점에서 바라보게 된

6 저자주_ 팀 버너스 리의 “Principles of Design” (설계 원칙), http://www.w3.org/DesignIssues/Principles.html 참고.

Page 90: 『Effective Unit Testing』 - 맛보기

www.hanbi t .co.kr

이것이 프로그래밍이다!

이것이 안드로이드다

진정한 안드로이드 개발자로 이끌어줍니다.

SDK 5.0 롤리팝 호환!

책만 보고,

동영상 강좌로도 만족하지 못했다면 Daum 카페 '슈퍼드로이드'에서 만나요

cafe.daum.net/superdroid

박성근 저 | 1,164쪽 | 45,000원

이것이 C언어다

세상에 없던 새로운C언어 입문서 탄생!

삼성, LG에서 펼쳐졌던 전설의 명강의를 풀타임 동영상 강좌로!

이보다 더 확실한 방법은 없다, 칠판강의

전체 동영상 강좌 유투브 전격 공개!

http://goo.gl/tJK3Tu

서현우 저 | 708쪽 | 25,000원

이것이 자바다

가장 중요한 프로그래밍 언어를 하나배워야 한다면, 결론은 자바다!

중급 개발자로 나아가기 위한 람다식,

JavaFX, NIO 수록

자바의 모든 것을 알려주는 인터넷 강의궁금한 것은 카페에서!

cafe.naver.com/thisisjava

신용권 저 | 1,224쪽 | 30,000원

저자 직강 동영상 제공!

Page 91: 『Effective Unit Testing』 - 맛보기

모던 웹을 위한

JavaScript + jQuery 입문

www.hanbi t .co.kr

지금은모던 웹 시대!

HTML5 분야 부동의 1위 도서HTML5 표준안 확정에 맞춘 완전 개정판의 귀환!

HTML5 권고안과 최신 웹 브라우저 환경 대응

윤인성 저 | 624쪽 | 30,000원

모던 웹 디자인을 위한

HTML5 + CSS3 입문

자바스크립트에서 제이쿼리, 제이쿼리 모바일까지 한 권으로 끝낸다!

시대의 흐름에 맞춰 다시 쓴 자바스크립트 교과서

윤인성 저 | 980쪽 | 32,000원

페이스북, 월마트, 링크드인은 왜 Node.js를 선택했는가?

이 물음에 대한 답은 Node.js가 보여주는 빠른 처리 능력 때문이다.

윤인성 저 | 484쪽 | 25,000원

모던 웹을 위한

Node.js프로그래밍

필요한 것만 배워 바로 현장에서 쓰는 HTML5순서대로 읽으며 실습할 수 있는 HTML5 자습서

김상형 저 | 700쪽 | 32,000원

HTML5 + CSS3 정복

Page 92: 『Effective Unit Testing』 - 맛보기

Hanbit eBook Realtime

DRM free!어떤 디바이스에서도 자유롭게

eBook Oriented!전자책에 꼭 맞는 최적의 내용과 디자인

Hanbit eBook

Realtime 70

김세훈 지음

MFC프로그래밍주식분석 프로그램 만들기

이연복 지음

Hanbit eBook

Realtime 49Hanbit eBook

Realtime 89

자바 개발자를 위한

Vert.x애플리케이션 개발

Hanbit eBook

Realtime 49Hanbit eBook

Realtime 92

모바일/웹 메시징STOMP와 MQTT로 개발하는 IoT

모바일/웹 애플리케이션

제프 메스닐 지음 / 조건희 옮김

Mobile and Web Messaging

Hanbit eBook

Realtime 90

JavaScriptPromise azu 지음 / 주우영 옮김

www.hanbi t .co.kr /ebook

www.hanbi t .co.kr

Page 93: 『Effective Unit Testing』 - 맛보기

전자부품 백과사전 vol.1찰스 플랫 지음 / 배지은 옮김 / 30,000원

취미공학에 필요한 핵심 전자부품을 사전식으로 정리한 안내서.

Zero to Maker: 누구나 메이커가 될 수 있다데이비드 랭 지음 / 장재웅 옮김 / 14,000원

일반인에서 메이커로. 날백수에서 무인 잠수정 회사 CEO가 된 사나이, 데이비드 랭의 메이커 도전기.

처음 시작하는 센서키모 카르비넨,테로 카르비넨 지음임지순 옮김 / 13,000원

세상을 수치로 읽어내는 부품인 센서를 알려주는 책. 이 책을 통해 자신만의 프로젝트에 다양한 센서를 사용해보자.

프로젝트로 배우는 라즈베리 파이 도날드 노리스 지음 / 임지순 옮김

다양한 실전 프로젝트를 통해 라즈베리 파이를 쉽고 재미있게 배워본다.

Maker Pro 존 베이첼 지음 / 가격미정

메이커라면 반드시 읽어야 할 필수 계발서. 프로 메이커들과의 인터뷰 및 에세이 수록.

Make: 센서 키모 카르비넨, 테로 카르비넨, 빌 발토카리 지음 / 가격미정

필수 전자부품인 센서를 마이크로 컨트롤러 보드에 응용하는 방법을 담았다.

전자부품 백과사전 vol.2 찰스 플랫 지음 / 가격미정

<전자부품 백과사전> 시리즈의 두 번째 도서다.

즐거운 상상이 가득! 2015년 화제의 신간

전자부품 백과사전 vol.1찰스 플랫 지음 / 배지은 옮김 / 30,000원

취미공학에 필요한 핵심 전자부품을 사전식으로 정리한 안내서.

Zero to Maker: 누구나 메이커가 될 수 있다데이비드 랭 지음 / 장재웅 옮김 / 14,000원

일반인에서 메이커로. 날백수에서 무인 잠수정 회사 CEO가 된 사나이, 데이비드 랭의 메이커 도전기.

처음 시작하는 센서키모 카르비넨,테로 카르비넨 지음임지순 옮김 / 13,000원

세상을 수치로 읽어내는 부품인 센서를 알려주는 책. 이 책을 통해 자신만의 프로젝트에 다양한 센서를 사용해보자.

프로젝트로 배우는 라즈베리 파이 도날드 노리스 지음 / 임지순 옮김

다양한 실전 프로젝트를 통해 라즈베리 파이를 쉽고 재미있게 배워본다.

Maker Pro 존 베이첼 지음 / 가격미정

메이커라면 반드시 읽어야 할 필수 계발서. 프로 메이커들과의 인터뷰 및 에세이 수록.

Make: 센서 키모 카르비넨, 테로 카르비넨, 빌 발토카리 지음 / 가격미정

필수 전자부품인 센서를 마이크로 컨트롤러 보드에 응용하는 방법을 담았다.

전자부품 백과사전 vol.2 찰스 플랫 지음 / 가격미정

<전자부품 백과사전> 시리즈의 두 번째 도서다.

즐거운 상상이 가득! 2015년 화제의 신간

www.hanbi t .co.kr

Page 94: 『Effective Unit Testing』 - 맛보기