Presenter First
Presenter First: TDD for Large, Complex Applications with Graphical User Interfaces
Michael Marsiglia, Brian Harleton, Carl Erickson
번역: Brann Park
원문: https://atomicobject.com/uploadedImages/archive/files/PF_March2005.pdf
Motivation
GUI를 다루는것은 힘들다. 처음에는 매우 단순했던 것들이 어떻게 이리도 빠르게 거대해지고 복잡해지는걸까? 디자인에 작은 변화를 가하는 것이 왜 어려운걸까? 우리는 어쩌다가 구현할 기능도 확정되지 않은 순간에 개발공수가 큰 코드를 계속해서 작성하는 함정에 빠지게 된 걸까? 어쩌다가 기능 중심의 개발이라는 애초의 목표에서 어긋나게 된걸까? 스트레스와 고통을 덜어줄 유닛 테스트들을 만드는것이 어째서 어려운걸까? 커스텀 다이얼로그에 작은 변화를 주는것에 일주일의 시간이 든다는 말을 어떻게 고객에게 이해시킬 수 있을까?
그 첫 단계는 비지니스 로직과 인터페이스를 분리하는 것이다. MVP(Model-View-Presenter) 패턴을 사용하여, Model을 Presenter와 View로부터 격리하였다. 이를 이용하여 TDD 원칙을 따라 비지니스 로직(model)을 유닛 테스트 할 수 있다. 하지만 이 방식은 어플리케이션의 플로우 코드나, 기능 코드들을 GUI에 남겨두었기에 충분하지 않다. 왜냐하면 시스템 자동화 수준의 테스트 프레임워크를 이용하여 GUI 기능을 테스트 하는 것이 가능하지만, 우리의 경험상 이러한 테스트들을 만들고 유지보수 하는데에는 높은 비용이 발생하기 때문이다.
Michael Feathers의 Humble Dialog Box는 사용자 그래픽 인터페이스를 가진 어플리케이션의 View로부터 모든 기능과 로직을 제거함으로써 테스트성을 향상시켰다. Smart Object 라는 것을 만들어, Presenter를 View에서 Model로 이동시켰다. 그러나 이런 Smart Object를 만들고 관리하는 것은 사용자 인터페이스의 플로우와 로직이 어플리케이션의 비지니스 로직과 결합된다는 점에서 이상적이지 못하다. 어플리케이션 플로우에 대한 모든 변화는 직접적으로 비지니스 로직에 영향을 미치고 반대로 비지니스 로직의 변화 또한 어플리케이션 플로우에 영향을 끼치게 된다. 이러한 커플링의 문제는 요구사항의 변화에 대응하는것을 어렵게 만든다.
비용 효율적인 관점에서 우리는, 사용자 우선순위 기반의, 스토리 기반의, 테스트 주도의 개발이라는 Extreme Programming practice를 따르기를 원한다. 이를 위해선 개발 프로세스 전반에 걸쳐 TDD의 도입이 필요하다. 우리는 깔끔하고(clean), 믿을만 하며(robust), 비결합적이며(decoupled), 완전히 테스트된(tested) 코드를 작성할 수 있어야한다. 우리는 요구사항의 변화에 쉽게 대응 할 수 있는 개발 프로세스와 코드를 원한다.
Presenter First는 GUI를 가진 복잡한 어플리케이션과 패키지들을 만들어내는 문제를 다룬다. 또한 끊임없이 변화하는 인터페이스로부터 개발을 분리시키고 기능의 문제에 집중하도록 해준다. 사용자 스토리들은 일반적으로 기능 중심적이기 때문에, Present First는 우리가 버튼, 슬라이더, 체크박스 따위가 아닌, 기능의 관점에서 생각할 수 있게끔 도와준다. 또한 테스트된, 유지보수 가능한 코드를 만들어내고, 어플리케이션 기능의 구현 및 테스트를 위한 프로세스를 제공해준다.
Presenter First
Presenter First는 변형된 MVP 패턴과 프로세스의 결합이다. 프로세스란 어플리케이션이 MVP를 이용하여 어떻게 만들어지고 테스트되는지를 나타낸다. 이러한 결합의 결과, TDD의 효과가 고객이 이야기하는 기능 요건들에까지 이를 수 있게 된다.
시스템이 실제 운영 환경에서 사용될 준비가 되었는지 최종적으로 확인하는 단계인 인수 테스트는, 고객의 관점에서 시스템의 기능을 검증한다. 고객의 관점은 어플리케이션의 인터페이스를 포함하기 때문에, 인수 테스트의 자동화는 곧 인터페이스 테스트의 자동화를 요구한다. 이는 복잡하고 비싸며, 특히 인터페이스가 그래픽 요소를 가진다면 더더욱 그러하다. Extreme Programming 커뮤니티에서 흔히 이야기되는 한가지 대처방안은, GUI를 가능한 한 얇은 레이어 구조로 만들고 그 바로 아래에서 테스트하라는 것이다. Presenter First는 이러한 개념의 구체적인 실현을 제공한다.
MVP 패턴은 Taligent에 의해 처음 알려지고, Dolphin에 의해 널리 쓰이고, 가장 최근에는 Fowler 에 의해 소개되어진 SmallTalk의 전통적 MVC 패턴으로 부터 유래되었다. MVP의 의도는 MVC처럼 프리젠테이션에서 비지니스 로직을 분리시키데 그치지 않고, 더 나아가 행동을 분리시키는데에 있다.
Model 어플리케이션의 비지니스 로직과 데이터. 고객에게 보이지않으며 관련되어있지 않다.
View 어플리케이션의 인터페이스. 고객에게 있어서는, View가 곧 어플리케이션이다. 고객 스토리는 View에 무언가를 하고 View에서 결과를 보는 관점에서의 기능성을 대해 설명한다. 그러므로 View는 프로젝트 전반에 걸쳐 가장 높은 변화율을 가지게 된다. 이러한 변화들로부터 디커플링 시키는 것이 Presenter First의 가장 주요한 목적이다.
Presenter Presenter는 Model과 View가 서로 상호작용할 수 있도록 허용해주는 커스텀 로직이다. 이것은 MVP 각각의 요소들의 기능 또는 흐름을 나타낸다. 고객 스토리들이나 요구사항들은 Presenter내의 기능과 대응된다.
Presenter First에서 사용되는 MVP의 변형은 MVP 각각의 요소들 내에서의 커뮤니케이션 패턴들에 영향을 미친다. 본래의 MVP 패턴은 Model이 직접적으로 View 와 커뮤니케이션하는 것을 허용한다. 그러나 Presenter First는 모든 커뮤니케이션이 Presenter를 통해서 이뤄지는 것을 요구한다. 이런 제약은 Model과 View의 디커플링을 보장한다. 그리고 MVP 각각의 요소들에 대한 테스트성을 크게 증대시킨다. [그림 1]은 MVP와 Presenter First에서의 커뮤니케이션 패턴을 대조하여 보여준다.
Presenter First의 프로세스 측면은 시간의 흐름에 따라 개발이 어떻게 이뤄지는지, 그리고 개발과정에서 어플리케이션이 어떻게 테스트되는가라는 점을 다룬다. 모든 어플리케이션 개발에서 가장 먼저 마주하게 되는 궁금증은, 어디에서부터 개발을 시작해야하는가 이다. MVP에 기반한 어플리케이션에서는 다음과 같은 세가지 답변이 가능하다.
Model에서 시작하는 것은 전통적인 소프트웨어 개발의 “infrastructure first” 접근의 형태이다. 이 접근에는, 무엇이 필요한지 확실히 인지하지 못한 채 Model을 만든다라는 점과, 초기 개발 과정에서 개발의 초점을 사용자에게 보이지 않는 것들에 대해 맞춘다는 등의 단점들을 가진다. Model은 테스트하기 쉬워야 하며, 테스트 주도 방식으로 개발하기 용이해야 한다. 고객의 기능 요청에 의해 Model에 대한 요구조건들이 명확해질 때까지 작업을 연기하는 것이 핵심이다.
View에서 시작하는 것은 고객 우선순위 기능 주도 개발 프로세스에서 MVP를 사용할 때 꽤나 합리적인듯이 보인다. 이 접근의 논리는 매력적이다. 고객 스토리들은 View에서 일어나는 액션들과 View에서 보여지는 결과, 그들이 어플리케이션을 사용하는데 있어 어떤 인터페이스들이 필요한지에 대한 피드백, 그리고 최소의 Model(infrastructure)의 중요성을 이야기한다. 하지만 View First는 쉽게 만들어지지만, 불행히도 쉽게 값비싼 실수를 야기하기도 한다.
위에서 이야기했듯이 View First 개발은 몇가지 문제점들을 가지고 있다. View는 고객에게 있어 어떤 확고한 생각을 가지게 하고, 세부사항의 결정에 대한 망설임들을 만들어 결국 요구사항의 높은 변경률들을 불러일으키는데 쉬운 요소다. 경험적으로, 사용자 스토리들은 거의 대부분 인터페이스에 대한 자세한 명세를 하지 않는다. 그들은 좀 더 일반적인 어플리케이션 기능들에 대해서 이야기한다. 이렇게 남겨진 모호성은 고객이 만들어진 인터페이스를 반려하도록 함으로써 인터페이스의 개발에만 수시간을 써버리게 될 수도 있다. 뿐만 아니라, View에 초점을 맞추게 되면, View를 비지니스 로직으로 두텁게 만들어 버리는 위험성을 증대시킬 수 있다. 끝으로 View 인터페이스 테스트의 어려움은 테스트 주도 개발의 올바른 개발 사이클을 허물어 버린다.
우리의 경험상, 최고의 대안은 Presenter First이다. Presenter에서 시작하고 그 주위에서 개발을 구성해나감으로써, 테스트 주도 개발 실행을 따라 사용자 스토리로부터 어플리케이션이 만들수 있다. Presenter에서의 유닛 테스트들은 작성하고 유지하는데 경제적이며, 특정 인터페이스 요소들에 묶이지 않은 채 어플리케이션 기능의 정상 동작을 확인한다. Presenter First로 개발함으로써, 개발자의 관심을 고객 기능에 두면서도, 동시에 Model과 View에 대한 최소 완전한 명세들이 만들어지게 된다.
이는 MVP의 하나의 구현 옵션이지만, Model과 View 모두의 인터페이스를 필요로 한다. Presenter와의 커뮤니케이션은 Model과 View 모두 Presenter로 부터 디커플링 된 채 이벤트 서브시스템을 통해서 이뤄진다. [그림2]는 Presenter First에서의 클래스들의 관계를 보여준다.
How it works
Presenter First 의 강점은 접근방식의 단순하고 구체적인 모습이다. 개발은 다음과 같이 진행된다.
- 생성자에서 Model 인터페이스와 View 인터페이스를 취하는 Stub Presenter 클래스를 만든다.
- Model, View의 인터페이스를 만족시키는 Mock 테스트 객체들을 만든다.
- 모든 사용자 스토리들에 대해서
- 3.1 우선순위화 된 사용자 스토리를 선택한다.
- 3.2 스토리의 View에 대한 영향력을 분석한다.
- 3.3 스토리에 대한 View 인터페이스를 추가한다.
- 3.4 스토리의 Model에 대한 영향력을 분석한다.
- 3.5 스토리에 대한 Model 인터페이스를 추가한다.
- 3.6 Mock 객체들에서, 호출되고 데이터들을 리턴 하는 인터페이스 메서드들을 구현한다.
- 3.7 끊어낼 수 있는 모든 요소들에 대해서(TDD 반복)
- 3.7.1 Do for each test
- 3.7.1.1 View 또는 Model에 대한 액션 또는 이벤트를 통해 앱을 활동시키는 Presenter에 대한 테스트를 작성하라.
- 3.7.1.2 Model의 상태와 View의 상태에 대한 Assertion을 만들어라.
- 3.7.1.3 Presenter 내의 private method들을 구현하라.
- 3.8 이 스토리의 View 인터페이스를 만족하는 최소의 사용자 인터페이스 구현체를 만들어라.
Presenter First 프로세스의 바깥쪽 루프는 Model과 View 인터페이스에 빈번한 리팩토링을 요구한다. 그렇지만 이 시점에서는 인터페이스들은 Mock 오브젝트만을 가지고 있기에 리팩토링에 많은 비용이 발생하지 않는다.
Presenter First의 한가지 아주 훌륭한 점은, 모든 현재의 고객 요구사항들이 Presenter에 코드화 되었을 때, Model과 View의 인터페이스들은 각각 최소 완전한 명세서가 된다는 것이다. 이 지점으로 부터 어플리케이션을 완성해낸다라는것은, 표준 TDD 실행에 따라 Model을 “구현"하고, View를 고객의 요구와 View의 인터페이스를 총족시키도록 “구현"해 내는 것이 된다.
고객으로부터 피드백을 얻기 위해, 우리는 일반적으로 최소의 시간과 노력으로 매우 단순한 사용자 인터페이스를 만든다. 고객은 아마도 그런 인터페이스가 유능한 전문가에 의해 새롭게 디자인되어야 하는지, 마케팅적으로 디자인 되어야 하는지, 혹은 이미 목적에 맞게 충분히 괜찮은지를 판단할 것이다. 그러는 동안에, 어플리케이션의 기능들이 테스트되고, 인터페이스 코드에는 적은 수고를 필요로하며, 새로운 인터페이스들은 View 인터페이스를 충족시키는 순간에 손쉽게 개발될 것이다.
실제의 View를 테스트하는 것은 툴과 예산이 허용된다면 자동화될 수 있을것이고, 그렇지 않다면 최종적으로 수동 시스템 테스트 단계로 남겨질 것이다. View는 매우 얇게 구성되어있으며, 적절히 분류된 이벤트들을 Presenter가 처리하도록 호출하는 메소드 이외의 몇몇 메서드들을 가지는 형태이다. GUI 위젯이 동작하는(버튼클릭과 같은) 내부를 생각해보자. 그 View에 대한 유닛 테스트는 주로 스크린내에 위젯이 올바르게 놓여있는지, 적절한 이벤트 타입을 발생시키도록 프로그래밍 되어있는지를 확인하는 것이다.
Presenter는 비상태성이고 public 메서드가 존재하지 않도록 의도되었다. Presenter의 유닛 테스트들은 어플리케이션의 “wiring"이 Presenter의 private 메서드들이 표현하는대로 적절히 구현되었는지를 명시적으로 테스트한다. 어플리케이션의 기능은 View와 Model의 사이에서 Presenter에 의해 조정된다. 이는 어플리케이션 행동에 대한 Presenter의 유닛 테스트 묶음을 만들고, 응집력(cohesive)있고 집중화된(centralized) 문서 소스를 만든다. 다시말해서 사실상 Presenter와 Presenter의 테스트들은 어플리케이션에 대한 실재하는, 실행가능한 명세서이다. 어플리케이션 요구사항들의 변화와 추가는 보통 Presenter에 의해 단독으로 처리(사용자 인터페이스의 변경이 아닐때)될 수 있다는게 우리의 경험이다. Presenter는 철저하게 유닛 테스트 커버리지를 가지므로, 이런 작업은 고객의 관점에서 나오는 어플리케이션의 단순 변화, 추가 작업들을 지원 하기 위한 개발자의 수고를 단순하게 만들어서 빠르고 자신감있는 처리가 될 수 있게 한다.
Presenter와의 커뮤니케이션은 Model과 View를 Presenter에 느슨하게 연결하기 위한 이벤트 서브시스템을 사용함으로써 가능해진다. 가장 흔한 케이스는, Model 트리거 이벤트 또한 가능하겠지만, View의 이벤트를 Presenter가 소비하도록 발생시키는 것이다. View가 Presenter와의 커뮤니케이션에 이벤트를 사용하는것은 View의 로직을 매우 얇게 유지시킨다. View는 모든 이벤트 처리를 Presenter에 위임시킨다. 이런 디자인은 View 자체의 행동성에 대해 테스트할 것이 거의 없는 “얇은 GUI"를 만들어낸다. Presenter와의 커뮤니케이션에 이벤트를 사용하는 것은, 컴포넌트의 패키지를 분리하고, 컴파일 의존성들을 줄이고, 동일한 View가 다른 행동성을 가진 Presenter들에 연결되어질 수 있도록 한다.
얇은 View의 결과, View 인터페이스는 Primitive 타입만을 파라미터로 취한다. 복잡한 타입들을 View에 전달하는 것은 View에서 Processing을 야기하고 View 층을 두텁게 하고, 유닛 테스트가 필요하도록 만든다.
Example use of Presenter First
X-Rite는 색상 측정 솔루션의 글로벌 선두 업체이다. X-Rite 시스템들은 색상 데이터에 대한 검증과 커뮤니케이션을 위한 하드웨어, 소프트웨어, 그리고 서비스들로 구성되어있다. Atomic Object는 프린팅 출력기의 프린팅 품질을 관리하는 크고, 복잡한 소프트웨어 시스템을 디자인하기 위해 X-Rite와 함께 일했다. 이 시스템은 대략 45개의 MVP 삼요소들과 8개의 독립적인 어플리케이션들로 구성된다. [Appendix 1]은 이 어플리케이션의 스크린샷을 보여준다.
Presenter First의 사용은 출력기 품질을 컨트롤하기 위해 개발된 어플리케이션 패키지의 복잡도를 관리하는것을 도왔다. 기능의 변경, 추가, 삭제에 대응하는 것은 아무런 문제가 없었고, 관련없는 코드 베이스에도 영향을 끼치지 않았다. 이 프로젝트에서 MVP 삼요소 중 한가지 예는 사용자들을 그들의 제품에 회원등록시키도록 하는 방법이었다. 우리는 다양한 스크린 해상도지원 뿐 아니라 사용자가 등록할 수 있는 다양한 수단을 지원해야만 했다. Presenter First를 이용하여 이 작업은 매우 쉽게 구현되고 테스트되었다.
[Figure 3]은 회원등록 예제를 테스트들과 함께 이에 대한 관계들을 설명하는 클래스 다이어그램을 보여준다. Presenter First를 이용하여 회원등록 기능을 만들어내는 것을 입증하는 샘플코드는 [Appendix 2]에서 보여준다. RegPresenter라는 Presenter의 구현체는 보이지 않는것에 주목해라. 두가지 테스트(test_registration_Fail과 test_Registration_Pass)는 사용자가 어플레이케이션에 등록할 때 RegPresenter가 발생시키는 같은 이벤트들이 RegPresenter의 구현체를 테스트하기 위해 어떻게 사용되는지를 보여준다. Presenter내에서 회원등록 로직들이 적절하게 wire 되었는지를 확인하기 위해 Mock View와 Mock Model에서 Assertion들이 작성된다.
Presenter First를 사용함으로써 고객의 추가 요청이나 변화는 개발자에게 더이상 골칫거리로 남겨지지 않게된다. 예를들면, 고객이 그저 엔터키를 누르는 대신 그래픽의 푸쉬 버튼을 클릭하여 회원등록을 전송하기로 결정했다면, 그 변화는 Presenter 혹은 Model 어디에도 영향을 끼치지 않은 채 View를 변경하는 것만으로 처리될 수 있을 것이다. 만약 고객이 회원등록 코드에 대한 검증을 데이터베이스 검색에서 웹서비스를 통한 검증으로 변경하기로 했다면, 그 결과는 Presenter나 View 가 아닌 Model에서의 변화로 나타날 것이다.
Benefits of Presenter First
Presenter First는 기능에 초점을 유지시키고 사용자 우선순위의 기능을 전달하는데 도움을 준다. 개발자들은 특징들(model과 View)의 디테일들에 집중하는데에 더 적은 시간을 쓰게되며, 기능성(Presenter)에 더 많은 시간을 쓰게 될 것이다. 개발자는 초기 개발단계에서 디테일들로 인해 너무 빠르게 산만해지는 대신, 높은 수준의 추상화 단계에서 작업을 할 수 있다.
기능성은 Presenter First로 쉽게 테스트 될 수 있다. Model과 View의 인터페이스들은 쉽게 Mock 될 수 있기에 Presenter 클래스 안의 기능 레벨에서 테스트들의 초점을 둔다. Mock된 View와 Model 컴포넌트들은 개발자들에게 View와 Model의 모든 측면에 대한 완전한 제어를 주고, Presenter 클래스 내의 모든 기능 케이스들에 대해 쉽게 테스트할 수 있는 자유를 준다.
사용자 인터페이스의 모든 기능성의 유닛테스트를 만들어내는 능력을 향상시키는 것 뿐 아니라, View와 Model을 나타내는 Mock 클래스들을 만들어내는것은 추가적인 이득 또한 제공한다. Model 인터페이스를 Mock 오브젝트화 함으로써, 개발자는 라이브 데이터베이스에 연결하는 것, 커뮤니케이션 디바이스들을 사용하게끔 하는것, 파일을 다루게 하는것과 같은 문제를 피한다. 그러한 타입의 기능들을 테스트 하는 책임은 실제 Model의 다른 클래스들에 놓여진다. Presenter First는 이런 기능들이 실제로 어떻게 구현되었는가 하는 디테일이 아닌, 단지 사용자 인터페이스를 통한 기능 접근을 테스트하는것을 중시한다.
View를 Mock 하는것 또한 장점을 가진다. 실제의 사용자 인터페이스 컨트롤과 폼들은 인스턴스화 되어질 필요가 없기 때문에 유닛 테스트 패키지는 이러한 비싼 연산들로 인해 성능이 저하되지 않는다. 유닛 테스트들이 실행하기에 느려서 고통스럽다면, 개발자들은 테스트를 덜 실행하고자 할 것이고, 따라서 개발팀에게서 테스트의 가치는 더욱 떨어지게 될 것이다.
사용자 인터페이스를 관리하는것은 Presenter First를 사용함으로써 매우 효율적이게 된다. Presenter 클래스들에 대한 유닛 테스트들은 사용자 인터페이스에 대한 살아있는 명세서가 될 것이다. Presenter 클래스의 유닛 테스트를 설명하는 것으로 개발자들은 쉽사리 사용자 인터페이스의 행동과 룰을 알아낼 수 있다.
기능성은 Presenter 클래스의 유닛 테스트들에 의해 쉽게 관리될 수 있다. 기능이 추가 또는 삭제가 될 때, Presenter 클래스의 유닛 테스트는 모든게 정상적으로 동작한다는 자신감을 제공해준다. 테스트 주도 개발은 높은 수준의 시스템 기능성까지 포함하도록 발전되어왔다.
비지니스 로직으로부터 사용자 인터페이스를 디커플링 시키는 것이 우리가 따라야만 하는 중요한 행동이라고 충분히 이해를 했을지라도, 대부분의 개발자들은 부적절하게 커플링 된 코드를 작성하고 있는 자신을 발견한다. Presenter First는 비지니스 로직과 사용자 인터페이스의 디커플링을 강제한다. 철저하게 디커플링 하는 것은 개발자 코드의 유연성을 증대시키고 그들이 요구사항의 변화에 따른 고통을 최소화 하도록 한다.
많은 고객들이 특정한 Look and Feel을 요청한다면, 이는 단순히 그래픽 요소들을 변경하여 처리할 수 있다. 또는 마케팅이 제품이 Palm Pilot을 반드시 지원해야 한다고 결정할지도 모른다. 사용자 인터페이스 관점에서 이제 이런 변화는 쉽게 처리된다. 단순히 그래픽 컴포넌트들을 같은 View 인터페이스를 구현하는 Palm Pilot의 컴포넌트들로 교체하면 사용자 인터페이스의 변경이 완료된다. 단순히 다른 패키지를 인스톨하는 것으로 지원하는 플랫폼이나 Look and Feel을 완전히 변경하는 용이함과 강력함을 상상해보자. 이러한 변경들은, 그래픽 컴포넌트들을 변경하는것이 기능적인 어떠한것도 변화시키지 않고, 따라서 다른 어떤 부분들에 대해 살펴볼 필요가 없다는 확신을 가지게 해 준다.
Presenter First의 컴포넌트들의 디커플링을 강제함으로써, MVP 중 한 컴포넌트를 교체하는일은 사소한 일이 되고, 다른 컴포넌트들에 영향을 끼치지 않게 된다. View 컴포넌트가 View 인터페이스를 구현하는 한, 사용할 사용자 그래픽 인터페이스를 변경하는 것은 더이상 어렵지 않다. 다른 사용자 그래픽 인터페이스들 또한 쉽게 교체 되어질 수 있다. 이제는 실제로, 사용자 그래픽 인터페이스가 인스톨 또는 실행 시점에 결정될 수 있다. 같은 점에서 Model 컴포넌트도 Presenter나 View에 영향을 끼치지 않고 수정되거나 완전히 교체될 수 있다. 파일로 부터 정보를 액세스하는 오늘날의 사용자 인터페이스는, Presenter나 View에 영향없이 데이터베이스를 통해 엑세스 하는 내일의 인터페이스로 쉽게 만들어질 수 있다. 우리는 심지어 어떤 케이스에서는 같은 View에 다른 Presenter를 사용했다. 사용자 그래픽 View는 같다. 그러나 행동(Presenter)이 다르다.
Presenter First는 유연하다. MVP 패턴으로 복잡한 어플리케이션들은 효율적으로 수많은 작은 어플리케이션들이 묶인 구성이 된다. 각각의 미니 어플레이케이션들은 그 자체로 MVP 트라이어드 하다. 이런 미니 어플리케이션들은 관리가 용이하고, 전적으로 다른 어플레이케이션들에 디커플링되어있다. 구조적으로, 각 부분들이 독립적으로 만들어질 수 있고, 의존성에 대한 두려움없이 나중에 합쳐 질 수 있기에, 이는 프로젝트의 관리성을 증가시킨다. MVP 트라이어드를 구성하고 만들어내는 패턴들은 중첩된 트라이어드들의 테스트성을 유지하는데 도움을 준다.
Presenter First는 팀에 있어서 비용 효율적이고 실용적이다. 개발은 streamlined이고 관리가능하다. Presenter의 부분은 View와 Model이 시작되게 할 필요 없이 홀로 완료되게 둠으로써 온전히 테스트 되고 끝마쳐질 수 있다. 각자가 그들의 계약(Model, View 인테퍼이스)의 끝을 지키는 한, 각 팀들은 서로에 독립적으로 독립적인 속도와 타임라인으로 코드를 만들어 낼 수 있게 된다.
Presenter First를 사용함으로써, 그래픽 View는 적절한 툴과 그래픽 디자이너들 또는 그래픽 View를 만들어 내도록 훈련될 수 있는 사용성 전문가들과 함께 매우 단순해지고 얇아지게된다. 개발자들은 더이상 사용자 그래픽 인터페이스를 구현하기 위해 많은 시간을 낭비하지 않아도 된다. 개발자들은 일반적으로 그래픽 사용자 인터페이스를 구현해내는데 있어 충분한 역량을 가지지 않기에 엉성한 결과물을 만드는게 많은 시간을 쏟게 된다. Presenter First는 개발자들이 가장 잘할 수 있는 프로그램 같은것을 하게 해주고, 사용가능한 인터페이스를 만드는 것과 같은 훈련되지 않은 것들에 대해 시간을 소비하는것을 피하게 해준다.
Appendix 2. Sample code showing model and view interfaces, mock test classes, and test methods.
public delegate void RegSubmitted();
public interface IregView {
void AddRegSubmittedEvent(RegSubmitted ev);
string GetSubmittedData();
void SetRegistrationStatus(bool bSuccess);
}
public interface IregModel {
bool Register(string strRegistration);
}
public class RegViewMock : IregView {
private RegSubmitted regSubmittedEvent;
public void FireRegSubmittedEvent() {
RegSubmitted evtCopy = regSubmittedEvent;
if (evtCopy != null) {
evtCopy();
}
}
public void AddRegSubmittedEvent(RegSubmitted ev) {
regSubmittedEvent += ev;
}
public string GetSubmittedData() {
return "ThisIsMyRegistration";
}
public bool bRegistrationStatus;
public void SetRegistrationStatus(bool bSuccess) {
bRegistrationStatus = bSuccess;
}
}
public class RegModelMock : IregModel {
public bool regSuccess;
public string regInput;
public bool Register(string strRegistration) {
regInput = strRegistration;
return regSuccess;
}
}
public void test_Registration_Pass() {
/* force the registration to succeed */
modelMock.regSuccess = true;
/* Simulates the user submits event by firing an event
* that the presenter should handle.
*/
viewMock.FireRegSubmittedEvent();
/* Now just verify that what the user entered as the
* registration code is what the model actually uses
* as input.
*/
Assert.AreEqual("ThisIsMyRegistration", modelMock.regInput);
/* Verify that the view has been updated to reflect
* a successful registration.
*/
Assert.IsTrue(viewMock.bRegistrationStatus);
}
public void test_Registration_Fail() {
/* force registration process to fail */
modelMock.regSuccess = false;
viewMock.FireRegSubmittedEvent();
Assert.AreEqual("ThisIsMyRegistration", modelMock.regInput);
/* verifies that the view was updated to reflect
* an unsuccessful registration.
*/
Assert.IsFalse(viewMock.bRegistrationStatus);
}