Extremely Agile/General2009.01.21 23:51
보통 프로그래머들에게는 한가지씩의 무용담이 있습니다. 그 중 가장 흔한 것이 바로 "내가 만났던 가장 개같은 버그" 류의 무용담입니다.

이런 이야기는 왜 하는 것일까요? 뭐 다들 잘 아시겠습니다만, (1) 이런 무용담이 많으면 어디가서 분위기를 띄우기 좋고 (2) 자기가 경력이 꽤 된다는 걸 간단하게 입증해 보여줄 수 있으며 (3) 다른 프로그래머에게 유용한 정보를 전해줄 수 있다는 장점이 있습니다. 물론 이런 이야기는 '군대에서 축구한 이야기'랑 비슷하기 때문에, 프로그래머들이 둘 이상만 모이면 저절로 튀어나오게 되는 경향도 있긴 합니다. ^^;

프로그래밍 심리학이라는 책에도 나옵니다만, 천공 카드를 통해 프로그래밍을 했던 중세시대에도(ㅋㅋ) 소위 '개발자 커뮤니티'라는 것이 있었습니다. 천공 카드를 컴퓨터에 입력하려면 순번을 기다려야 하니까, 기다리면서 노가리들을 깠던 것이죠. 인터넷이 없었으니 당연했겠죠? 그 시대의 개발자들은 아마 지금 개발자들에 비해 백배는 수다스러웠을 겁니다.

각설하고... 그런데 버그에 관한 그런 무용담들을 들어보면, 보통 '버그를 잡았다'에서 스토리가 끝나버려요. 그 뒤의 이야기들은 좀체로 하질 않습니다. 오늘도 커피를 마시러 휴게실로 가다가 같이 일하는 분들하고 잠시 이야기를 할 기회가 있었는데요. 개발자를 한달간 애먹인 버그에 대해서 들려주시더군요. 관련된 모든 사람들의 영혼을 한달동안이나 잠식했던, 정말 가공할만한 버그였습니다. (가장 큰 문제는 그 버그가 그분들이 개발한 시스템에서 나온게 아니라는 점이었죠.) 듣는 제가 다 소름돋을 정도였으니....

보통은 거기까지 듣고 마는데, 오늘은 제가 이런 질문을 한번 해봤습니다.

"그런 버그를 한 번 겪고 나면 본인이 어떻게 달라졌다고 느끼나요?"

질문을 좀 뜨악하게 들으시는 것 같아서 좀 다르게 물어봤습니다.

"그런 버그가 (프로그래머로서의) 자신의 인생을 바꿔 놓았다고 느낄 때가 있나요?"

생뚱맞게 들리실수도 있겠습니다만, 저는 이런 질문을 한번쯤은 던져 보아야 하지 않나 합니다. 제가 이 직장에 처음 들어왔을때 처음 맞닥뜨린 가장 심각했던 'UNDETERMINISTIC' 버그[각주:1]는 시그널 핸들링 관한 것이었습니다. 시스템이 죽긴 죽는데, 진짜 어쩌다 한번 죽는 겁니다. 그리고 그 상황 사이의 일관성을 찾기가 정말 힘들었어요.

이 문제의 해결책을 찾기 위해 정말 많은 삽질을 했습니다. (지금 생각하면 삽질이 아니라 당연하게 해야 하는 일들이지만요.) 첫 번째로 했던 일은 메모리 누수가 시스템의 crash로 이어지는 시점을 정확하게 동기화시키기 위해 MALLOC_CHECK_ 환경 변수의 값을 설정하는 것이었습니다. (Linux라면 man malloc하시면 관련 자료가 나올 겁니다.) 처음에는 시스템이 죽는 이유가 메모리 누수 때문이 아니었을까 하고 추측했거든요. (그당시에는 valgrind에 대해서 몰랐습니다.)

물론 그래도 문제가 해결이 안되었습니다. 한달 뒤에야 겨우 문제의 실마리를 찾을 수 있었죠. (쪽팔립니다ㅎㅎ) 문제인즉슨 이런거였습니다. write를 할 때 리턴 값으로 '파이프가 깨졌다'는 오류 메시지를 받을 수 있을 것으로 기대했었는데 (write를 하는 중에 서버가 죽을 경우를 대비하려던 거였죠) 문제는 그게 SIGPIPE 시그널을 block 하지 않으면 제대로 동작하지 않고 프로세스가 죽는다는 거였습니다. (그것도 꼭 항상 죽는건 아니었죠. ㅋㅋ)

이 문제의 교훈은 이런 거였습니다. 매뉴얼에 의존해서 그대로 몇 줄 코드를 구현했습니다. ('상대 프로세스가 죽을 경우 내 프로세스는 파이프가 깨졌다는 오류 메시지를 받는다'는 것이 매뉴얼 내용이었습니다.) 그런데 그 코드가 정말로 제대로 동작하는지는 확인을 하지 않았던 겁니다. '몇 줄'에 불과하고, '매뉴얼 대로' 구현했기 때문에, 거기 버그가 숨어들어갈 거라고는 생각을 못했던 것이죠.

이 문제를 '기계적'으로 해결하려면, 작은 수정을 가할 때 마다 그 수정이 정말로 올바른지 확인을 하고 넘어가야 했습니다. TDD 수준으로 보폭을 좁게 가져가는 것이 필요하다는 결론을 그 때 얻은 거죠. 이 결론을 실천해 볼 기회는 2년 뒤에 찾아왔습니다. 일단 모든 코드의 구현을 마친 뒤 (테스트 과정에서 설계가 변경될 수 있다는 가정은 일단 배재했기 때문에 그럴 수 있었습니다. 코드에 확신이 있기도 했고, 사실 무식할 때 가장 용감해지는 법이니까요), 시스템의 아주 작은 부분부터 차례로 테스트를 해 나기가 시작했습니다. 테스트를 마친 코드만을 조금씩 시스템에 포함시켜서 빌드를 해 나가기 시작했고, 그 부분이 제대로 시험되었다는 확신이 들 때메만 다음 코드로 넘어갔습니다. 이런 식으로 해서 결국 연동시험 때 발견된 버그 수를 0으로 낮출 수 있었습니다. 테스트에 CppUnit을 도입한 것, TDD를 공부한 것, '한 걸음을 뗄 때 마다 뒤돌아보기'에 대한 확신이 생긴 것 등이 그 시기에 했고 느꼈던 것들입니다. (지금 생각하면 '책 한 권만 잘 읽었어도 더 잘 할 수 있었을텐데' 하긴 합니다만.)

결국, '그 자그마한 버그 하나'가 저를 바꿔놓은 것이죠. 이 블로그도 그 덕에 생겼습니다. ^^;

많은 개발자들이 Continuous Integration이나 Issue Tracking의 필요성에 대해 공감은 하면서도 실제 도입을 망설이는 이면에는 '버그는 부끄러운 것'이라는 개발자적 자존심이 어느 정도 깔려 있다고 저는 생각합니다. 그런 부분을 해소할 수 있으려면, 버그 자체도 지식으로 대우하는 자세가 필요합니다. Bug Tracking이라는 말 대신 Issue Tracking이라는 다소 점잖은 용어가 널리 쓰이는 것도, 어쩌면 그래서일지도 모르겠어요.

버그가 나를 더 좋은 곳으로 데려갈 수 있다는 확신을 가지는 것은, 그런 의미에서 중요하다고 생각합니다. 여러분도 저처럼, '나를 바꾼 버그'를 하나씩 가지고 계신가요?

  1. reproduce하기 굉장히 난감한, 발현 시점을 도무지 정확히 알 수 없는 버그. [본문으로]
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 공감하고 갑니다. ^^

    2009.01.29 02:40 신고 [ ADDR : EDIT/ DEL : REPLY ]
    • 아. 안녕하세요? 제가 블로그 첨 열었을 때 A2님 블로그에서 X-note에 리눅스 설치하는 방법을 참고했던 기억이 나네요. 새해 복은 많이 받으셨는지? 올해도 건승하시길~

      2009.01.29 09:11 신고 [ ADDR : EDIT/ DEL ]
  2. 정말 공감가는 글입니다. 저는 아직 인생까지 걸어보진 못했지만 잡아내는 버그 한두개로 소프트웨어 자체를 fail 시켜야 한다는 점에서는 거의 매일 누구에겐가는 운명적인(?) 버그를 잡아낸다고 해야하나요. 하지만 더 열심히 한다면 언젠가는 저도 제 인생을 바꿀만한 버그를 잡아내는 날이 오겠죠? ^^; 어쨌든 이젠 돌다리를 두들겨만보고 건너는 버릇은 아예 없앴습니다. 뛰어보고 엎어보고 발로 차보고 긁어보고 맛도 본 후에나 건너게 되더군요.

    2009.03.26 13:00 신고 [ ADDR : EDIT/ DEL : REPLY ]

Extremely Agile/TDD2008.11.04 14:20
앞서 남이 짠 클래스 코드를 어떻게 리팩토링하면 jMock을 통해 테스트하기가 좀 더 쉬워지는지를 살펴봤습니다. 그런데 현실 세계의 클래스들이 전부 그렇게 간단하게 만들어져 있으면 문제가 쉬운데, 실제로 테스트를 진행하다보면 그렇지 않은 경우를 종종 만나게 됩니다.

가장 골치아픈 경우 중 하나는, 생성자 안에 해당 클래스의 비즈니스 로직(?)이 들어가 있는 경우죠. 그러니까 좀 돌려서 말하면, 클래스의 생성자 코드 안에 테스트해야하는 알고리즘의 일부가 들어가 있는 경우입니다. 이 알고리즘이 대상으로 하는 객체들 중 하나를 Mock 객체로 만들어 테스트해야 한다면, 상황은 간단히 풀리지 않습니다. 다음 코드를 보시죠.

class Foo {
    public Foo() {
        ...
        bar.doSomething();
        ...
    }
    ...
};

bar에게 뭔가를 하고 있습니다. 그런데 bar에 doSomething을 호출하는 순간 네트워크 접속이 발생하기 때문에, 그 상황을 피하기 위해 bar를 mock 객체로 만들고 싶다고 해 보죠. 앞선 글에서 사용한 방법(아마 패턴을 아시는 분이라면 그 방법이 소위 '템플릿 메소드 패턴'인건 아시겠습니다만...)을 그대로 써먹자면 다음과 같이 해야 할 텐데요.

class Foo {
    protected void doSomething() {
        ..
        bar.doSomething();
        ..
    }

    public Foo() {
        ...
        doSomething();
        ...
    }
    ...
}

얼핏 보면 그럴듯해 보입니다만, 다음과 같이 하는 순간 문제가 복잡해집니다.

class FooT extends Foo {

    private Bar bar;

    protected void doSomething() {
        ...
        // do something with bar
    }

    public FooT() {
        super();
        // do something with bar
    }
}

왜 문제가 복잡해지죠? FooT의 생성자가 불리는 순간, 상위 클래스의 생성자가 불리면서 doSomething()이 호출되는데, 이 때 호출되는 doSomething은 하위 클래스에 정의된 doSomething이어야 하거든요. 그런데 아직 객체가 완전히 초기화도 되지 않은 상태이니, 하위 클래스의 doSomething이 제대로 수행될 리가 없죠. 이런 문제는 C++이건 Java건 공통적으로 발생하는 문제입니다. C++에서는 가상 함수 포인터 초기화에 관련된 오류가 발생하면서 프로그램이 죽는 현상이 발생하고, Java에서는 멤버 변수 초기화에 관한 오류 메시지가 뜨면서 프로그램이 죽습니다.

이 문제는 "Don't call subclass methods from a superclass constructor"라는 글에 잘 정리가 되어 있습니다. 요지는 When designing a class for subclassing, it’s important to avoid calling any method that the subclass could or must override from the superclass constructor. 인데, 간단히 요약하면 "하위 클래스가 오버라이드할 가능성이 있는 메소드를 생성자 안에서 호출하는 것은 피해라"라는 뜻입니다. 하위 클래스에서 오버라이드를 해버리면, 이런 문제가 생길 수 있으니까요.

그러니까 생성자 안에 테스트 해야할 알고리즘의 일부가 들어있는 경우에는, 원래 클래스의 코드를 수정하는 일이 불가피해지고 맙니다. 그런 일이 자주 생기면 좋지 않기 때문에, 가급적 생성자 안에는 "초기화에 관련된 코드들"만을 두는 것이 바람직하겠습니다.

그럼 "어쩔수 없이 원래 클래스 코드를 수정해야 하는 경우"에는 어떻게 수정하는 것이 바람직 할까요? 가령 다음과 같은 클래스가 있다고 해 봅시다. 제가 실무에서 만난 클래스입니다.

public class Connection implements Definition {
   ...
   public Connection(String ip, int port, int type) throws Exception {
      ...
   }
   ...
}

이 생성자는 내부적으로 Socket 객체를 생성하여 그 메소드를 호출합니다. 골치아픈 생성자죠. ㅋㅋ 우리가 해결해야 할 목표는, Socket 객체를mock객체로 바꿔칠 수 있는 방법을 Connection 클래스에 추가하는 것이었습니다. 그래서 코드를 다음과 같이 변경했습니다.

public class Connection implements Definition {
   ...
   public Connection(String ip, int port, int type) throws Exception {
      init(ip, port, type, null);
   }
   
  Connection(String ip, int port, int type, Socket alternativeSocket) throws Exception {
      init(ip, port, type, alternativeSocket);
   }

   private void init( ... ) {
      // if alternativeSocket is not null, do something with it.
      // or, create Socket object and deal with it
   }
   ...
}

간단히 요약하면, (1) initialization 로직은 별도의 private 함수로 refactoring하고 (하위 클래스에 의해 오버라이드 되지 않아야 하기 때문에 private입니다) 그 함수의 네번째 인자로 외부에서 정의한 소켓 객체 (mock 객체겠죠)를 받은 경우 그 객체를 가지고 필요한 작업을 하도록 변경했으며, (2) 테스트를 위한 별도의 생성자를 정의하고, 그 생성자의 네번째 인자로 mock 객체를 인자로 받아 init 함수에 넘기도록 한 다음 (3) 원래 생성자는 init 함수를 호출하되 네번째 인자로 null을 넘기도록 코드를 변경했습니다.

이렇게 함으로써 코드에 그다지 '큰' 변경을 가하지 않고서도 테스트 가능성을 확보할 수 있었습니다. 새로 추가된 생성자는 그 scope가 package 이어야 합니다. 그래야 테스트 코드 안에서만 사용이 가능할테니까요.

[다음 글에서 계속...]

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/TDD2008.11.03 16:09

그러면 오늘은 jMock을 이용해 남의 코드를 테스팅하는 과정을 한번 살펴보겠습니다. 정답이라고 할 수는 없고, 저 개인적인 경험에 따른 것이니, 참고 정도 하시면 좋을 것 같습니다.

남의 코드를 테스팅할 때 중점적으로 고려한 사항은 다음과 같습니다.

1. 남의 코드의 인터페이스는 손대지 않는다.
2. 남의 코드의 주 알고리즘은 손대지 않는다.


물론 2는 어쩔 수 없이 손대야 할 경우도 생기긴 합니다만, 가급적 그러지 않는 것이 정신건강상 이롭습니다. 왜 그런지는 아마 여러분들도 잘 아실 거라 생각합니다.

그러면 이제 실제 사례를 살펴보겠습니다. ConnectionManager라는 클래스입니다. 이 클래스는 대략 다음과 같은 골격을 가지고 있습니다.

public class ConnectionManager extends Thread implements Definition {
  ...
 
  ConnectionManager(PolicyAgent pa) {
  ...
  }
  ...

  public void connect() {
    Connection conn = null;
    // main algorithm
    ...
    conn = new Connection(this.policyAgent.primaryServer, this.svrPort, ...);
    ...
  }

  ...
}

이 코드를 테스트하는 데 있어서 가장 큰 문제는, 내부에서 생성하고 관리하는 Connection 객체가 어딘가로 TCP/IP 연결을 시도한다는 점이었습니다. 코드를 단위테스트하는 와중에는 해당 서버가 없었기 때문에, 우리는 Connection 객체를 어떻게든 'mock' 해야만 했습니다. 그런데 생성하는 코드가 주 알고리즘에 뒤섞여있었기 때문에 애매한 상태였죠.

그래서, 우선은 주 알고리즘에서 Connection 객체를 생성하는 코드를 별도의 메소드로 분리하고, 알고리즘 안에 포함되어 있던 new Connection 부분을 전부 createConnection을 호출하도록 변경했습니다.

public class ConnectionManager extends Thread implements Definition {
  ...
 
  ConnectionManager(PolicyAgent pa) {
  ...
  }
  ...

  protected Connection createConnection(String svr, int port, int type) {
    return new Connection(svr, port, type);
  }

  public void connect() {
    Connection conn = null;
    // main algorithm
    ...
    conn = createConnection(this.policyAgent.primaryServer, this.svrPort, ...);
    ...
  }

  ...
}

이렇게 한 다음에, JUnit 코드 안에서 ConnectionManager 클래스 코드를 계승하여 ConnectionManagerT라는 클래스를 정의했습니다.

class ConnectionManagerT extends ConnectionManager {
 private List<Connection> extConns = null;

 public ConnectionManagerT(PolicyAgent parent) {
  super(parent);
  this.extConns = new LinkedList<Connection>();
 }

 void addExternalConnection(Connection extConn) {
  synchronized( this.extConns ) {
   this.extConns.add( extConn );
  }
 }

 private Connection getExternalConnection() {
  synchronized( this.extConns ) {
   if ( this.extConns.isEmpty() ) return null;
   return this.extConns.remove(0);
  }
 }

 protected Connection createConnection(String svr, int port, int type) {
  Connection conn = null;
  if ( (conn = getExternalConnection()) == null ) {
   return super.createConnection(svr, port, type);
  }
  return conn;
 }
 ...
}

그리고 이 클래스 안에서 createConnection메소드를 오버라이드 했습니다. 이제 테스트 코드에서는 ConnectionManager 클래스를 사용하는 대신, ConnectionManagerT 클래스를 사용해서 테스트를 진행하면 됩니다. ConnectionManager 클래스에 정의된 알고리즘이 createConnection을 호출하는 순간, 하위 클래스에 정의된 createConnection이 호출될 것이고, 이 메소드는 new Connection을 하는 대신 getExternalConnection 메소드를 호출하여 connection을 얻을 것이기 때문에, mock 객체를 통해 주 알고리즘의 테스트가 진행되도록 만들 수 있습니다. setExternalConnection 메소드를 사용해 mock 객체를 ConnectionManagenrT 객체에 세팅해두면 되는 것이죠.

그러니까 테스트 케이스 코드 안에서는 다음과 같은 짓을 순차적으로 하면 된다는 겁니다.

  PolicyAgent pa = new PolicyAgent();
  pa.initiate();

  ConnectionManagerT cm = new ConnectionManagerT(pa);

  ...

  final Connection connMock1 = context.mock( Connection.class );
  cm.addExternalConnection( connMock1 );

  //
  // test no-signature error scenario
  //

  context.checking( new Expectations() {{
   ...
  }});

  cm.run(); // 알고리즘 테스트

  ... // 수많은 assertion 코드들...

어떻습니까? 이렇게 하면 남의 코드를 몇줄 고치지 않고서도 테스트를 진행할 수 있습니다. new Connection 때리는 부분을 별도의 overridable 메소드로 분리하는 것 만으로, 테스트 가능성을 높인 코드를 만들 수 있다는 이야깁니다.

이런 가능성은 TDD 형태로 테스트 코드를 먼저 작성하건, 아니면 작성된 코드에 테스트 코드를 추가하건, 순서 불문하고 테스트 코드를 작성하느라 고민하지 않으면 좀처럼 열리지 않습니다. 의외로 테스트 가능한 코드를 만드는 것은 아주 간단한 리팩토링(Refactoring)으로부터 시작되는데도 말이죠.

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/TDD2008.10.31 14:31

jMock은 Mock 객체를 이용해 테스트를 지원하는 라이브러리입니다. www.jMock.org에서 다운받으실 수 있습니다. 요즘 회사에서 '다른 사람이 작성한 코드'를 좀 더 '테스트 가능'(testable)하게 만드는 동시에 test coverage를 높이는 일을 하고 있습니다. 작년까지 했던 일과는 조금 다른 일이라, 재미있습니다.

jMock 계열의 라이브러리로는 EasyMock 같은 것도 있습니다만, 써보니까 'Easy'하다고 꼭 좋은것만도 아니어서 다시 jMock으로 돌아왔습니다. 돌아와보니 jMock이라고 또 그리 사용법이 복잡하지는 않습니다. 사용법이 좀 까다로와 보여서 진입장벽이 '약간' 있을 뿐이죠.

Mock 라이브러리를 사용해 테스트 코드를 작성하는 목적은, '테스트 대상 알고리즘을 그 알고리즘이 대상으로 하는 자원과 분리'하는 것이라고 볼 수 있겠습니다. 가령 알고리즘 foo가 어떤 자원 bar들을 대상으로 돌아간다고 해 봅시다. bar의 구현 상태와는 독립적으로, foo의 정확성만을 테스트하고 싶다고 해 봅시다. 그 경우에는 '원하는 대로 동작하는 것이 보장된' 가짜 bar 객체들만을 만들어 두고 그 위에서 foo를 돌리게 되면 foo의 정확성을 무리없이 검증할 수 있을 것입니다.

그러니 결국 Mock 라이브러리들이 하는 일은, '분리(separation)'라고 요약할 수 있겠습니다.

실제 '남의 코드'를 테스팅하는 사례를 살펴보기전에, 우선 jMock 라이브러리 사용법의 '골격'부터 알아보도록 하겠습니다. jMock라이브러리를 사용하기 위해서는, 대략 다음과 같은 클래스들을 임포트해 둘 필요가 있습니다. (뭐 eclipse가 알아서 다 해주긴 하지만..ㅎㅎ) 전 JUnit 4를 가지고 프로그래밍을 했기 때문에, JUnit4Mockery 클래스를 임포트하도록 했습니다.

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.jmock.lib.legacy.ClassImposteriser;

그런 다음 이제 JUnit 테스트 클래스의 안을 채워나가야 하는데요. ConnectionManager라는 클래스가 있다고 치고, 그 클래스를 테스트하는 JUnit4 클래스를 구현한다고 가정하겠습니다. 그 클래스 제일 윗부분에 다음과 같은 부분을 둡니다.

public class ConnectionManagerTest {

  private Mockery context = new JUnit4Mockery() {{
    // enable mocking of class also (not only interface!)
    setImposteriser(ClassImposteriser.INSTANCE);
  }};
  ...

}

보통 Mock 라이브러리들은 Interface 클래스를 인자로 받아, 그 인터페이스가 하는 일을 흉내내는 가짜 객체를 생성합니다. 그런데 EasyMock이나 JMock은 확장을 통해 일반 클래스를 인자로 받아서 Mock 객체를 생성할 수 있도록 배려하고 있습니다. 위의 적색으로 표시된 부분은, 일반 클래스를 인자로 받아 Mock 객체를 생성할 수 있도록 지시하는 부분이라고 생각하시면 됩니다.

이렇게 하고 나면 이제 테스트 코드를 작성하면 되는데요. 대략 다음과 같은 순서를 따릅니다.

 @Test
 public void testRun() throws Exception {

  // 1. Mock 객체 생성
  final Connection mock = context.mock( Connection.class );

  // 2. Mock 객체를 테스트 대상 알고리즘에 세팅
  ...
 
  // 3. 알고리즘을 돌렸을 때 mock 객체에 무슨 일이 일어나게 되는지를 명시
  context.checking( new Expectations() {{
   allowing (mock).getInteger(); will(returnValue(34));
   oneOf (mock).writeInteger(56);
   allowing (mock).close();

  }});

  // 4. 알고리즘 구동
  ...

  // 5. 각종 assert 문들 삽입
  ...
 }

우선, Mock 객체를 만듭니다. context.mock의 인자로 객체를 찍어낼 클래스를 인자로 넘겨주어야합니다. 그런 다음, 만들어진 mock 객체를 테스트 대상 알고리즘에 세팅합니다. 알고리즘을 구동할 때 mock 객체를 인자로 넘겨주게 되어 있다면, 이 단계를 생략해도 무방하겠습니다.

그런 다음, 알고리즘을 실제로 돌리면 mock 객체에 어떤 행위가 일어나게 되리라 '기대'(expectations)하는지를 세팅합니다. context.checking 메소드에 Expectations객체를 만들어 넘겨주는 식으로 세팅하게 되는데, 그 안에 발생할 것으로 기대되는 행위 목록을 적어주면 됩니다. 일단 위의 예제의 의미를 간단히 설명드리면,

allowing... 으로 시작되는 줄은, 알고리즘 구동 과정에서 mock 객체의 getInteger() 함수가 여러번 불리게 될 수 있는데, 그 때 마다 34를 리턴하도록(will(returnValue(34))) 지시하고 있습니다.
oneOf... 로 시작되는 줄은, 알고리즘 구동 과정에서 mock 객체의 writeInteger 함수가 단 한번 불리게 되는데, 그 때 인자로 56이 전달되어 호출되어야 함을 명시하고 있습니다.
마지막의 allowing...으로 시작되는 줄은, 알고리즘 구동 과정에서 mock 객체의 close() 함수가 여러번 불리게 될 수 있다는 것을 명시하고 있습니다.

결국, mock 객체가 알고리즘의 요구에 어떻게 반응하여야 하는지를 명시하는 셈입니다. 이 부분을 잘 작성해주면 mock 객체에 전달되는 값이 예상치와 일치하는 지를 검사하여 알고리즘이 제대로 된 값을 사용해 mock 객체와 통신하는지를 검사할 수 있게 되고, 결국 알고리즘의 정확성을 검증할 수 있게 됩니다.

이렇게 expectation의 목록을 작성하고 나면, 그 다음으로는 알고리즘을 구동시키고, assert구문들을 사용하여 (assertTrue, assertFalse 등) 구동 후의 상태가 예상한 것과 일치하는지를 보면 됩니다.

expection을 작성하는 문법이나, JMock 사용법에 대해 더 자세히 알고싶으신 분은 JMock 웹사이트에 올라온 cheat sheet를 보시면 좋을 것 같습니다.

[다음 글에 이어서...]

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 이상곤

    JMock에 대한 강좌 잘 읽었습니다. 헌데 따라 하는 중 ClassImposteriser 클래스를 임포트하는 과정에서 바로 막혔습니다. ㅠㅠ package가 org.jmock.lib.legacy 아래에 ClassImposteriser 있다고 나와 있고 API 문서를 봐도 그렇게 나와 있는데 stable 버전 2.5.1 jar 파일에는 그 이하에 legacy가 안보입니다. 혹시 제가 뭔가 간과 한 부분이 있는지요?

    2010.01.20 14:26 신고 [ ADDR : EDIT/ DEL : REPLY ]

Extremely Agile/TDD2008.10.21 13:53
TDD를 배우고 실무에 적용해보고 나면, 프로그래머는 대략 다음 두 가지 중 하나의 유형으로 바뀝니다.

1. TDD를 굉장히 열정적으로 사용하는 프로그래머
2. 테스트의 필요성을 절감하고 테스트 케이스를 작성하긴 하는데, 그렇게 TDD를 열심히 하지는 않는 프로그래머

저는 이 중 어느쪽이냐 하면, 후자 쪽입니다. 아직 TDD에 확실히 익숙해지지 않아서 그럴 겁니다. 물론, 자기가 작성하는 코드에 대한 자신감이 높은 프로그래머도 두번째 유형에 속할 가능성이 높습니다. 이런 프로그래머는 TDD는 너무 오버헤드가 높은 방법이라고 느끼는 경향이 있습니다.

사용자 삽입 이미지

http://www.nilkanth.com/archives/2007/06/08/three-monkeys-of-test-driven-development/


그런데 두 번째 유형의 프로그래머들이 흔히 빠지는 함정이 하나 있습니다. 블랙박스 테스트(blackbox test)를 하게 될 가능성이죠.

보통 프로그램을 클래스 단위로 구분해서 작성하게 되면, 최소한 클래스가 하나 만들어 질 때 마다 테스트 케이스들을 만들도록 하는 것이 좋습니다. 그런데 프로그래머에 따라서는 클래스가 아니라, 모듈이 하나씩 만들어 질 때마다 테스트 케이스들을 작성하기도 합니다.

그렇게 하면 보통 모듈에 속한 모든 클래스들에 대해 테스트 케이스를 작성하기는 어려워집니다. 정말로 어려워서 어렵다는 것이 아니라 귀찮아서 생략할 가능성이 높아지기 때문에 어렵다고 하는 것이죠. 그렇게 되면 모듈의 관리자 클래스(Manager class)에 대한 테스트 케이스들만 만들어지게 됩니다. 모듈의 인터페이스에 대한 테스트 케이스만 만들어지게 된다는 것이죠.

결국 모듈이 블랙 박스가 됩니다.

블랙 박스 테스트가 의미가 없다는 뜻은 아닙니다. 하지만 추상화 단계의 위쪽으로 올라가 테스트를 하게 되면, 거기서 처리해야 할 테스트의 가짓수가 늘어나게 됩니다. 그 모든 경우를 다 따져 테스트 케이스를 작성하면 좋겠지만, 그렇게 못할 가능성이 높습니다.

결정적으로, 블랙 박스 테스트로는 "모듈의 어느 부분에서" 오류가 발생했는지를 감지하기가 어렵다는 문제도 있습니다. 그렇기 때문에 가급적 코드의 가장 작은 단위에 대해서부터 단위 테스트를 진행하는 것이 좋다는 것이죠.

TDD를 하지 않더라도 그렇게 하면 적어도 버그의 수를 줄이는 데는 도움이 되는 것을 경험했습니다. 거기다 부수적인 이득도 얻을 수 있는데, "테스트가 쉬운 코드를 만든다"는 목적은 그대로 유지할 수 있다는 겁니다. 물론 "테스트 코드를 통해 실제 코드의 디자인이 결정되는" 신기한 경험은 할 수 없겠지만 말이죠.
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/General2008.01.22 05:55

SW 프로젝트는 일입니다. 모든 일이 다 그렇듯, 시간이 지나면 남은 일의 양은 차츰 감소하게 마련입니다. 하지만 대부분의 SW 프로젝트의 경우, 그렇지 않을 떄도 많습니다. 가장 흔한 것은, 마감 시간에 맞추어 밤을 새우는 일입니다. 일의 총량은 정해져 있을 터인데, 왜 프로젝트가 끝나갈 무렵까지 일의 양은 전혀 줄어들지 않고, 결국에는 철야를 하게 되는 것일까요?

애자일 방법론을 만든 사람들은 이 문제를 깊이 있게 연구했습니다. 그리고 그 결과, 이런 결론을 얻습니다.

일은 빵과 같다. 빵을 먹으면, 빵은 줄어든다. 일도 마찬가지이다. 일을 하면, 남은 일의 크기는 점점 작아진다. 하지만 '내가 먹으려는 이 빵이 정말로 내가 먹고 싶었던 빵인지' 한 입 베어물어 보기 전에는 정확하게 알 수 없는 것과 마찬가지로, 고객에게 소프트웨어를 인도하기 전까지는 누구도 그것이 정말로 고객이 원했던 그것인지 확신할 수가 없다.

고객이 원하지 않는 기능을 구현하게 되면, 그 기능을 구현하는 데 투자했던 시간들이 고스란히 오버헤드가 되고, 결국 고객이 정말로 원하는 기능을 구현하기 위해 프로젝트 후반부에 날밤을 새우게 됩니다. 이런 문제에 대해서는 마이크 콘(Mike Cohn)을 비롯한 여러 사람들이 많은 연구를 했고, 좋은 책도 많이 있습니다. Agile Estimating and Planning은 그 중 하나입니다.

하지만 오늘은 그 보다는 좀 더 개인적인 이야기를 해 볼까 합니다. 개인적인 견지에서 보면, 프로젝트 막바지에 더 바쁜 가장 결정적인 이유는, 프로젝트 중간 중간에 박혀있는 마일스톤 점검 시점에 반드시 해야 할 몇 가지 일들을 하지 않고 넘어갔기 때문입니다.

마일스톤 점검시에, 제가 속한 팀은 주로 그 때 까지 개발한 소프트웨어의 큰 틀이 정상동작하는지를 점검합니다. 속칭 '연동시험'을 하는 것이죠. 이 연동시험은 개발에 참여하는 여러 업체와 개발자들로 하여금 '다른 사람의 관점에서' 시스템을 바라볼 수 있도록 해 주기 때문에 굉장히 중요합니다. 그 과정에서 '개발에는 별로 적합하지 않은' 업체나 개발자가 가려지기도 하죠. ('다른 사람의 입장을 전혀 고려하지 않는 개발자'는 그 한 예가 되겠습니다. 이런 개발자일수록 '개발자의 자질'에 대해 더 많이 이야기하곤 한다는 것은 아이러니 한 일이죠. 좋은 코더가 되는 것도 중요하지만, 사실 더 중요한 것은 존중의 자세입니다.)

그런데 이 마일스톤 점검시에는 보통 자질구레한 사항이나 버그들에 신경을 쓰기 보다는, 서로 다른 사람들이 만든 시스템이 별 탈 없이 연동되어 돌아가는지를 살펴보는데 집중하는 일이 많습니다. 자잘한 문제들은 TO-DO 리스트에 적어놓는 것으로 점검을 마무리 짓곤 하죠.

문제는 그렇게 만들어진 TO-DO 리스트를 나중에는 아무도 거들떠보지 않는다는 사실입니다. 그러다보니, 데드라인이 다가오면 최종 연동시험과 병행해서 '자질구레한' 버그들도 함께 잡아 나가야 하는 일이 벌어지게 되고, 결국은 잠을 줄이게 되는 것이죠.

'엉뚱한 기능'을 구현하는 것도 위험천만한 일이지만, 진짜 '일'을 남겨두는 것도 위험천만하기는 마찬가지라는 거에요. 그런데 이런 실수를 되풀이하게 되는 이유는 대체 뭘까요?

가장 큰 이유는, 그런 '뒷마무리 작업'이 대체로 재미가 없기 때문입니다. 뭔가 그럴듯한 새 기능을 시스템에 추가하는 건 재미있습니다만, 그 부산물들을 치우는 것은 사실 지루한 일이죠. 물론, 그런 뒷마무리 작업을 충실히 할 만큼 넉넉한 시간이 주어지지 않는 경우도 있습니다. 자고 일어나면 세상은 달라져 있고 또 뭔가 새로운 요구사항은 생겨나게 마련이니까요.


PS.

이 글은 새벽 네시쯤 쓴 것 같군요.
저도 날밤을 새고 있었다는 이야기죠. ㅋㄷ



 

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. SW개발은 아니지만 나름 개발일을 하고 있는 완전공감이에요 ;ㅁ;
    내일까지 프로젝트마감을 맞추기위해 1주일전부터 완전철야중. 하루에 2~3시간밖에 못자는것 같아요 ;ㅁ;
    지금도 밤샘중 흑흑흑;;;
    왜 마지막날이 되면 온갖 수정사항들이 나오는건지 TO DO LIST가 줄질않네요.
    한개 지우면 2~3개 추가되고. orz

    2008.01.22 06:27 신고 [ ADDR : EDIT/ DEL : REPLY ]
    • 그러게요. 이런 문제를 극복해 나가는 현명한 방법을 찾는 것이 개발자들이 해야 할 일이겠죠. 개인적으로는 TO-DO LIST를 관리하는 것 보다는, TO-DO BOARD(해야할 일 게시판)을 만들어 모두가 볼 수 있는 자리에 배치하는 것도 한 방법이 되지 않을까 생각하고 있습니다. 그러면 시간이 지날수록 그 게시판에 나열된 개선 항목들이 줄어드는 모습을 볼 수 있어서 좋을 거라고 생각해요. 어쩐지 '사용자 스토리'의 동어반복같다는 생각도 드는군요. 덧글 감사합니다.

      2008.01.22 06:44 신고 [ ADDR : EDIT/ DEL ]
  2. 개발자만 공감하는 얘기는 아니랍니다 ;ㅁ;
    기획자도 똑같은 짓(?)을 하고 있다죠

    제 시간관리 능력을 탓해야 겠지요 쩝

    2008.01.22 07:47 신고 [ ADDR : EDIT/ DEL : REPLY ]
    • 기획자도 개발팀의 일원이니 개발자라고 보는 것이 좋겠죠. 시간관리라는 문제를 개인 차원의 문제로 접근하면 전반적인 상황은 크게 개선되지 않을 수도 있다고 생각합니다. 가급적 팀 단위의 해결책을 찾는 것이 좋겠죠.

      2008.01.22 09:25 신고 [ ADDR : EDIT/ DEL ]
  3. 아직 프로젝트에 대한 경험이 없어서 선배분들의 얘기가 마냥 멀리 느껴집니다.OTL.....
    하지만 토이박스를 몇 번 만들어본터라 막바지에 자질구레한 일 때문에 지겹고 바빠진다는 것을 매번 느꼈습니다.;;ㅜㅜ

    2008.01.22 08:54 신고 [ ADDR : EDIT/ DEL : REPLY ]
    • 그런 일들이 쌓이고 쌓이면 결국 개발이라는 것이 막판에는 재미없는 일이 되어버리겠죠.

      2008.01.22 09:26 신고 [ ADDR : EDIT/ DEL ]
  4. 저는 프로그램 개발과는 완전 거리가 먼....출판 편집을 하고 있지만 와닿네요. ㅡㅡ;;

    2008.01.22 22:19 신고 [ ADDR : EDIT/ DEL : REPLY ]
  5. ㅡㅡ;

    [펌] 해가겠습니다. ^^; 당연히 펌한 url 과 명시는 꼭 하겠습니다. ^^;
    감사합니다. 펌한 사이트 : http://cafe.daum.net/aspdotnet

    2008.02.13 00:36 신고 [ ADDR : EDIT/ DEL : REPLY ]

Extremely Agile/TDD2007.09.26 22:11
HttpUnit에 대한 글을 쓴지도 얼마되지 않았는데, 웹 서핑을 하다보니 또다른 툴을 만나게 되는군요.

아래의 그림은 JWebUnit 웹사이트 (http://jwebunit.sourceforge.net/index.html) 에서 가져온 아키텍처 그림입니다. JWebUnit 역시 HttpUnit과 마찬가지로 JUnit에 기반하여 만들어진 단위 테스트 프레임워크임을 알 수 있습니다. 흥미로운 것은 플러그인 구조를 채택해서 확장가능토록 구성된 점인데, 현재로서는 HtmlUnit 플러그인만 제공됩니다. ^^;

자바 스크립트 지원이 어느 정도까지 가능한지는 아직 잘 모르겠습니다. 솔직히 웹 브라우저와 직접 연동해서 테스트를 해 주는 Selenium과 같은 솔루션이 아니라면 자바 스크립트 엔진을 직접 구현하는 테스트 솔루션이 되어야 하는데, 그런 정도까지는 무리지 않을까, 하는 생각도 드네요.

JWebUnit 아키텍처

JWebUnit 아키텍처



public class WebIntegrationTest extends net.sourceforge.jwebunit.WebTestCase {

    public void testIndex() {
        beginAt("/index.html");
        assertTextPresent("Hello world");
    }

    private org.mortbay.jetty.Server server;

    protected void setUp() throws Exception {
        server = new org.mortbay.jetty.Server(0);
        server.addHandler(
                new org.mortbay.jetty.webapp.WebAppContext("src/main/webapp", "/my-context"));
        server.start();

        int actualPort = server.getConnectors()[0].getLocalPort();
        getTestContext().setBaseUrl("http://localhost:" + actualPort + "/my-context");
    }
}

위의 코드를 보시면 아시겠습니다만, assertTextPresent같은 재미있는 assert 함수들을 많이 제공합니다. 이런 함수들을 통해서 웹 페이지에 대한 제약사항들을 검사할 수 있습니다. 위의 예제 코드는 http://www.brodwall.com/johannes/blog/2006/12/10/in-process-web-integration-tests-with-jetty-and-jwebunit/ 에서 가져왔습니다.


신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/TDD2007.09.25 02:00
방금 웹 서핑을 하다가 HttpUnit에 대한 글을 잠깐 읽었습니다. 블로그들에는 없는 내용이 없군요 :-P

http://httpunit.sourceforge.net/

위의 URL이 httpunit의 주소입니다. jUnit과 함께 사용해야하는, Java 기반의 solution입니다.

Cookbook 문서를 읽어보면 알수 있는 것입니다만 (아래의 글은 Cookbook 문서를 보고 간단히 요약한 것입니다) HttpUnit 프레임워크는 WebConversion 클래스를 사용해서 웹 브라우저가 만드는 HTTP conversion을 흉내냅니다.

WebConversation wc = new WebConversation();
WebRequest     req = new GetMethodWebRequest( "http://www.meterware.com/testpage.html" );
WebResponse   resp = wc.getResponse( req );

WebResponse 객체를 받아온 다음에는 getText() 메소드를 불러서 텍스트 형태의 프로세싱을 할 수도 있고, getDOM() 메소드를 호출해서 DOM 기반의 조작을 할 수도 있습니다. (이 편이 좀 더 편하긴 하겠네요.)

다음 처럼 하면 링크를 따라가는 것도 가능합니다.

WebConversation wc = new WebConversation();
WebResponse resp =
            wc.getResponse( "http://www.httpunit.org/doc/cookbook.html" ); // read this page
WebLink link = resp.getLinkWith( "response" );                              // find the link
link.click();                                                                               // follow it
WebResponse jdoc = wc.getCurrentPage();                                  // retrieve the referenced page

폼 프로세싱이나 테이블 형태의 자료 처리도 가능합니다. 폼 프로세싱 예제만 살펴보면,

WebForm form = resp.getForms()[0];      // select the first form in the page
assertEquals( "La Cerentolla", form.getParameterValue( "Name" ) );
assertEquals( "Chinese",       form.getParameterValue( "Food" ) );
assertEquals( "Manayunk",      form.getParameterValue( "Location" ) );
assertEquals( "on",            form.getParameterValue( "CreditCard" ) );

위와 같이 해서 폼의 기본 파라메타 값이 어떻게 만들어져있는지 검사할 수도 있고,

form.setParameter( "Food", "Italian" );      // select one of the permitted values for food
form.removeParameter( "CreditCard" );         // clear the check box
form.submit();                                // submit the form

폼에 포함된 컨트롤들에 값을 넣어서 날릴 수도 있습니다.

웹 기반의 인터페이스 테스트를 하는 데 굉장히 유용할 것 같네요. :-) 하지만 이 프레임워크의 가장 큰 약점은...

자바스크립트 지원이 아직은 기본적인 수준이다

는 점이 되겠군요 -_-;


다들 알고 있는데 저만 모르는 사실들이 아주 많은것 같아 좀 우울합니다 ㅋㅋ



신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 시월애

    잘보고갑니다. ^^
    많은도움얻고갑니다 ㅎ

    2008.03.24 13:38 신고 [ ADDR : EDIT/ DEL : REPLY ]
  2. 이것은 위대한이며 많은 사람들이이 공유를 주셔서 감사합니다 사랑 할거야 확신

    2011.11.22 04:12 신고 [ ADDR : EDIT/ DEL : REPLY ]

Extremely Agile/TDD2007.09.23 20:45

아래의 그림은 TDD를 사용한 개발을 수행하는 절차를 요약하고 있습니다.

원본은 여기에 링크 걸어둔 PDF 파일에 나옵니다. 하도 예전에 다운받아둔거라 언제 받았는지도 기억이 잘 나질 않습니다만, PDF 파일을 열어 보면 어디서 다운받은 것인지는 나오는군요. :-) 참고하시기 바랍니다.

나온지 좀 오래된 자료이기는 합니다만, TDD의 개발 절차라는 것이 TDD 개념이 등장한 이래로 그렇게 많이 바뀌거나 하지는 않았습니다. 코드 작성 전에 테스트를 먼저 한다, 는 원칙은 그대로 있고, 그 적용에 따르는 세부사항만 좀 바뀐 정도일텐데, 그 나마 큰 변화가 있는 것 같지는 않아요.


사용자 삽입 이미지



절차는 간단히 요약하면 이렇습니다.
  1. 테스트 리스트를 만듭니다. (어떤 테스트를 수행할 것인지 협의하는 과정에서 나옵니다. 사용자 스토리를 만드는 과정에서 도출되는 일이 많습니다.)
  2. 다음의 3~7까지를 계속 반복합니다.
  3. 테스트 리스트에서 테스트 하나를 고릅니다.
  4. 우선, 컴파일조차 되지 않는 테스트 코드를 작성합니다. (컴파일이 되지 않으니 테스트를 아예 할 수가 없습니다.) [위의 다이어그램에서는 위에서 두 번째 줄의 왼쪽에서 두 번째 박스]
  5. 대충 뜯어 고쳐서 컴파일은 되도록 만듭니다. 테스트는 당연히 실패할 것입니다.
  6. 그런 다음, 우선 테스트가 통과되도록 만듭니다. (테스트가 통과되도록 만드는 것이 목적이니, 코드에 hardwired 된 로직이 존재해도 아직은 상관이 없습니다. 가령, 3이라는 반환값을 요구하는 테스트라면, 함수 안에서 그냥 3을 리턴해도 이 단계에서는 상관이 없다는 말입니다.
  7. 그런 다음 리팩토링(refactoring)을 시작합니다. 리팩토링을 해나가면서 중복을 제거하고, 말이 안되는 부분을 하나씩 말이 되도록 만듭니다. 리팩토링은 '아주 작은 단위'의 코드 변경입니다. 코드가 변경될 때 마다 컴파일하고 테스트를 돌려서, 그 리펙토링이 테스트를 fail시키지 않는지 확인해야 합니다. fail시켰다면, 너무 작은 리팩토링을 시도했거나, 잘못된 리팩토링 - 즉, 논리적인 오류를 발생시킨 리팩토링을 했다는 뜻입니다. 위의 그림에서는 '리팩토링'->'테스트' 사이의 순환 관계가 명시적으로 드러나지는 않습니다만, 리팩토링과 테스트는 반복적으로 계속해서 해나갈 수 있습니다.
뭐, 그림 자체는 그런 뜻입니다. :-) 그럼 테스트는 대체 어떻게 만드느냐.

이상적으로 보자면 테스트가 '개발될 모듈이 가져야 할 이상적인 인터페이스'에 근거해서 만들어지는 게 가장 좋겠지만, 그렇게 하자면 머리속에 '개발될 모듈의 형태'가 우선 잡혀야 합니다.

TDD 개발 과정에 대한 책 ("테스트 주도 개발") 을 보신 분이면 아시겠지만 테스트는 고정적이 아니며 항상 변화합니다. 개발될 모듈의 형태가 어떠할 지 완벽하게 미리 예측하는 것이 불가능하기 때문입니다. 테스트를 하고, 중복을 제거하고 리팩토링을 하다보면 개발중인 클래스가 개발자가 예측한 것과 조금은 다른 방향으로 흘러가게 되는 일도 생길 수 있고 (물론 가급적 그렇게 되지 않는 것이 좋겠지만 말입니다) 다른 식으로 구현을 가져가는 것이 보다 효율적이겠다는 판단을 내리게 되는 일도 생길 수 있습니다.

그러니, 테스트를 '미리 잘 정돈하여 만들어두어야 겠다'는 생각은 버리는 게 좋겠습니다. '어떤 테스트를 해야 하느냐'는 고정적일 수 있지만, '테스트를 어떻게 해야 하느냐'는 것은 계속해서 달라질 수 있기 때문입니다.
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. TDD를 적용하기 위한 절차를 찾다가 발견하게 되었습니다. 많은 도움 얻고 갑니다...
    즐거운 하루되세요~^^

    2008.04.22 11:38 신고 [ ADDR : EDIT/ DEL : REPLY ]
  2. TDD를 하면서 느끼는 것은 초보자 무리라는 것입니다. ^^ 제가 생각하기에는 그래요.
    일단 초보자는 상급자로부터 Requirement를 받기 때문에 상당히 제한적이고, 초반에 참여를 하더라도 경험이 적기 때문에 테스트 코드를 많이 못 만들어 내는 것 같습니다.
    또한, 초보자는 Refactoring을 해야할 때, Refactoring 자체에 대한 지식이 별로 없는 관계로 힘들고 때에 따라서 Design Pattern을 적용해야 할 때가 있는데, 그렇지 못 할 때도 있더군요. 또 Design Pattern을 몰라도 되지만, 바라던 최적화 코드는 그다지 나오지 않더라구요.

    하지만, 지속적으로 이렇게해서 개발경력을 쌓으면 무적이 될 것 같기도 하네요. 그래서 저도 TDD를 지향합니다.

    Rod Johnson의 인터뷰인가? 정확하게 기억나지는 않지만, 꼭 필요한 기술이라고 해서, 1. OFD(Oriented Framework Development), 2. DI & IOC, 3, TDD 등이 있었던 것 같네요.

    잘 읽고 갑니다.

    2008.06.11 15:05 신고 [ ADDR : EDIT/ DEL : REPLY ]
    • 반갑습니다. 초보자에게 좀 무리일 수는 있습니다만, 그 과정을 통해 배우는 것이 많다는 점에서 긍정적일수도 있을 것 같습니다. 저는 TDD를 최근에야 접했는데, 제가 통상적으로 프로그램을 짤 때 거치는 과정과 많이 닮아있어 오히려 프로그래밍을 배우기 시작하는 초기에 배웠더라면 어땠을까, 하는 생각을 자주 했습니다.

      2008.06.12 14:30 신고 [ ADDR : EDIT/ DEL ]

Extremely Agile/TDD2007.09.20 10:54
웹에 대해서 관심을 끊은지 하도 오래되어서 이런 툴이 나온줄도 몰랐군요.

TDD할 때 가장 짜증나는 부분이 웹 사이트를 비롯한 "User Interface"에 대한 interaction을 어떻게 자동화 할 것이냐 하는 부분입니다. 사실 이 부분에 대한 고민을 예전에 좀 했었지만, 그냥 얼핏 생각하기에는 Lynx같은 텍스트 기반 브라우저를 돌려서 그 결과를 적절히 파싱한 다음에 원하는 결과와 대조하면 되는게 아닐까, 하는 다소 느슨한 생각만 하고 있었어요. 언젠가 그런 솔루션을 찾거나 만들거나 해야지... 하는 생각만 했더랬죠.

그러다 웹 서핑하는 도중에 Selenium이라는 솔루션을 만나게 되었습니다. 웹 사이트 주소는 http://openqa.org/ 입니다.


Selenium IDE 사용 예를 보여주는 플래쉬 무비


웹 사이트에 대한 interaction을 녹화해두었다가 재생해주는 방식을 사용해서 웹에 대한 테스트를 자동화할 수 있게 해 줍니다. 얼마나 테스트를 잘 할 수 있는지는 아직 해보지 않아서 모릅니다만, 나중에 한 번 써 봐야 겠어요. 그런데 공짜는 아닌거같군요. -_-

Seleniuim IDE는 Firefox하고만 연동되는것 같습니다만, Selenium Core는 대부분의 웹 브라우저에서 다 잘 되는군요.  심지어는 이런 저런 운영체제들에서도 다 잘 돌아갑니다. 거기다 오픈 소스... :-) 쭉 읽어보니 Selenium Core는 자바 스크립트 기반 솔루션인것 같습니다. Selenium IDE는 Core를 통한 Test Case 개발을 좀 더 편하게 할 수 있도록 지원해주는 툴인 셈이고요.
신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/TDD2007.09.19 16:42
예전에 만들었던 PDF 파일을 첨부합니다. 참고하시길 :-)

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

Extremely Agile/General2007.09.18 15:44

Extreme Programming에 관해 최근 이 책 저 책 열심히 보고 있습니다만.

Extreme Programming 혹은 Agile 방법론을 능숙하게 실행할 수 있거나 혹은 그 절차를 능숙하게 정의내릴 수 있는 프로그래머 혹은 아키텍트가 되려면, 대체 무엇을 알아야 하느냐. 이런 의문이 생기더군요.

그래서 몇 가지를 정리해봤습니다.

  1. 기술에 대한 열린 마음가짐 - 어떤 형태의 기술이던 열린 자세로 받아들이겠다, 는 마음가짐이 정말 중요한것 같습니다. 이런 자세가 없는 프로그래머는 어디서 일해도 실패하기 딱 좋습니다.
  2. 사람에 대한 열린 마음가짐 - 주변 사람들이 전부 자기와 같은 가치를 갖는 사람이라는 마음가짐을 가지고, 존중하는 자세가 필요합니다. 이런 자세가 없으면 왕따당하기 딱 좋지요...
  3. 기본적인 기술들에 대한 지식들 - 이런 지식들로는 뭐가 있을까요? 간단하게 꼽아보면 얼핏 생각나는 것만해도 UML, CVS, C++, Java, Design Pattern, Refactoring, Make, Ant, TDD, jUnit, CppUnit 등등이 있군요. 여기서 제가 잘 모르는 거로는 Ant가 있네요. -_-;

사실 UML과 C++, Java에 대해서는 오래 전부터 알고 있었습니다만, Design Pattern이나 Refactoring 기법, CVS에 대해서는 공부하기 시작한지 일년 남짓밖에 되질 않습니다.

하지만 개발을 진행하면 할수록, 상대적으로 저수준의 기술(C++, Java 등, 프로그래밍 언어레벨의 기술)보다는 그 상위의 개념에 대한 지식이 보다 절실하게 느껴지더군요. 사실 Extreme Programming에 사용되는 기술들은 프로그래밍 언어보다는 추상화 레벨이 높습니다. UML 같은 것은 'Language'라고는 하지만 실제로 구현될 시스템을 기술하는 기호언어에 가깝고, Design Pattern이나 Refactoring같은 기술들은 어떤 언어에도 적용될 수 있는 중립적인 기술이라 (물론 적용 형태는 언어에 따라 조금씩 달라질 수 있겠습니다만) 저수준의 지식이라고 하기는 힘들어요.

아키텍트라고 불리는 사람들에게 요구되는 것이 프로젝트 관리 능력이라고 본다면,  그런 역할을 맡는 사람들은 저수준의 프로그래밍 언어뿐 아니라, 보다 추상화 정도가 높은 언어(그런 언어들을 메타-프로그래밍 언어라고 부를 수 있을까요?)에도 능통할 필요가 있습니다. 사실 아키텍트는 단순한 개발자(주로 프로그래밍 언어만을 사용하여 소통하는) 뿐 아니라 다른 팀이나 고객들과도 소통을 할 의무가 있거든요. 그런 사람들에게 프로젝트의 궁극적인 지향점을 설명하려면, 보다 추상화 정도가 높은 언어로 개념을 다듬는 것이 필요하다는 거죠.

좀 일찍 이런 생각을 했으면 공부를 더 열심히 했을텐데... ㅋㅋ

최근에 관련해서 읽고 있는 (혹은 다 읽은) 책들은 다음과 같습니다.

  1. 익스트림 프로그래밍
  2. 사용자 스토리
  3. 린 소프트웨어 개발
  4. 테스트 주도 개발
  5. 실용주의 프로그래머
  6. 실용주의 프로그래머를 위한 버전 관리
  7. Java 프로그래머를 위한 UML, 실전에서는 이것만 쓴다

책을 안읽은지 너무 오래되어서 -_- 여러 책들을 한꺼번에 읽으려니까 좀 정신사납긴 하군요. ㅋㅋ



신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 안녕하세요!!! 좋은 글 감사합니다. 자주올게요!!

    2011.01.28 01:15 신고 [ ADDR : EDIT/ DEL : REPLY ]