10/20 일일회고

Agile Story 2009/10/20 20:06
<배운 것>
- iPhone 어플에서, Thread에서 Animation 관련 코드를 실행하는 방법
  --> 약간 삽질했는데, Main Thread에서 호출하도록 처리해주는 메소드를 사용하면 됨.
- 새로운 framework을 사용할 때에는, 관련 소스 분석 및 기술적인 면을 많이 습득해야 한다는 것

<좋았던 것>
- 일, 스트레스에 대하여 많은 생각을 했던 것
- 안되던 것을 잘 되도록 하여 프로그램이 원하는 대로 돌도록 한 것
- 팀장이 약간 의외의 일을 시킨 경우에, 바로 괴로워(?)하지 않고 입장을 바꿔 생각해 보며 스트레스를 조절하려는 시도를 한 것.

<나빴던 것>
- Android 어플 설계작업을 하고 있었는데, 팀장이 설계보다는 기술적인 내용 습득에 더욱 매진하라고 한 것. 설계에 한창 물이 올랐는데...
- 시간을 너무 비효율적으로 사용하였음.
- 여전히 스트레스 제어는 쉽지 않다. 오전에는 그럭 저럭 괜찮았는데, 오후를 지나 저녁이 가까워 지면서 몸이 피곤해 진 탓인지 잘 제어가 되지 않는다.

<시사점>
- 팀장이 설계보다는 기술적인 내용 습득을 지시하여 약간 삐졌(?)지만 iPhone 어플을 개발하면서 느낀 것은, 새로운 프레임웍을 다룰 때 기술적인 내용을 많이 습득해 두면 둘수록 개발 시간을 절약할 수 있다는 것이다. 팀장 말이 틀린 것은 아니다.

- 일은 스트레스를 주는 그 무엇이 절대 아니다! 모든 일에 대해 '바로 지금' 해야 한다거나 '꼭 해야' 한다는 강박관념을 덜어내면 훨씬 좋을 것이다.
  또한 일에 대한 인식을 바꿀 필요가 있다. 일은 하나님이 주신 것이고, 괴로운 것이 절대 아니라는 생각을!!

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를 사용하도록 구조를 바꾸었는데, 구조를 바꾼 후에 바꾼 구조에 대한 검증을 테스트 실행 한 번으로 끝낸 사실 자체가 너무 재미있었음 ㅋㅋㅋㅋ

OCUnit의 대안

iPhone 2009/06/28 22:00
http://code.google.com/p/google-toolbox-for-mac/wiki/iPhoneUnitTesting

OCUnit이 마음에 들지 않는 사람은,
위의 링크에 들어가 보면 OCUnit과 비스무레한 Unit test용 framework이 있으니
다운받아 설치하여 사용하면 된다.

Unit Test를 돌리다 보면 테스트 통과여부를 아는 것 만으로는 부족할 때가 있다.

테스트가 fail이 되었는데 도대체 왜 그랬는지 도통 감이 오지 않을 때에는,
디버깅을 해 보는 것이 많은 도움이 된다.

OCUnit에서 디버깅을 하려면, 다음과 같이 설정해 주면 된다.

1. Project - New Custom Executable을 클릭한다.






















2. 다음과 같이 입력하고 Finish 버튼을 누르면 'otest' 라는 이름의 Executable이 하나 만들어 진다.























3. 새로 만든 Executable 'otest' 의 Info 창을 띄우고 다음과 같이 Arguments와 Variables 를 설정한다. 2번째 Argument인 "Unit Test.octest" 에서 "Unit Test" 는 앞서 추가한 UnitTest bundle의 이름이다. Xcode 최신 버젼(3.1.2)에서는 세 번째 variable인 OBJC_DISABLE_GC를 YES로 설정해 주어야 한다.






































4. Active Executable을 방금 추가한 otest로 바꾼다.











5. break point를 설정하고, Debug 버튼을 눌러 마음껏 디버깅을 해 보자!



















P.S)
1. 참고사이트
http://beatworm.co.uk/blog/computers/tracing-unit-tests-with-the-xcode-3-debugger/
http://chanson.livejournal.com/119578.html

2. iPhone OS 2.2 / 2.2.1 에서는 몇몇 Framework( ex)UIKit.framework )를 추가하면 디버깅이 되지 않는다. 다행히도, Foundation.framework은 잘 돌아간다. UIKit을 사용하는 부분을 테스트 할 일은 그리 많지 않을 것이니 사용하는 데 큰 지장은 없을 것이다(하지만 찝찝한 건 어쩔 수 없다...) iPhone OS 3.0에서는 이 문제가 해결되었기를...

OCUnit은 Cocoa/Cocoa touch용 유닛 테스트 도구이다.
unit test용 bundle을 추가한 후, test 메소드를 몇 개 추가하여 간단하게 유닛테스트를 수행할 수 있다.

Cocoa에서는 간단하게 사용할 수 있으나 Cocoa touch(iPhone OS 2.2 / 2.2.1)에서는 제대로 동작하지 않아서 설정값 몇 개를 변경해 주어야 한다. iPhone OS 3.0에서는 제대로 동작하기를...

OCUnit을 사용하는 법은 다음과 같다.

1. 새로운 target을 추가한다.













2. Cocoa / Unit Test Bundle 을 선택하여 추가한다. 적절한 Target Name을 입력한 후 Finish 버튼을 누르면 Unit Test Bundle이 추가된다.






















3. 새로 추가된 Unit Test Bundle의 Info 창을 연다.(iPhone OS 2.2 / 2.2.1(이하 iPhone OS)관련 내용)













4. Build tab에서, User-Defined Settings’ 과 ‘Other Linker Flags' 항목을 삭제한다.(iPhone OS 관련 내용)



































5. General 탭의 'Linked Library' 란에 다음 경로의 프레임웍을 추가한다: /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator2.2.sdk/Developer/Library/Frameworks/SenTestingKit.framework  (iPhone OS 관련내용)



















6. 기존 Application target의 Info 창을 연 후에, 'Direct dependencies' 란에 생성한 Unit test bundle을 추가한다. Application 빌드 시에 Unit Test bundle도 같이 빌드되도록 하여 , test bundle을 빌드하기 위해 Active Target을 바꿀 필요가 없도록 하기 위한 작업!




















7. Unit Test target에 다음과 같이 필요한 framework을 추가한다. Foundation.framework은 기본적으로 추가하는 것이 좋다.

















8. Unit Test target에 다음과 같이 Objective-C test class 를 추가한다.










































9. 추가한 testcase class(여기서는 UnitTest.m)를 열고 다음과 같이 testSample 메소드를 추가한 후 빌드한다. 모든 유닛 테스트 메소드(testSample과 같은)가 성공한 경우에는  'Build succeeded' 메시지가 나온다. OCUnit에서는 테스트 케이스 실행 = 빌드 인 것이다.

10. test가 실패한 경우에는 다음과 같이 에러메시지가 뜬다. 유닛 테스트 실패 = 빌드 에러 인 것이다. 빌드만으로 유닛 테스트를 실행시킬 수 있어서 킹왕짱 편리하다.


















11. test case에 다음과 같이 추가하여 시뮬레이터 상에서만 테스트가 실행되도록 한다.




















p.s)
1. 참고 URL
http://www.sente.ch/s/?p=535&lang=en

2. OCUnit 관련 정보. 사용할 수 있는 assert 목록 등의 유용한 정보가 들어 있다.
http://developer.apple.com/mac/articles/tools/unittestingwithxcode3.html