검색결과 리스트
TDD에 해당되는 글 16건
- 2010/10/30 2장 테스트(3) - 스프링 테스트와 학습 테스트
- 2010/10/30 2장 테스트(2) - UserDaoTest 개선과 JUnit 프레임워크
- 2010/10/30 2장 테스트(1) - UserDaoTest 다시 보기
- 2010/07/12 테스트 주도 개발 TDD 실천법과 도구
- 2009/10/07 복식부기와 TDD (바뀌는 글) (8)
- 2008/11/21 TDD가 좋은 줄 알면서도 안하는 이유 (2)
- 2008/09/18 이클립스를 사용하는 TDD 기초 동영상 튜토리얼
- 2008/08/29 UML 모델링과 TDD
- 2008/08/13 당신이 생각하는 TDD (6)
- 2008/07/31 TDD isn’t about testing (2)
- 2008/06/26 마이크로테스트(microtests) (6)
- 2008/04/25 꼬리에 꼬리를 무는 '쾌적한 프로젝트 수행 환경' 만들기
- 2008/04/17 Test Driven Development 전도 일지 2
- 2008/04/12 Test Driven Development 전도 일지 1
- 2007/03/02 삼각측량(Triangulation)을 보완하는 UnsupportedOperationException 발생 기법 (2)
- 2006/09/21 테스트 주도의 방식과 아키텍처 중심적 접근의 근본적 차이
글
2장 테스트(3) - 스프링 테스트와 학습 테스트
사진 출처: 이프릴(Epril) 스프링 세미나 풍경
그간 스프링의 테스트 지원 기능은 Java 5 프로그래밍 요소에 맞춰 우아하게 변모하여 Spring TestContext Framework로 발전했다.
이와 관련한 책 읽기 모임 의견을 들어보자. 찬욱군은 TestContext를 학습하면서 JUnit 3.8과 JUnit 4.x 호환성을 지키는 부분에 놀랐다고 한다. 나는 주입을 위해 굳이 생성자나 수정자가 필요하지도 않게 배려한 부분에서 감동했다. :)
테스트 코드에 의한 DI
책을 따라 실습해보면 무리 없이 스프링 테스트 컨택스트를 배울 수 있다. 다만, @DirtiesContext 내용이 정확히 와 닿지 않았다. 이런 경우라면 다음 절에 소개하는 학습 테스트를 만들어 명확히 할 수 있다. 여러 개의 테스트 클래스를 만들고 한 쪽에 정적 변수로 ApplicationContext 개체를 참조할 수 있게 한다.
@ContextConfiguration(locations="/testcontext/applicationContext.xml")
public class AaaTest {
static ApplicationContext firstContext;
그리고 테스트 클래스에서 정적 변수를 한 번은 초기화하게 한다.
public void setUp(){
if(AaaTest.firstContext == null) AaaTest.firstContext = applicationContext;
}
마지막으로 정적 변수와 멤버 변수인 applicationContext가 가리키는 개체가 같은지 비교하는 테스트를 여러 개 만든다. @DirtiesContext를 클래스에 붙였다가 메소드에 붙여본다.
public void test1(){
assertEquals(AaaTest.firstContext, applicationContext);
}
@DirtiesContext
@Test
public void test2(){
assertEquals(AaaTest.firstContext, applicationContext);
}
@DirtiesContext를 클래스에 붙이면 해당 클래스가 끝날 때 애플리케이션 컨텍스트가 새로 만들어진다. 메소드에 붙이면 해당 메소드가 끝나면 새로 만들어진다.
저자의 꼼꼼함을 들여다볼 수 있긴 했으나, 찬욱군은 현장에서 @DirtiesContext가 필요한 경우에 대해 아직 감이 없다고 한다.
컨테이너 없는 DI 테스트
2장까지 내용을 충분히 이해했다면 쉽게 읽고 넘어갈 내용이지만, 사실 관심사 분리(SoC) 훈련이 없다면 이해하지 못할 내용이 아닐까 싶다. 혹시 발췌한 내용이 이해가 가지 않거나 갑자기 왜 이런 설명을 하는지 이해가 안 간다면 안타깝지만 1장을 다시 읽어야 한다. :)
DI를 이용한 테스트 방법 선택
다양한 방법을 소개하고 마지막에 선택 기준을 제시한다. 1번은 역시 스프링 컨테이너조차 없는 테스트다. 앞서 설명한 고립성과 시간 절약은 스프링 컨테이너에도 그대로 해당한다. 스프링 컨테이너를 이용하는 기준은 무엇인가? 여러 개체와 복잡한 의존관계를 갖는 개체를 테스트할 경우다. 이 경우는 테스트 설정을 따로 만드는 방법이 좋다. 세 번째는 예외적인 의존관계를 강제로 구성할 경우다. 이때는 앞서 학습한 @DirtiesContext가 유용하다.
§ 2.5 학습 테스트로 배우는 스프링
작년인가 월간 마소에 JUnit을 이용한 코드 개선 과정 녹화라는 글을 기고한 일이 있다. 개발 후 운영자도 내막을 잘 모르는 레거시 코드를 학습하는 과정과 이를 공유하는 수단으로 테스트를 사용한 경험담이다. 저자가 열거한 학습 테스트 다섯 가지 장점을 그 경험에 대입해보니 그대로 들어맞는다. 학습 테스트는 과정에서 배우는 바도 매우 크지만, 결과를 보존하면 문서로는 전달하기 어려운 생생한 재현을 보장한다. 특히나 프레임워크나 제품의 주요 기능 혹은 API 쓰임새를 포괄하는 테스트를 만들어 둔다면 마치 TCK(Technology Compatibility Kit)처럼 훌륭한 호환성 검증 도구 역할을 할 수 있다.
글
2장 테스트(2) - UserDaoTest 개선과 JUnit 프레임워크
157쪽에서 수정 후에는 성공 메시지가 "조회 테스트 성공"으로 바꾸어야 하는데 출판사 정오표에는 빠진 오류인 듯하다. 159쪽 하단에 JUnit 필수 조건에도 void 가 빠져 있다. 170쪽에 내용이 나오는 내용인지라 저자가 실수로 빠뜨린 듯하다. 저자는 스프링 3.0에서 처음 등장한 설정 방법부터 소개한 것처럼 assertEquals() 메소드보다 assertThat() 메소드부터 소개한다. assertThat은 예전에 만든 교육 자료가 있어 일부를 공개한다.
Joe Walnes의 글은 Flexible JUnit assertions with assertThat()이다. assertThat 자체는 JUnit에 들어갔지만, 책에서 사용하는 Matcher는 hamrest 라이브러리에 있다. 책 읽기 모임에서 나온 의견을 덧붙여 보자. JUnit을 충분히 써본 찬욱군은 굳이 assertThat을 사용하는 이유를 모르겠다고 한다. 이에 대해 용권씨는 Scala 학습 전에는 자신도 몰랐으나 이제는 DSL로 대변하는 가독성 있는 스타일에 대해 이해가 가며, assertThat도 같은 맥락으로 이해한다고 했다. 내 생각엔 assertThat 자체는 영어문화권이 아닌 우리에게 큰 의미는 없다. 다만, Matcher는 (클래스 수준 이하) 단위 테스트를 돕는 훌륭한 개념이기 때문에 은근 슬쩍 스프링 학습과정에서 배웠으면 하는 저자의 노파심이 아닐까 싶다. 찬욱군 의견처럼 161쪽 리스트 2-5는 assertEqual이 차라리 낫다. :)
JUnit에 hamcrest가 포함되어 있긴 하지만, 테스트 주도 개발 TDD 실천법과 도구에 따르면 최신 버전(1.2)은 직접 내려받아야 한다. hamcrest에 대한 자세한 내용은 테스트 주도 개발 TDD 실천법과 도구에서 배울 수 있다. 134쪽부터 약 15쪽 정도 분량으로 hamcrest 기초부터 확장 방법까지 설명하고 있다.
테스트 주도 개발 - 
채수원 지음/한빛미디어
205~206쪽을 보면 종전 사용법까지 포함한 여러 가지 확인 코드 작성 방식을 보여준다. 저자는 더 나아가 자신의 블로그를 통해서 JUnit assert 매쉬업이라는 최신 기법까지 소개하고 있다.
개발자를 위한 테스팅 프레임워크 JUnit
저자는 다시 한번 테스트를 강조한다.
역시 지나치다 싶을 정도로 초강수를 둔다. 하지만, 단위 테스트가 주는 이점(품질 향상, 빠른 개선 주기 지원)을 공감하지 못하는 개발자에게 이 정도 위협(?)이 먹힌다면 의미는 있다. 필자는 다른 회사가 개발을 맡는 2번의 프로젝트에서 DAO 단위 테스트만 의무화했던 경험이 있는데 모두 예상치를 웃도는 효과를 얻었다. '우리도 한번 해보자!'라고 하시는 분은 저렴한 방법으로는 테스트 주도 개발 TDD 실천법과 도구 학습이 있고, 비용이 들어가는 방법으로는 필자에게 컨설팅 요구를 하실 수 있다. :)
동일한 결과를 보장하는 테스트
혼동하기 쉬운 내용을 잘 지적하는 내용이다. 외부 요인이나 실행 순서가 테스트에 영향을 미치지 않도록 실험실 꾸미듯 단위 테스트를 만들어야 한다.
토스 177쪽을 보면, 스프링의 창시자인 로드 존슨이 '항상 네거티브 테스트를 먼저 만들라'라고 조언했다고 하는데 정말 그럴까? Expert One-on-One J2EE Development without EJB 430쪽에 Negative Tests라는 내용이 나온다. '항상 먼저 만들라'라고 한 내용은 찾을 수 없지만, 바람직한 작동을 위한 테스트만큼이나 비정상적일 때에 대한 테스트도 잊지 말 것을 강조하고 있다. 영어라 접근에 제약은 있지만, Expert One-on-One J2EE Development without EJB 14장은 J2EE/Java EE 영역에 있어서만큼은 가장 얇으면서 풍부한 내용을 담은 주옥같은 글이니 꼭 읽어보길 권한다. 사실 많은 내용이 토스에 녹아 있긴 하다.
책 읽기 모임에서 용권씨가 막장(?) 사례를 말해줬다. 이를 테면 assertTrue(true)와 같은 코드로 단위 테스트를 한 경우도 있단다. ㅡㅡ;
반면에 찬욱군은 외부 솔루션이나 고객 사이트 환경을 모르고 대답해주는 이가 없어서 대충 테스트 했던 과거를 회계했다.
테스트 코드 개선
테스트 순서나 외부 요인에 영향을 받지 않는 고립 테스트의 중요성은 여러 차례 이야기했다. JUnit은 이를 위해 테스트마다 별도 개체를 생성한다. 토스 2장에서는 실습과 함께 체득하기 때문에 효과적으로 익힐수 있다.
픽스처
테스트 픽스처(Test fixture)에 대한 위키피디아 정의를 옮겨본다.
Test fixture refers to the fixed state used as a baseline for running tests in software testing. The purpose of a test fixture is to ensure that there is a well known and fixed environment in which tests are run so that results are repeatable. Some people call this the test context.
글
2장 테스트(1) - UserDaoTest 다시 보기
아래 발췌한 도입부 내용은 테스트에 대한 강조가 지나쳐 마치 스프링을 볼모로 독자를 위협하는 듯한 인상을 받는다.
스프링 프레임워크 자체는 테스트를 강제하지 않는다. 그럼에도 저자가 2장 전반에 걸쳐 무리다 싶을 정도로 강조하는 이유는 무엇일까? 추측건대 테스트를 거의 하지 않는 개발 풍토 탓이 아닐까? 2005년부터 현재까지 참여한 9개 프로젝트 가운데 단위 테스트 작성에 성공(?)한 경우는 5회이고, 실패한 경우는 4회다. 다른 이유를 배제하면 단위 테스트에 성공한 다섯 번은 모두 납기 내에 시스템을 오픈했다. 단위 테스트 작성을 도입하지 못한 4번 중 3개의 프로젝트는 필자가 조기 철수하는 프로젝트여서 결말은 모르지만 무리한 야근이 있었다는 점은 확인할 수 있었다. 그리고 필자의 참여 비중(주 1회)이 낮아 어찌할 수 없이 실패를 지켜봤던 1번은 6개월가량 오픈이 늦어졌다. 자동화 테스트와 회귀 테스트를 충족하는 단위 테스트의 효과는 분명한데 실전에 도입하기란 만만치 않다. 필자가 성공한 5번의 프로젝트 중에 3번은 우리 팀만으로 개발한 경우다. 우리 팀에서도 단위 테스트를 한 번도 작성하지 않았던 개발자가 있었지만, 짝 프로그래밍 형태로 일주일만 가르쳐주면 무리 없이 테스트를 작성했다. 문제는 소속이 다른 여러 조직에서 개발하는 경우다. 필자는 항상 단위 테스트를 주장하지만, 개발이 끝날 때까지 참여하는 경우만 힘을 쓸 수 있었다. 필자를 대신하여 테스트를 유도하고, 개발자가 막힐 때 방법을 알려줄 사람이 없기 때문이다. 성공한 두 차례중 한 번은 외국에서 훈련 받은 최고수준의 프로젝트 관리 진이 포진한 경우였다. 그렇지 않은 한 차례는 필자가 속한 팀이 프로젝트 관리 조직을 장기간 계몽(?)했다. 사실 투쟁의 역사이기도 했다. 그리고 실무적으로도 1,000회 정도의 코드 인스펙션을 수행했다. 우리 팀 개발자는 '차라리 내가 짜는 편이 빠르겠다'는 불평을 수없이 내뱉었다. 이런 사회적 배경을 고려하면 테스트는 아무리 강조해도 지나치지 않다.
토론 모임에서는 '스프링을 통해 처음으로 자동화 테스트를 경험했다'는 사람이 많았다. 소수지만 모인 사람만 놓고 보면 압도적인 비중이었다. 한편으로는 스프링이 현실에서 테스트를 적합하게 해낸 사례기도 하다.
작은 단위의 테스트
직접 관계는 없지만, JSP 모델1 구조에서 화면 단위로 테스트하던 모델2 MVC 구현일텐데. 그 부분이 아니라 아이들이 학교와 와야 의미가 있겠죠.
토스 151쪽 내용은 단위 테스트 경험자라면 누구나 고민해봤거나 고민했어야 할 내용을 잘 짚어낸 좋은 예다. 단위 테스트의 고립(isolated) 테스트 특성을 잘 설명한 내용이다. 필자는 2006년 단위 테스트의 "단위"라는 글을 쓸 즈음에 단위 테스트 개념을 이해하려고 여러 가지 시도를 한 바 있다. Mock을 이용한 테스트가 단위 테스트를 이해하는 데 큰 도움이 되었다. 국내 블로그 등에서 "단위"에 대한 고민을 찾을 수 없다는 점은 얼마나 단위 테스트를 안 하는지를 반증한다. 예전에 단위 테스트에 대해 고민했던 흔적을 남겨둔다.
- 단위 테스트의 "단위"
- easymock을 이용한 Spring MVC Form 컨트롤러 요청 처리 테스트 1
- easymock을 이용한 Spring MVC Form 컨트롤러 요청 처리 테스트의 개선
- 단위 테스트의 경계: 어디까지가 단위 테스트인가?
- 개발자들이 테스트 작성시 어려워하는 점
- JUnit 기본 사용법
- 단위 테스트에 관한 흥미로운 글
- 단위/통합 테스트의 경계 선정을 돕는 그림들...
- EasyMock2을 활용한 협업 테스트 1
- cohesion, TDD 그리고 SRP(Single Responsibility Principle)
저자는 작은 단위 테스트의 효과를 문제가 발생했을 때 찾을 수 있다고 설명한다. 프로젝트 전체를 놓고 생각하니 V 모델이 입자 크기와 겹쳐져서 떠올랐다. 프로젝트 초반에는 시스템을 규정하는 입자가 크고, V 모델 하부 구현단계에 들어서면 입자가 가장 작아졌다가 점차 테스트 입자가 커지는 그림이 머릿속에 그려졌다.
출처: 위키피디아
검증과 확인을 제공하는 V 모델의 효과를 극대화하는 방법은 무엇이 있을까? 수학적으로 증명할 수는 없지만, 빠른 피드백을 받고자 한다면 가장 작은 V 인스턴스(?)를 만드는 방법이다. 다시 말해서 작은 단위 테스트를 활용하는 방법이다. 필자가 켄트벡의 TDDBE를 읽고 가장 인상 깊었던 부분이 바로 작은 테스트를 통해 찾아가는 리듬인데, 앞서 사용한 표현에 따라 가장 적절한 V 인스턴스 크기라고 말할 수 있다. 린 소프트웨어 개발의 적용에서도 작은 테스트를 지지하는 경구를 제공한다.
하지만, 적절한 V 인스턴스 크기를 찾는 일은 매우 높은 밀도로 상당 기간 경험을 쌓았을 때 주어지는 선물이다. 볼링 게임으로 처음 TDD를 배우던 때를 회상하면 밥 삼촌이 카타에서 보여준 놀라운 칼질은 내공 차이를 명확히 보여준다.
저자는 단위 테스트에 대해서 개발자 테스트라는 다른 표현을 제시한다. 책에서 다루는 단위 테스트 작성자가 개발자임을 분명히 한다. 단위 테스트 수행자가 누구냐에 따라 테스트 목적이 달라지고, 프로젝트 갈등 구도도 달라진다. 하지만, 설사 제삼자 테스트 형태로 단위 테스트를 수행하는 이상적인 조직이 있다고 해도 개발자 스스로 단위 테스트를 해야 한다. 그렇지 않은 코드는 부실 코드(anemic code)라 불러 마땅하다.
자동수행 테스트 코드
저자는 수치까지 예를 들며 자동화 테스트의 중요성을 설명한다. 사실 자동화 테스트 작성의 생소함 탓에 놓치기 쉬운 그러나 실로 엄청난 자동화 테스트의 위력이다. 프로젝트 전체를 놓고 보면 잘 만든 자동화 테스트가 모여 만들어낸 회귀 테스트 자산의 힘은 말로 설명하기 어렵다. 앞에 언급한 필자의 경험을 놓고 보면 프로젝트 성공의 핵심 열쇠 중 하나다.
작은 시간이라도 아끼라는 자동수행 테스트 저번에 깔린 교훈은 필자가 학교에서 C++을 배울 때 가장 먼저 마음에 새긴 원칙인 Principle of least privilege을 떠오르게 한다. 이른바 POLA 원칙은 변수의 가시성(scope) 맥락에서 배웠지만, 자원 사용에 대한 정책이나 보안에도 응용된다. 그런데 지금 생각해보니 프로젝트에 주어진 시간 자원에 적용해도 의미가 살아난다.
지속적인 개선과 점진적인 개발을 위한 테스트
다시 한번 V 모델을 떠올린다. 작은 단위 테스트가 쌓여 회귀 테스트 기반을 이루어 시스템 전반을 검증할 수 있는 토대를 만드는 양상은 실용적인 V 모델 적용의 전형적인 예다. 아쉽게도 많은 프로젝트에서는 V 모델의 맥락을 충분히 이해하지 못하고 높은 성숙도를 요하는 이론을 그대로 적용하려고 옥신각신하는 모습을 볼 수 있다. 진정 V 모델을 실현하고자 한다면 긴 호흡으로 계획하여 단장은 필수적인 단위 테스트부터 팀/조직에 보급하고 성숙도를 높아지면 더 큰 그림을 그리는 방법이 어떨까?
UserDaoTest의 문제점
옥에 티 수준이지만, 두 번째 문제점 제목은 실행 작업의 번거로움보다는 체계적인 테스트 실행의 어려움이 나을 듯하다. UserDaoTest를 수정한다고 실행 작업을 벗어나는 것은 아니다. 155쪽 내용에도 체계적인 테스트 실행과 결과 확인에 대해 이야기하고 있다.
글
테스트 주도 개발 TDD 실천법과 도구
![]() |
테스트 주도 개발 - ![]() 채수원 지음/한빛미디어 |
채수원님으로부터 책을 증정받은 지가 한참인데 바쁘다고 잊고 살다가 일민형이 백년만에 쓴 서평을 보고 몇 자 덧붙인다. 일민형 서평에 완전 공감하는지라 "me, too." 라고 달아도 충분하지만 짧게 추가.
한마디로 정말 좋은 책이다. 이미 TDD에 익숙한 사람이라도 한번은 훑어볼만한 책이고, TDD에 익숙치 않다면 MUST HAVE 아이템이다. 이 책은 TDD나 자동화 테스트 실천에 필요한 꼭지를 두루 다루고 있다. 일민형이 책 분량이 적다고 아쉬워했는데 난 아이폰이나 플래쉬 책만큼 많이 팔리지는 않는다는 점이 아쉽다. 사실 그래서 더욱 값진 책이다. 열악한 시장성(?)에도 불구하고 집필한 저자의 노력과 의지의 결과물이니까.
책 Q&A를 위한 그룹스가 있다. '애프터'를 만들어가는 독자를 기대하며 찾은 김에 가입을 했다. 책 공식 사이트에 책에 실어준 인터뷰가 있길래 링크한다.
글
복식부기와 TDD (바뀌는 글)
TDD를 복식 부기1에 비유하다니 절묘한 은유다. 동시에 외과 의사의 어떤 절차(sterile procedure for surgeons)2를 함께 예로 들면서, 전문가들이 이러한 규율이 주는 이점을 알고 활용하듯 TDD 역시 전문적인 규율(TDD is a professional discipline)라 설명한다.
소프트웨어 산업은 아직 초창기인 듯하다. 최소한의 검증 장치가 없는 코드를 인정해주는 문화가 증거다. 현장에서 아직 TDD를 적용해보지 못했지만, JUnit 기반 자동화 테스트 구축으로 변화에 대응하기 위한 회귀 테스트를 5년에 걸쳐 4개 프로젝트에 적용하고 있다. 변화를 강제 당하는 개발자가 테스트 작성을 반대하는 목소리는 이해할 수 있다. 그러나 관리자마저 정확한 내용도 모르고 그저 개발자에게 부담을 준다고 테스트 작성을 반대하는 일을 최근에도 경험할 수 있다. 조금 과장해서 이야기하면, 개발자가 자기 마음대로 코드를 짜도 화면으로 결함을 확인할 수 없으면 문제없다는 이야기다.(세상에~ 내가 일하는 곳이 이렇게 엉성하게 일할 수 있는 곳이었다니... 새삼스럽다.) Uncle Bob이 TDD가 개발을 더디게 한다는 사람을 석기 시대(the stone age) 사람이라 했는데, 소프트웨어 산업은 이제 막 석기 시대를 지난 듯하다. :)
글
TDD가 좋은 줄 알면서도 안하는 이유
대답은 명쾌하다. 의지가 부족할 뿐.
나는 의지가 투철한 사람이 아니다. 초중고는 물론이고 대학교는 물론 회사에서까지 끊임없이 지각을 하는 행태를 고쳐야지 마음먹어도 이렇게도 오랬동안 고치지 못하는 것이 이를 입증한다. @@
그런데도 왜 자꾸 포스팅을 하고 난리냐? 부족한 의지를 채우는 나름의 방식이다. 좋은 글을 통해 만날 수 있는 동시대인들의 동경하는 모습은 부족한 의지를 극복하는데 도움을 준다. 그리고, 그때 생겨나는 열정이 식어버리기 전에 흔적을 남기는 수법이 아닐까 싶다. 1
한 가지 확실한 것은 적어도 나에게 있어 TDD는 일단 아침에 일찍 일어나는 것보다 쉽다. 그래서, 그리 멀지 않아서 'TDD를 하자'고 말할 필요도 없는 상태가 오지 않을까 기대하고 있다. 그때 되면 또 딴거 하자고 (스스로를 그리고 나와 같은 생각을 하는 이들을) 선동(?)하고 나서겠지만...
- 왜 내가 이러는지 즉흥적으로 분석해 본 결과 [본문으로]
글
이클립스를 사용하는 TDD 기초 동영상 튜토리얼
글
UML 모델링과 TDD
내가 과거에 답답했던 것은 가이드에 의해 만들어내는 UML설계모델의 수많은 다이어그램들이 별로 유기적으로 코드로 이어지지 못하고 있다는 점이다. 내가 만들어야 했던 것은 요구사항부터 구현으로 이어지는 전체 표준 프로세스였다. 결국 유스케이스에서 테스트케이스를 만들게 하고, 이 때 설계한 내용을 구현에 반영하게 했다. 그때, 해결하지 못했던 것은 요구사항에 대응하는 Acceptance test수준에서 컴포넌트 테스트, 그 하위의 클래스 테스트까지 체계화하고, 유기적으로 연계할 방법이었다.
그로부터 2~3년이 흘렀다. 토비형 질문을 통해 내 머리속에서 추출한 TDD의 정의는 내가 TDD를 어떻게 써먹었는가를 여실히 보여준다. 당시 내 입장에선, 지금 이곳에서 유용한 내용을 실천하는 것이지, 원론적인 TDD는 아니었으니까.(지금은 2~3년 전과 같은 상황이 아니고, 이젠 TDD를 Kent Beck이 정의한대로 이해할 필요가 있기 때문에 다시 TDDBE책을 열어봤다. Preface를 꼭 보시기 바란다. ^^)
![]() | 테스트 주도 개발 - ![]() 켄트 벡 지음, 김창준 외 옮김/인사이트 |
2~3년이 지났지만, 밑줄친 저 부분에 대한 대답은 여전히 요원하다. 나비효과로 토비형과의 대화에서 'TDD에 어떻게 관심을 가졌는지'를 떠올렸지만, 밑줄친 과제(?)는 쉽게 잊지 못하는 것 같다. 내가 DDD에 관심을 갖았던 것도 거의 같은 이유다. UML을 소모적인 다이어그래밍에 쓰지 않고, 어떻게 설계모델을 구현에 유기적으로 반영할 것인가?
마침 다시 한번 모델링을 돌아볼 기회가 왔다. 어제부터 일을 잠시 쉬면 머리 속에서 모델링에 대한 아이디어가 모락모락 피어난다.
글
당신이 생각하는 TDD
안영회 님의 말:요구사항 구현하는 과정에서 테스트를 출발점이자 기준으로 삼는 개발방식
Toby Lee 님의 말: 결국 요구사항을 검증할 수 있게 코드로 먼저 만들어 놓고 개발하는 것이 tdd라는 얘기네.
안영회 님의 말: 검증과 명세 두 가지 의미가 있겠죠.
사실 이러한 정의는 TDD 핵심을 언급했다기 보다는 Usecase Driven Developement에서 유기적인 설계가 어렵다는데서 대안을 찾던... 내 이력을 떠오르게 했다. :)
토비형의 정의를 묻자 다음과 같이 대답했다.
Toby Lee 님의 말: 그리고 화장실에서 TDDBE를 다시 읽었다고 하면서.. 켄트백의 얘기를 정리해준다. 결정 사이에 피드백을 제공하는 자동화된 코드를 가진다 이거지. tdd의 핵심은 그 결정과 결정 사이에 피드백을 이용하는 기법이지. 테스트를 어떻게 작성할 것인가는 한참.. 밑에서 등장하는 전략일 뿐이지.
조금 발전한 간단한 설계적 결정을 하고.. 그 피드백을 빨리 받고 그리고 용기를 얻은 후 조금 더 나아가고, 필요하면 중복을 제거해서.. 클린한 코드로 만들고, 세분화된 결정단계의 모든 피드백을 자동화된 테스트로 가지고 있으니 더 용기를 가지고 유기적인 설계가 지속되게 할 수 있다는 거지. tdd는 design skill이라는 얘기켄트 백이 정의한 tdd가 바로 이거야.
다들.. test 코드 만드는데만 정신이 팔리지. 그래서 두가지로 가지. 하나는.. unit test라는데 집착해서 pure unit test주의자가 되서 거기에 목매거나, 아니면.. 요구사항을 테스트로.. acceptance test를 전통적인 qa기법의 대체로 하는 자동화된 테스트 기반의 QA 기법으로 빠지거나. 다들 방향을 잘못잡고 있는거지. tdd를 통해서 어떻게 설계가 발전하는가 이지. test 코드 만드는 기법이 아니거든. 그래서 그걸 봐도 막상 test를 만들려면 잘 안되는게 그 책은 test 만드는 법을 알려주는 게 아니니깐. tddbe의 서문을 잘 읽어바. 거기 다 나오는 얘기야.
Write new code only if an automated test has failed.
TDD is an awareness of the gap between decision and feedback during programming, and techniques to control that gap
젠장 나는 서문을 빼고 읽는 버릇이 있다. 집에 가서 다시 읽어봐야겠다.
(집에 와서)
이런 TDDBE에서 내가 보고싶어했던 내용이 Preface에 다 있었다. 쩝... Preface/서문/머릿말은 무조건 스킵하는 습관때문에 앙꼬를 다 빼먹었다. 토비형이 이 책을 다시 뒤척이는걸 보니 애자일 스프링 책을 쓰고 있긴 한가 보다.
글
TDD isn’t about testing
TDD가 테스트에 대한 것이 아니고 그럼 도대체 뭐냐?
TDD is all about speed.
테스트 만들면 더 느려지는데 무슨 소리냐? 라고 한다면 TDD를 전혀 모르는 것이다.
TDD를 채용하면
- 조기에 결함을 발견할 수 있어 누적되는 디버깅 시간이 줄어들고
- 변경이 필요할 때, 영향도 파악이 가능해 빠른 프로그램 수정이 가능하다(Regression Test를 떠올려보라)
물론, TDD를 배우는 시간이 필요하지만, 세상의 모든 기법, 기술, 프로세스에는 학습 비용이 든다.
글
마이크로테스트(microtests)
TDD를 해보지 않은 사람들과 TDD의 단위 테스트를 이야기 할 때 나타나는 혼선을 막기 위해 단위 테스트 대신 '마이크로테스트'라고 다른 말을 쓴다는 것이다. :)
또한, 고품질은 TDD의 부수입(side effect)일 뿐이라며 본래 목적은 생산성에 있음을 효과적인 어법으로 표현했다.
듣고 보니, 종래 개발 방식에서 QA가 관심을 두는 단위 테스트와 TDD의 microtests가 분명 구분할 만 하다. 앞으로 나도 '마이크로테스트'라는 표현을 써야겠다.
글
꼬리에 꼬리를 무는 '쾌적한 프로젝트 수행 환경' 만들기
CI 관련 글:
헛슨(Hudson)이 만들어준 상쾌한 프로젝트 환경
공동작업시 묵비권 행사를 자제해주세요
중복의 제거
사람을 위한 자동화 한글 번역 시리즈 (IBM DW)
진정한 의미의 리비전?
개발환경 자동화 환경에 대한 추천 조합 (조대협님 블로그)
Hudson을 이용한 빌드 배포 테스트 자동화 (조대협님 블로그)
Atlassian JIRA를 이용한 프로젝트 관리 (기초편) (조대협님 블로그)
TDD 관련 글:
실전 테스트 코드 리팩토링(수정)
Test Driven Development 전도 일지 1
Test Driven Development 전도 일지 2
EasyMock(old)을 활용한 협업 테스트
문서화 관련 글:
jAutodoc으로 깔끔한 javadoc 만들기
글
Test Driven Development 전도 일지 2
- Run the program
- Analyze coverage data
테스트와 테스트 대상 코드 사이에서 마우스로 왔다 갔다 하는 것이 번거로와서 다시 MoreUnit을 설치했다. 이젠 이클립스 JDT에서 JUnit TestCase 자동생성을 지원하니 MoreUnit의 효용이 줄어들엇지만, 내가 필요한 것은 Ctrl+J 단축키 하나 뿐이다. ^^
스프링 스타일로 테스트 케이스 접미사를 Tests로 변경했더니 MoreUnit 디폴트와 충돌했다. 그래서 변경해주니 잘 먹는다. 편하다. ^^
글
Test Driven Development 전도 일지 1
// normal case
assertEquals("loggerName", ApplicationLogger.getLogger("loggerName").getName());
// unusual case
try {
ApplicationLogger.getLogger(null);
fail("filename으로 null이 올 수 없습니다.");
}
catch (Exception e) {
assertEquals(IllegalArgumentException.class, e.getClass());
}
try {
ApplicationLogger.getLogger("");
fail("filename으로 공백문자가 올 수 없습니다.");
}
catch (Exception e) {
assertEquals(IllegalArgumentException.class, e.getClass());
}
}
동료개발자가 짠 테스트가 없는 코드에 단위 테스트를 같이 작성해본다. 위에 보이는 정도면 대부분 흥미를 보일 수 있는 수준이다. 그러나, 난항 끝에 긴 메소드의 작성 의도를 물어가며 만든 아래 테스트를 보자.
// 시작 환경 조사
Logger applicationLogger = ApplicationLogger.getLogger(ApplicationLogger.APPLICATION_LOGGER);
assertNotNull("applicationLogger 로거가 없습니다.", applicationLogger);
assertEquals("로거 이름이 applicationLogger가 아닙니다.", ApplicationLogger.APPLICATION_LOGGER, applicationLogger.getName());
Logger defaultLogger = ApplicationLogger.getLogger(ApplicationLogger.DEFAULT_LOGGER);
assertNotNull("defaultLogger 로거가 없습니다.", defaultLogger);
assertEquals("로거 이름이 defaultLogger 아닙니다.", ApplicationLogger.DEFAULT_LOGGER, defaultLogger.getName());
assertNull("default Level이 null이 아닙니다.", applicationLogger.getLevel());
// log4j.xml 설정
FileAppender fileAppender = new FileAppender();
fileAppender.setName("FILE");
defaultLogger.addAppender(fileAppender);
ConsoleAppender consoleAppender = new ConsoleAppender();
consoleAppender.setName("CONSOLE");
defaultLogger.addAppender(consoleAppender);
JDBCAppender jdbcAppender = new JDBCAppender();
jdbcAppender.setName("DB");
defaultLogger.addAppender(jdbcAppender);
applicationLogger.setLevel(Level.DEBUG);
AsyncAppender asyncAppender = new AsyncAppender();
asyncAppender.setName("ASYNC");
applicationLogger.addAppender(asyncAppender);
// log message 설정
LogMessage logMessage = new LogMessage();
List noticeTypeList = new ArrayList();
noticeTypeList.add("FILE");
noticeTypeList.add("CONSOLE");
logMessage.setNoticeTypeList(noticeTypeList);
// applicationLogger가 DEBUG모드일 때 debug() 수행
assertTrue("applicationLogger가 DEBUG 모드가 아닙니다.", applicationLogger.isDebugEnabled());
// Async debug
AsyncAppender asAppender = (AsyncAppender) applicationLogger.getAppender("ASYNC");
assertNotNull(asAppender);
ApplicationLogger.log(logMessage, new Throwable());
assertTrue("Location Info가 false입니다.", asAppender.getLocationInfo());
assertNotNull("FILE 어펜터가 부착되지 않았습니다.", asAppender.getAppender("FILE"));
assertNotNull("CONSOLE 어펜터가 부착되지 않았습니다.", asAppender.getAppender("CONSOLE"));
assertNull("DB 어펜터가 부착되어 있습니다.", asAppender.getAppender("DB"));
// Sync debug
applicationLogger.removeAllAppenders();
ApplicationLogger.log(logMessage, new Throwable());
assertNull("", applicationLogger.getAppender("ASYNC"));
assertNotNull("FILE 어펜터가 부착되지 않았습니다.", asAppender.getAppender("FILE"));
assertNotNull("CONSOLE 어펜터가 부착되지 않았습니다.", asAppender.getAppender("CONSOLE"));
assertNull("DB 어펜터가 부착되어 있습니다.", asAppender.getAppender("DB"));
}
로거 설정 사항을 읽어서 동적으로 특정 로거 인스턴스에 어펜더를 붙이는 코드를 테스트한 것이다. 옆에서 작성하는 모습과 설명을 듣던 동료는 '굳이 이렇게 해야 하나?'라는 반응이었었다. 엄격한 설계를 하지 않은 경우에 테스트마저 작성하지 않는다면, 암산(?)으로 설계를 할 것인가? 내가 동료의 코드를 파악하는 동안에 설명을 요구했지만, 그는 정확한 작동 원리는 설명하지 못했다. 작성의도 정도만 설명할 뿐이었다. 테스트를 작성하고 나서 메커니즘이 잘못된 것을 알게 되었다.
글
삼각측량(Triangulation)을 보완하는 UnsupportedOperationException 발생 기법
Dollar five = new Dollar(5);
five.times(2);
assertEquals(10, five.amount);
}
JUnit 3.x 코드는 JUnit4 용으로 바꿨을 뿐이다. 이를 만족시키기 위한 최소한의 작업을 했을 때 생성된 코드(production code)는 다음과 같다.
public int amount = 10;
public Dollar(int amount) {}
public void times(int i) {}
}
코드를 향상 시키기 위해서는 먼저 테스트를 더 엄밀하게 해볼 수 있다.
Dollar five = new Dollar(5);
five.times(2);
assertEquals(10, five.amount);
Dollar six = new Dollar(6);
six.times(2);
assertEquals(12, six.amount);
}
(중복에 대한 문제를 배제하고 보면) 위와 같이 새로운 사례를 추가하면 코드의 품질이 향상된다. 왜냐하면, 속성에 상수값을 설정하여 문제를 해결하는 방식은 더 이상 통하지 않게 되니까. 이러한 개선 방법을 삼각측량(triangulation)이라고 한다.
이 정도로 간단한 것을 면밀하게 살펴보는 것에 의아한 사람도 있을 것이다. 이러한 과정은 자기가 짠 코드에 대해 완벽하게 통제하기 위한 의도로 훈련하고 있는 것이다. 그간의 나는 내가 짠 코드가 미치는 영향에 대해서 충분히 숙지하지 못한채 빠르게 타이핑을 하는데만 열중했다. 단계를 모두 밟아 가는 과정을 모두 경험한 상태에서 빠르게 일을 수행하는 것과 한번에 문제를 해결하려고 드는 것은 엄청난 차이가 있다.
관점에 대한 이야기는 이 정도로 마무리하고, TDD를 위한 이클립스 메소드 생성 템플릿에서 소개한 로드 존슨의 기법을 써보자.
public int amount = 10;
public Dollar(int amount) {
throw new UnsupportedOperationException();
}
public void times(int i) {
throw new UnsupportedOperationException();
}
예외를 발생하는 구문을 유심히 보자.
Dollar five = new Dollar(5);
five.times(2);
assertEquals(10, five.amount);
}
굵게 표시한 부분에서 이들 메소드를 호출한다. 당연히 예외가 발생한다. 기능을 제공하는 측 즉, Dollar 객체에선 지원하는 않는 오버레이션이다. 그런데 이를 요청하니 문제가 생긴 것이다.
만일, 위의 두 구문이 제 구실을 하지 않음에도 테스트가 통화했다면
1) 테스트 코드가 잘못 되었거나
2) 이들 메소드가 불필요한 기능이거나
3) 이들 메소드의 구현이 잘못된 것으로 볼 수 있다.
여기서 빠르게 녹색불을 보기 위해서 Ctrl+D 단축키를 이용하여 throw 구문을 지워버릴 수 있다. 하지만, 엄밀하게 따지고들면 테스트 성공을 위해서 불필요한 기능이 호출되는 것을 방관하는 짓이 된다. 따라서, 불필요한 것이라면 테스트에서부터 제거한다. 그렇지 않으면, 구현이 잘못된 것이므로 구현을 수정하면 된다. 이는 삼각측량이 제공하는 도움과 같다. 결론적으로 로드 존슨의 기법을 사용하면 삼각측량의 필요성을 조금은 줄일 수 있고(그렇다고 해도, 삼각측량은 여전히 매우 유용하다.) 불필요한 기능을 방치해서 API를 오염시키는 일을 줄일 수 있다.
