올 상반기 발표 자료입니다. 이클립스 기반으로 되어 있습니다.
https://bit.ly/oktdd2022 

* 개발환경 세팅
* Getting Started TDD
* TDD Concept
* TDD Misunderstood
* Test 단계와 유형
* assert 종류
* 리팩터링
* Maven

tdd

앞서 오픈소스를 통해서 소스 저장소에 등록되는 디렉토리 구조를 살펴보았습니다. 그리고 프로젝트 소스를 일괄처리하는 build.xml 도 살짝 들여다 보았습니다. 이클립스 툴을 쓰면 다 되는 작업을 왜 Ant 빌드스크립트를 만들어야하는지 질문을 종종 들어봤습니다. 지속적인 통합 빌드 같이 주기적으로 반복되는 작업이나 단계가 복잡한 작업들은 빌드스크립트가 효과적입니다. 클릭클릭하는 일도 지겨울 때가 있거든요. 이클립스 작업의 자동화가 빌드 스크립트라고 생각하시면 될 것입니다.

지난 번 소스 가져오기(import) 이후로 소스의 빌드 경로를 잡아보려고 합니다. 로컬pc에서 컴파일되나 안되나 확인하는 것이죠. 소스가 패키지에 맞게 제자리에 있어야 할 것이고, 참조하는 jar파일의 경로도 함께 지정해줘야 합니다.

일단 이클립스 자바 프로젝트에서 소스 폴더를 추가합니다. 자바소스가 있는 폴더를 소스폴더라고 합니다. *.java 파일과 *.properties 파일들이 위치합니다. httpunit-1.7 아래 src 폴더가 바로 소스폴더입니다. 그리고 httpunit-1.7 아래 test 라는 곳도 소스 폴더로 추가합니다. src 아래 있는 클래스들을 테스트하는 테스트케이스들의 소스 폴더입니다.

자동 빌드가 일어나면서 에러가 무진장 일어납니다. jar 파일 연결이 안되서 그렇습니다.

왼쪽 패키지 익스플로러에서 프로젝트를 선택하고 Properties 창을 엽니다. 컨텍스트 메뉴에서 제일 아래 Properties 메뉴를 선택하면 됩니다. 단축키는 alt+Enter
Java Build Path 항목을 선택하고 Libraries 탭을 클릭합니다.

우측의 Add JARs... 버튼을 클릭해서 httpunit-1.7/jars 폴더의 *.jar 파일들을 선택합니다.

오호, 에러가 줄기는 했는데, 하나 남았네요. fnfe를 클릭해서 해당 소스를 열어보겠습니다. 흠, 겁을 상실했군요. 에~ 뭐 꼭 고친다는 뜻은 아닙니다. ^^;

소스를 살짝 보니 캐릭터셋 문제로 Roger Lindsj 이름 옆에 이상한 문자가 생긴 것 같네요. 그것 때문에 아랫줄 if라인이 주석 줄로 따라 올라온 듯 합니다.

if 앞에서 엔터로 줄바꿔주니 고쳐졌습니다. ^^; 잠시 우쭐~

Problems 탭에 에러는 싹 사라졌군요. Warnings는 살짝 봐주기로 하죠. ^^;

소스 폴더가 추가된 모습입니다.

작업공간은 에러 없는 코드로 관리되는 것이 개발자 심리에 좋다고 생각을 합니다. ^^;

과거의 이야기들입니다. Kent Beck, 익스트림 프로그래밍과 JUnit의 창시자. 지난 20년 동안을 회고해보면서 그의 사상에 영향을 준 것들과 특히 TDD(테스트 주도 개발), 디자인 패턴, 익스트림 프로그래밍(XP)의 역사에 대해 얘기합니다.

그의 얘기로는 좋은 아이디어는 그에 따른 충분한 시간을 필요로 합니다. 성숙하고 열매를 맺기위한 시간 말이죠.

http://www.okjsp.pe.kr/bbs?act=DOWN&maskname=1219467188527&fileName=3759 Kent Beck - Test Driven Development, Patterns and Extreme Programming.mp3

Relating anecdotes from the past, Kent Beck, the father of Extreme Programming and JUnit, reflects back on the impact his ideas have had in the last 20 years, especially with respect to the history of Test Driven Development (TDD), Design Patterns, and Extreme Programming (XP). According to him, good ideas take about that much time to mature and come to fruition.

from: http://feeds.conversationsnetwork.org/channel/itc

진도를 마쳤습니다. 시험봐야죠. 다른 말로 테스트.
생활 속에서 이러한 이유로 테스트라는 단어는 항상 실체의 뒤에 위치합니다.

TDD, Test Driven Development. 흔히 테스트 주도 개발이라고 얘기하는 것입니다.
학원 안 다니고 주행에 도전했다가 몇 번 씩 탈락한 뒤에 합격해서 받은 운전 면허가 TDD로 받은 운전면허증일까요. ㅋㅋ. 이 경우는 테스트가 학습을 유발했다고도 볼 수 있죠. 영어로 Heuristic 이라고 얘기하는 학습법이요.

이와는 반대로 건드리기 전에 테스트를 해야하는 경우도 있습니다.

사용자 삽입 이미지

image from: http://www.esfi.org/workplace/test-before-you-touch.html 

전기 회로를 손대기 전에 전류가 흐르는지 아닌지 확인을 하는 경우처럼 말이죠.

리팩토링의 관점에서는 테스트 코드의 존재가 이와 같다고 생각합니다. 레거시 코드를 고치기 전에 소스의 특성을 알아내는 것이죠. 그 다음 테스트 코드가 신뢰할 만큼 누적되면 좀 더 안전하게 코드를 수정할 수 있겠지요. 변경으로 인한 사이드 이펙트를 금방 인지할 수 있으니까요.
6월 14일 발표할 내용의 일부입니다.
JUnit에 관한 책들은 많이 있습니다.

사용자 삽입 이미지


Working Effectively with Legacy Code 책의 p.147에 있는 내용입니다.
메소드 또는 함수는 두 가지 타입의 역할을 하게 됩니다. 동작의 명령(Command)이 첫번째이고 결과값이 필요한 질의(Query)가 다른 역할입니다.

메소드는 이 두 가지 타입의 역할이 구분되어 있어야 되고, 메소드 내에서 두 가지가 혼용이 된다면 그 메소드의 재활용은 예상치 못한 결과를 초래하게 됩니다.

특히나 메소드 간에 통용되는 필드변수의 값에 변동이 다른 메소드에 영향을 주게 된다면 그 메소드의 독립성은 저하되는 것이고, 이에 따라 코드의 재활용과 테스트 가능성도 낮아지게 됩니다.

내용이 많이 어렵습니다만, 부작용(Side Effect) 없는 코드를 만들기 위해서 중요한 기준 원칙이라 생각합니다.

좋은 예가 생각나면 공유하도록 하겠습니다.
Working Effectively With Legacy Code 책을 다시 보기 시작했습니다.
이제 1/3 정도 읽었는데, 아주 벅찬 책입니다.

124페이지를 보면 다음과 같은 표현이 있습니다.

If we want to avoid talking to the database, we can subclass PermitRepository like this:
public class TestingPermitRepository extends PermitRepository {
...

심각하게 고민해봐야겠습니다.
자바를 좀 한다고 하는 사람들은 TDD를 들어봤을 것입니다. Test Driven Development, 즉 테스트로 주도하는 개발 방법입니다. 2003년인가 http://xper.org 에서 김창준님이 주최하는 XP 그 세번째 이야기 라고 기억되는 연극형 세미나에서 감명을 받은 이후로 계속해서 시도하고 있는 개발방법입니다. 6년째이지만 아직 잘 못하는 방법이기도 합니다.

방법이 기괴한데 먼저 테스트 코드를 짭니다. 테스트의 대상이 되는 코드를 짜기 전에 말이죠. 그래서 이런 형태가 됩니다.
assertEquals( 2, add(1, 1) );


1과 1을 인수로 하는 add() 메소드의 결과는 2와 같아야 된다는 뜻인데, 아직 add() 메소드가 만들어지지 않았지만 이렇게 먼저 선언, 또는 정의합니다. add() 메소드의 결과 동작을 먼저 만드는 것이죠. 에러 납니다. 컴파일도 안됩니다. 아직 없으니까요.

그 다음 동작은 add() 라는 메소드를 만드는 것입니다. 그럼 저 테스트 코드로 add() 메소드의 결과를 확인해 볼 수 있겠죠. 이렇게 개발을 하니 모든 메소드는 자동으로 실행할 수 있는 테스트 코드들을 갖고 있게 됩니다. 이게 모이게 되면 전체 애플리케이션을 테스트할 수 있는 집합체가 구성되는 것이죠.

그런데 이런 방식에는 테스트라는 단어의 레거시가 너무 강했습니다. 일반적인 상식에서는 테스트라 함은 후에 위치한다는 것이 자연스럽기 때문입니다.

사용자 삽입 이미지

그래서 나온 것이 Behavior Driven Development(행위 주도 개발 BDD) 입니다. 자바에서는 JBehave 라는 xUnit과 비슷한 개념으로 나온 것이 있습니다. 이에 관한 developerWorks의 기사를 추천합니다. 사실 저도 Kenny군에게서 일,이 년 전부터 계속 듣기는 했는데, 솔직히 말장난한다는 느낌이 들어서 안 했거든요.

TDD를 하지 않는 이유 중 가장 흔한 것이 "테스팅할 시간이 없어요"와 "테스트하기에 너무 복잡하고 힘든 코드"라는 것이다. 테스트 우선 프로그래밍의 또 다른 장애물은 테스트 우선이라는 개념 그 자체다. 우리 대부분은 테스팅을 감촉적(tactile) 행위, 추상적이기보다는 구체적인 행위로 본다. 우리는 경험적으로 존재하지 않는 어떤 것을 테스트하는 것이 가능하지 않다고 생각한다. 이 개념적인 프레임이 머릿속에 이미 자리잡은 일부 개발자들에게는 '테스트 우선(testing first)'이라는 아이디어가 바보 같은 일로 여겨진다.

그러나 테스트를 작성하고 어떻게 테스트할 것인가 하는 관점이 아닌, 행위(behavior)에 대해 생각해 본다면 어떨까? 여기서 말하는 행위란 애플리케이션이 어떻게 동작'해야' 하는가, 즉 본질적으로 그것의 명세를 말하는 것이다.


국내에도 IDD라 하여 Intention Driven Development를 얘기합니다만 가장 빨리 만들어 내는 방법이라 하여 얘기한다면 BDD와 다르다고 할 수 밖에 없을 듯 합니다. TDD나 BDD의 장점은 Test Harness를 마련해서 기능 추가나 변경에 따른 영향도 감지가 부수적으로 오게 되는 것인데, 그것을 얘기하지 않는다면 유지보수하는 사람들의 고통이 필요하기 때문이죠.

Working Effectively With Legacy Code 내용 중에 테스트 가능한 프로그래밍 기법이 있는데, 재밌는 예제였습니다.
올바른 값을 가져오는지 테스트하기 위해서 탐침(probe)과도 같은 클래스를 만들어냅니다. 보통 메소드 안에서 흘러가는 흐름을 테스트 때문에 감지를 위한 것이지요.

package net.okjsp;
public class Car {
 public void 급정거(int level) {
  // ...
  System.out.println("버럭! "+level+"번");
  // ...
 }
}

저 부분을 테스트하기 위해서 코드를 분리합니다.

package net.okjsp;
public class Car {
 public void 급정거(int level) {
  // ...
  CarSpeaker speaker = new CarSpeaker();
  speaker.yell("버럭! "+level+"번");

  // ...
 }
}

CarSpeaker 는 내용을 감지하기 위한 미끼역할을 하게 됩니다.
package net.okjsp;
public class CarSpeaker {
 public void yell(String string) {
  System.out.println(string);
 }
}

테스트를 위해서 인터페이스를 뽑아냅니다.
package net.okjsp;
public class CarSpeaker implements Speaker {
 public void yell(String string) {
  System.out.println(string);
 }
}

package net.okjsp;
public interface Speaker {
 public void yell(String string);
}


그리고 speaker를 외부에서 지정할 수 있도록 필드로 만듭니다. 지정은 생성자에서 담당합니다.
package net.okjsp;
public class Car {
 private Speaker speaker;
 
 public Car(Speaker speaker) {
  this.speaker = speaker;
 }
 
 public void 급정거(int level) {
  // ...
  speaker.yell("버럭! "+level+"번");
  // ...
 }
}

이렇게 코드의 구성이 끝나면 감지를 전담하는 FakeSpeaker를 만들 수 있습니다.
package net.okjsp;
public class FakeSpeaker implements Speaker {
 String string;
 public void yell(String string) {
  this.string = string;
 }

 
 public String getString() {
  return string;
 }
}

테스트케이스는 다음과 같이 됩니다.
package net.okjsp;
import junit.framework.TestCase;
public class CarTest extends TestCase {
 public void test급정거() {
  FakeSpeaker speaker = new FakeSpeaker();
  Car car = new Car(speaker);
  car.급정거(1);
  assertEquals("버럭! 1번", speaker.getString());

 }
}


처음 코드 보다 설계는 복잡해졌지만, 테스트를 자동화하는 효과를 얻었습니다.
선택의 시간입니다. 그냥 System.out.println(">>>>"+level); 로 원본 클래스를 바꿔서 테스트할 것입니까, 아니면 테스트 케이스를 위해서 소스 코드의 설계를 바꿔서 하시겠습니까.

참고자료
Working Effectively With Legacy Code, Michael C. Features, Prentice Hall, P23-28
까오기님이 정리한 글
http://kkaok.pe.kr/servlet/KBoard?cmd=view&tableName=kmytip&flag=0&search=&findword=&gotoPage=1&seq=124
레거시 코드, 또는 레거시 시스템이라고 합니다.
요즘 보기시작한 책이 WORKING EFFECTIVELY WITH LEGACY CODE 입니다.
http://kangcom.com/common/bookinfo/bookinfo.asp?sku=200410110002
 
자바지기 박재성님의 블로그를 보고 부쩍 읽고 싶은 마음이 생겨서 구입을 했습니다. 원서라 조금씩 책장을 넘기고 있는데, 표현이 예술입니다. ^^; 구구절절이 제 마음을 후벼파는군요.

  • 누군가 나에게 맡겨놓은 코드
  • 자기가 직접 짜지 않은 누군가가 만들어 놓은 코드
  • 많은 의문의 그림자와 중압감을 내포한 코드
  • 얽히고 설킨, 아둔하게 짜놓은, 갈아엎고 싶지만 그럴 자신이 안 생기는 코드
  • 기능이라도 하나 추가할라치면 몇일 밤을 새게 만드는 코드
  • 도저히 손댈 수 없어서 팀에서 누구라도 나서지 않는 코드
  • 손대면 손댈수록 나락으로 빠져버리는 코드
  • 차라리 날 죽여줘 라고 절규하게 만드는 코드
  • 개선한다는 생각 자체에 몸서리 치게 만드는 코드

레거시 코드를 잘 묘사하지 않았나요.
책을 읽어가면서 요약해서 글을 올려보고 싶군요.
사용자 삽입 이미지

늪 괴물과 레거시 코드의 공통점


image from: http://wow.allakhazam.com/db/mob.html?wmob=766

+ Recent posts