1. TDD를 적용하게 된 계기

TDD에 대한 이야기를 처음 들은 후 부터, TDD를 처음 적용했던 LG-SH150 DMB Player 프로젝트 이래로 TDD 에 대한 나의 관심은 주욱 지속되어왔다.

처음에는 다소 번거로운 작업이지만 어느 정도 test가 쌓이게 되면 그 후부터는 마음 놓고 코드를 수정할 수 있다. 그래서 다양하고 실험적인 refactoring이 가능하며, 코드의 중심 로직을 대거 뜯어 고친 후에도 코드 검증에 걸리는 시간이 매우 짧다는 점, 무엇보다도 코드의 품질이 보장된다는(이 부분은 test 가 어느 정도의 coverage를 가지고 있느냐에 따라 다를 수 있다) 것 등...TDD는 거부할 수 없는 매력을 가지고 있다고 생각한다.

iPhone 개발을 시작하고 OCUnit에 대해 알게 되면서, iPhone 어플 개발시에도 TDD를 적용해 보리라 하고 생각하고 있었다.
그러던 중에 개발중인 어플리케이션이 DB(sqlite3)를 사용하게 되었고, view 부분과 data 부분을 어떻게 하면 효과적으로 분리할까 고민하던 중에 Ruby On RailsActiveRecord와 비슷한 일종의 DB framework 비스무레 한 것을 하나 만들어 보자는 생각을 하였고, 이 프레임웍을  TDD로 구현해 보기로 했다.


2. Ruby On Rails의 ActiveRecord를 참고하여 만든 iRecord

올해 초(3~4월)에 Ruby On Rails(이하 RoR)를 처음 접하고, RoR을 사용하여 고민상담 서비스를 만들면서 RoR의 매력에 흠뻑 빠져 들었다.
RoR은 다양한 framework을 제공하는데, 그 중에 관심을 제일 많이 끈 것은 DB framework인 ActiveRecord 였다.

RoR의 DB쪽 구현하는 부분의 구조를 간단하게 설명하면,
우선, DB migration을 생성한다. 개발자는 migration을 통해서 테이블 생성/삭제/변경 등을 수행할 수 있다. 생성된 DB migration을 실행하면 DB에 table이 생성되고, 간단한 model class가 생성된다.


















이와 같이 생성된 model class는 보기에는 별 것 없어 보이지만, 이 클래스가 상속받고 있는 ActiveRecord::Base 클래스를 통하여 DB에 access할 수 있는 많은 method를 제공한다. 몇 가지 예를 들어보면...



















위의 controller에서 find 메소드는, 'select * from event where id = params[:id]' 를 호출하여 해당 record를 @event 라는 인스턴스 변수에 저장하는 역할을 수행한다. 밑의 view에서는 '@event.name, @event.budget' 처럼 property에 접근하는 형식으로 record의 각 column의 값을 얻어올 수 있다.

ActiveRecord는 find 외에도 find_by_id, save, update 등의 메소드를 제공하여서, SQL을 거의 사용하지 않고서도 DB 관련 기능을 수행할 수 있도록 해 준다. 덕분에 DB관련 작업을 너무나도 쉽게 했던 기억이 있다.

iPhone 어플리케이션에서도 이러한 framework을 사용할 수 있으면 좋겠다는 생각이 들었다. SI 프로젝트와는 달리 iPhone 어플에서는 table 1~2개 정도만을 사용하는 소규모 db를 사용할 것이기 때문에, ActiveRecord의 기능 중 일부만을 구현해서 사용하는 것을 목표로 하였다. 이름은 야심차게 'iActiveRecord'로 하려다가, 왠지 조금 겸손해지고 싶은 마음이 들어서 'iRecord' 라고 바꾸었다. ^^;

이와 같은 framework(까지는 아니고...framework 비스무레한 거..ㅎㅎ) 이야말로 코드 검증 / 지속적인 refactoring이 필요할 것이므로 TDD로 개발하기에 딱 좋을것이라는 생각이 들어, OCUnit을 사용하여 개발을 시작하였다!


3. TDD 진행
우선, test 모드와 일반 mode를 설정해 줄 수 있어야 할 것 같아서. 다음과 같이 test 초기화 메소드를 작성하였다. setUp / tearDown 메소드는 테스트 실행 전/실행 후에 각각 호출되는 메소드이다.













테스트를 실행(OCUnit에서는 빌드. 이하 '실행' 이라고 표기함)시키면 에러 메시지가 뜬다. DBManager 클래스를 singleton으로 구현하고, dbManager 메소드를 통해 singleton instance에 접근하도록 하였다. 그리고 dbTestMode라는 property를 추가하여 test 모드 여부를 체크하였다. 테스트를 실행시키면, (테스트 메소드가 하나도 없긴 하지만) 빌드 성공이다. 이제 다음 단계로 넘어갈 수 있다!

다음으로, 모델 클래스에 property를 설정하고 save 하는 동작을 구현하려고 하였다. 관련 테스트를 다음과 같이 작성하였다.








property를 설정하고 save하면 DB에 insert가 되도록 하는 것이 궁극적인 구현 목표이다. 그러나 TDD에서는 일단 현재의 test를 통과하도록 하는 것이 급선무이고, 추가 구현은 그 다음에 생각해 볼 일이다. CheckListModel이라는 클래스를 만들고, 위의 테스트에 나온 property를 정의한 후에, save라는 instance method를 만든 후 다음과 같이 구현하였다.






우선 이번의 테스트는 통과하였다. 이제 다음 테스트를 작성할 차례이다.

save 메소드를 하나 더 추가하여 test를 구현하였다. test는 다음과 같다.





checkList2라는 model instance를 하나 더 생성한 후에 save하면 결과는 둘 다 YES를 반환해야 하며, save한 후에 각 instance의 key property는 적절한 값으로 설정되도록 하는 구현이다. key는 모든 model class가 가지게 될 기본 property이며, DB에서는 key 컬럼으로 쓰이며, 자동 증가하는 필드이다. (RoR의 id 컬럼에 해당한다)

이 테스트를 통과시키기 위해서는, DB와 관련된 구현을 해야만 한다. 드디어 본격적인 구현이 시작되는 순간...

우선, model class는 iRecord 라는 클래스를 상속받도록 구현하였고, model class에는 table meta data를 넣도록 하였다. iRecord framework의 model은  RoR의 migration + model 이라고 생각할 수 있다.













다음으로, iRecord 클래스에는 key property를 추가한 후, save 메소드를 다음과 같이 구현하였다.
































save method에 대하여 간단하게 설명하면, model에서 정의한 table meta data와 설정된 property의 값을 바탕으로 하여 insert sql 문을 생성하고, 그것을 db에 실행시킨 후, db로부터 last_inserted_rowid를 받아 와서 return하는 내용이다.

구현하는 시간이 조금 오래 걸렸지만, test가 통과되었다.

그 다음으로는, 전체 테이블의 record count 및 특정 조건 하에서의 record count가 필요할 것 같아서 다음과 같이 테스트를 작성하였다(클릭하면 크게 보입니다).












count 메소드는 'select count(*) from CheckList' 가 될 것이므로, record 관련 메소드가 아니라 table에 적용되는 메소드라고 생각되어 instance 메소드가 아닌 class method로 구현하였다.

save 메소드 구현 시에 db 관련 기반 private method 들을 만들어 놓은 상태이었으므로, 구현 시간은 비교적 짧았다.

다음에 작성한 테스트는 findwithID - key값으로 하나의 record를 얻어오는 메소드- 관련 테스트이다(클릭하면 크게 보입니다).









얻어온 record가 null 값인지 아닌지 체크를 한 후에, checkListAssertwithCheckList 메소드에서 각 record의 값을 검증하는 테스트이다.

다음에는 find 메소드 관련 테스트를 작성하였다. find 메소드는 findwithID와는 다르게 table에 있는 모든 record를 가져오는 메소드이다. 따라서 NSArray형을 return하도록 테스트를 만들었다(클릭하면 크게 보입니다).












결과값이 NSArray이므로 count 관련 테스트 코드도 작성하였다.

이와 같이 test 작성 - test 통과 - 다음 test 작성 ... 등의 과정을 거쳐 구현한 메소드들은 다음과 같다(클릭하면 크게 보입니다).













4. 회고

1) 좋았던 점
- test 빽(?)을 믿고 refactoring 을 과감하게 할 수 있어서 좋았음
- test 작성 시에, 결과물을 사용할 유저(여기서는 프레임웍을 사용할 개발자라고 할 수 있음)의 관점에서 생각을 할 수 있어서 좋았음
- 블로그에 글 쓰는 것은 빡세지만 재미있고 뭔가 남은 것 같은 기분이 들게 하는 작업임 ㅋㅋㅋ

2) 아쉬웠던 점
- save 메소드 구현 시에 private 메소드에 대한 TDD를 하지 않아서, 테스트를 통과하기 위한 구현 시간이 너무 길었음. private 메소드도 TDD를 해 주는 것이 어땠을까 하는 아쉬움이 남음
- 날짜 관련 쿼리를 해 오는 메소드를 구현할 때에 벌어진 일...
3가지 case가 있었는데 구현한 테스트는 2가지만을 cover 할 수 있었음. 부랴부라 나머지 case를 테스트에 추가하였는데, 소 잃고 외양간 고치는 기분 절반에 테스트의 검증력이 훨씬 강해졌다는 위안 절반...이상야릇한 기분이었음 @_@
- 블로그에 글 쓰는것은 빡센 작업임 ㅋㅋㅋ


3) 재미있었던 점
- 중간에 구조를 바꾸어야 할 일이 있었음. table meta data를 하나의 class 변수에 저장하는 방법을 썼는데 테이블을 2개 이상 사용할 때에 이전 data가 겹쳐져서 오작동을 하는 사례가 발생하였음. 그래서 NSDictionary를 사용하도록 구조를 바꾸었는데, 구조를 바꾼 후에 바꾼 구조에 대한 검증을 테스트 실행 한 번으로 끝낸 사실 자체가 너무 재미있었음 ㅋㅋㅋㅋ

Trackback Address :: http://www.dreamjr.org/tt/trackback/110

  1. 홍구 2009/07/31 02:05 댓글주소 | 수정 | 삭제 | 댓글

    더 반갑습니다 ㅋㅋ


◀ PREV : [1] : .. [10] : [11] : [12] : [13] : [14] : [15] : [16] : [17] : [18] : .. [96] : NEXT ▶