Model View Presenter 패턴은
MVC(Model View Controller) 패턴을 기반으로 하는 아키텍처 패턴으로 관심사의 분리를 높이고 단위 테스트를 용이하게 합니다.
MVC 패턴에서 View와 Model의 의존성을 없애고 단위 테스트가 어려웠던 문제점을 해결하기 위해 등장하게된 패턴이라고 할 수 있다.
<예제>
1. Contract interface 생성
Presenter와 View 사이에 어떤 기능이 있는지 한눈에 파악할 수 있도록 명시하는 역할.
( Contract는 MVP의 필수요소는 아닙니다. )
interface MainContract {
interface View : BaseView<Presenter> {
fun showProgress(isShow: Boolean)
fun setData(str: String)
}
interface Presenter : BasePresenter
}
interface BasePresenter {
fun start()
}
interface BaseView<T> {
var presenter: T
}
2. Presenter class 생성
Presenter class에서는 Contract의 Presenter interface를 구현하고,
Model(여기서는 repository) 과 View를 생성자 매개변수로 받습니다.
class MainPresenter(
val mainRepository: MainRepository,
val mainContractView: MainContract.View
) : MainContract.Presenter {
init {
mainContractView.presenter = this
}
override fun start() {
mainContractView.showProgress(false)
val data = mainRepository.getData()
mainContractView.setData(data)
mainContractView.showProgress(true)
}
}
3. Model class 생성
Model은 일반적으로 server 또는 local database에서 데이터를 가져오지만 예제이기에 간단하게 작성하였습니다.
여기선 Repository를 Model이라고 했지만 Repository pattern에 따르면
Repository는 여러개의 datasource(local, remote, database) 에서 필요한 데이터를 선택해 가져오는
Presenter와 Model 사이의 중개자 역할입니다.
또 한가지 object 클래스로 작성했는데 Repository는 singleton 이어야 합니다.
object MainRepository {
fun getData() = "Hello World"
}
Activity에서 어떻게 사용하는지 살펴보겠습니다!
class MainActivity : AppCompatActivity(), MainContract.View {
private val textView: TextView by lazy {
findViewById(R.id.textView)
}
private val button: Button by lazy {
findViewById(R.id.button)
}
override fun showProgress(isShow: Boolean) {
textView.isVisible = isShow
}
override fun setData(str: String) {
textView.text = str
}
override lateinit var presenter: MainContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MainPresenter(MainRepository, this)
button.setOnClickListener {
presenter.start()
}
}
}
동작 과정은 이렇습니다.
- MainContract.View 를 구현해 persenter와 showProgress(), setData() 함수를 override 해줍니다.
- MainPresenter 클래스를 인스턴스화 합니다. 생성자 매개변수로 Model과 현재 Activity에 MainPresenter.View를 구현했기때문에 this를 넣어줍니다.
- button 클릭시 Presenter의 start() 함수를 호출합니다.
- Presenter는 View로 부터 이벤트를 전달받고 Model에서 데이터를 가져와 다시 View에게 전달해줍니다. ( 전달은 setData() 함수 호출을 뜻합니다.)
- Activity에서 setData() 함수로 데이터가 들어오게 되고 textView에 데이터를 표시합니다.
Unit Test 단위 테스트
MVP 패턴은 단위테스트를 더 쉽게 작성할 수 있다는 점인데 어떻게 하는지 위에서 작성한 Presenter의 테스트를 작성해 보겠습니다.
Mock 객체를 만들기 위한 라이브러리로 dependencies에 추가해 줍니다.
testImplementation "org.mockito:mockito-core:3.5.13"
class MainPresenterTest {
@Mock private lateinit var mainRepository: MainRepository
@Mock private lateinit var mainContractView: MainContract.View
private lateinit var mainPresenter: MainPresenter
@Before
fun setupPresenter() {
MockitoAnnotations.openMocks(this)
mainPresenter = MainPresenter(mainRepository, mainContractView)
}
@Test fun createPresenter_setsThePresenterToView() {
mainPresenter = MainPresenter(mainRepository, mainContractView)
verify(mainContractView).presenter = mainPresenter
}
@Test
fun start() {
mainPresenter.start()
verify(mainContractView).showProgress(false)
verify(mainRepository).getData()
verify(mainContractView).setData(mainRepository.getData())
verify(mainContractView).showProgress(true)
}
}
@Mock 어노테이션은 가짜 객체를 생성합니다.
@Before @Test가 실행되기전 실행되어 객체를 초기화할때 사용됩니다.
@Test 테스트 케이스로 실행될 수 있음을 나타냅니다.
@Before가 붙은 setupPresenter() 에서는 @Mock 어노테이션을 사용한 멤버변수들을 생성하고 presenter 객체를 생성합니다.
@Test의 createPresenter_setsThePresenterToView() 에서는 MainPresenter 클래스를 생성했을때 View에 Presenter가 주입되는지 검증하는 테스트 케이스입니다.
@Test의 start() 에서는 Presenter의 start()를 호출하고 Presenter의 start()에 구현되어있는 동작들이 각각 제대로 호출되고 있는지 검증하는 테스트 케이스 입니다.
이렇게 Presenter의 Unit Test를 알아보았습니다.
출처 : https://velog.io/@bang/Android-MVP-pattern-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0
'Android 공부 > 디자인패턴' 카테고리의 다른 글
1) MVVM 패턴 (0) | 2020.01.30 |
---|---|
MVP 패턴 예시1 (0) | 2019.09.23 |