Share
Embed
Transcript
88 slides
-
About me • kiriminͱ͍͏IDͰTwitterGitHubͬͯ·͢ • ΈΜ͔Β͖Γ·Ͱͱ͍͏ϒϩάͰ৭ʑॻ͍ͯ·͢ • 3͘Β͍ϑϦʔͰAndroidΤϯδχΞͬͯ ·ͨ͠ •
ࠓAnyPayͱ͍͏ձࣾͰpaymoͱ͍͏ΞϓϦΛ ࡞ͬͯ·͢ -
About paymo • εϚϗͰׂΓצͷਫ਼ࢉ͕ ग़དྷΔܾࡁΞϓϦ • ҿΈձϥϯνͳͲͰҰ ׅձܭɺޙ͔ΒϢʔβʔ ಉ࢜Ͱٻͨ͠Γࢧͬ ͨΓग़དྷΔ
• QRίʔυܾࡁػೳͳͲ ͋Γ·͢ -
About paymo • 90%Kotlin • MVP Architecture • Unit Test
for Presenter • CI with Bitrise -
paymo's Architecture %BUB %PNBJO 1SFTFOUBUJPO 7JFX 1SFTFOUFS 6TF$BTF 3FQPTJUPSZ %BUB4UPSF
"1* %# 1SFGFSFODFT "DUJWJUZ 'SBHNFOU $VTUPN7JFX 3FQPTJUPSZ %BUB4UPSF 3FQPTJUPSZ %BUB4UPSF .PEFM %* %* %* -
MVPɺͬͯ·͔͢ʁ • Androidք۾Ͱ2͘Β͍લʹʹͳͬͨ • ControllerͷΘΓʹPresenterͱ͍͏UIϩδοΫΛ୲͏Ϋ ϥεΛ༻ҙ͢Δ • ActivityFragmentViewͱͯ͠ѻ͍ɺۃྗϩδοΫΛ࣋ ͨͤͳ͍ •
Presenterͱ͍͏֓೦Clean ArchitectureͰͰͯ͘ΔͨΊ ࠾༻͍ͯ͠ΔϓϩδΣΫτ݁ߏݟ͔͚Δ -
1SFTFOUFS 7JFX 6TF$BTF ඳը "OESPJE '8 %# "1* 4IBSFE 1SFGFSFODFT
ViewͱPresenterΛ͢Δ • Androidʹґଘͨ͠ॲཧσʔλΞΫηεॲཧΛ Presenter͔Β͠ɺPresenterΛϐϡΞʹอͭ -
αϯϓϧΞϓϦ • GitHubΫϥΠΞϯτΞϓϦ • ೖྗͨ͠IDͷϢʔβʔͷ ProfileΛදࣔ͢Δ͚ͩ • γϯάϧϖʔδͷγϯϓϧ ͳΞϓϦ •
Λ͔Γ͘͢͢ΔͨΊ ʹDaggerͳͲະ༻ -
class TopActivity : AppCompatActivity() { lateinit var presenter: TopPresenter override
fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_top) presenter = TopPresenter(this) presenter.onCreate() toolbar.title = "Who on GitHub" submitButton.setOnClickListener { hideKeyBoard() parentLayout.visibility = View.GONE presenter.getUserInfo(userIdEditText) } userIdEditText.setOnKeyListener { view, keyCode, event -> if (KeyEvent.KEYCODE_ENTER == keyCode && event.action == KeyEvent.ACTION_UP) { hideKeyBoard() parentLayout.visibility = View.GONE presenter.getUserInfo(userIdEditText) } false } } ... fun setUserInfo(user: User) { parentLayout.visibility = View.VISIBLE nameText.text = user.name if (user.location.isNullOrEmpty()) { locationText.visibility = View.GONE } else { locationText.visibility = View.VISIBLE locationText.text = user.location } if (user.mail.isNullOrEmpty()) { mailText.visibility = View.GONE } else { mailText.visibility = View.VISIBLE mailText.text = user.mail } if (user.link.isNullOrEmpty()) { linkText.visibility = View.GONE } else { linkText.visibility = View.VISIBLE linkText.text = user.link } Picasso.with(this).load(user.iconUrl).fit().into(iconImage) } ... -
override fun onCreate(savedInstanceState: Bundle?) { ... submitButton.setOnClickListener { hideKeyBoard() parentLayout.visibility
= View.GONE presenter.getUserInfo(userIdEditText) } ... fun setUserInfo(user: User) { parentLayout.visibility = View.VISIBLE nameText.text = user.name if (user.location.isNullOrEmpty()) { locationText.visibility = View.GONE } else { locationText.visibility = View.VISIBLE locationText.text = user.location } if (user.mail.isNullOrEmpty()) { mailText.visibility = View.GONE } else { mailText.visibility = View.VISIBLE mailText.text = user.mail } if (user.link.isNullOrEmpty()) { linkText.visibility = View.GONE } else { linkText.visibility = View.VISIBLE linkText.text = user.link } Picasso.with(this).load(user.iconUrl).fit().into(iconImage) } 7JFXଆͰϩδοΫΛ ࣋ͬͯΔ 7JFX͕1SFTFOUFSʹΠϕϯτΛ௨ ͢ΔͷͰͳ͘ɺࢦࣔΛग़͍ͯ͠Δ -
class TopPresenter(val view: TopActivity) { val useCase = TopUseCase() lateinit
var disposables: CompositeDisposable fun onCreate() { disposables = CompositeDisposable() } fun onDestroy() { disposables.clear() } fun getUserInfo(userIdEditText: EditText) { val id = userIdEditText.text.toString() if (id.isEmpty()) { Toast.makeText(view, "validation error", Toast.LENGTH_SHORT).show() return } val progressBar = view.findViewById<ProgressBar>(R.id.progressBar) progressBar.visibility = View.VISIBLE useCase.fetchUserInfo(id).subscribe({ user -> progressBar.visibility = View.GONE view.setUserInfo(user) view.setLanguages(getLanguages(user.repositories)) view.setRepositories(sortRepositories(user.repositories)) }, { view.progressBar.visibility = View.GONE Toast.makeText(view, "network error", Toast.LENGTH_SHORT).show() }).also { disposables.add(it) } } /** * ϦϙδτϦΛݴޠ͝ͱʹݴޠ໊ͱϦϙδτϦϦετͷPairʹ·ͱΊͨListΛฦ͢ */ fun getLanguages(repositories: List<Repository>): List<Pair<String, List<Repository>>> { return repositories .filter { it.language != null } .groupBy { it.language!! } .toList().sortedByDescending { language -> language.second.count() } } /** * ϦϙδτϦΛελʔͷͱfork͔൱͔Ͱιʔτͯ͠ฦ͢ */ fun sortRepositories(repositories: List<Repository>): List<Repository> { return repositories .sortedByDescending { repo -> repo.starCount } .sortedBy { repo -> repo.isFork } } -
class TopPresenter(val view: TopActivity) { ... fun getUserInfo(userIdEditText: EditText) {
val id = userIdEditText.text.toString() if (id.isEmpty()) { Toast.makeText(view, "validation error", Toast.LENGTH_SHORT).show() return } val progressBar = view.findViewById<ProgressBar>(R.id.progressBar) progressBar.visibility = View.VISIBLE useCase.fetchUserInfo(id).subscribe({ user -> progressBar.visibility = View.GONE view.setUserInfo(user) view.setLanguages(getLanguages(user.repositories)) view.setRepositories(sortRepositories(user.repositories)) }, { view.progressBar.visibility = View.GONE Toast.makeText(view, "network error", Toast.LENGTH_SHORT).show() }).also { disposables.add(it) } } WJFXΛ"DUJWJUZͱͯ͠ ࢀর͍ͯ͠Δ pOE7JFX#Z*EͰ 1SPHSFTT#BSΛ ૢ࡞ͯ͠Δ 5PTUͷදࣔͳͲ 1SFTFOUFSଆͰߦ͍ͬͯΔ -
interface TopView { class TopActivity : AppCompatActivity(), TopView { lateinit
var presenter: TopPresenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) presenter = TopPresenter(this, TopUseCase()) presenter.onCreate() } override fun onDestroy() { presenter.onDestroy() super.onDestroy() } override fun initView() { setContentView(R.layout.activity_top) toolbar.title = "Who on GitHub" submitButton.setOnClickListener { presenter.onSubmitButtonClick(userIdEditText.text.toString()) } userIdEditText.setOnKeyListener { _, keyCode, event -> presenter.onUserIdEditTextKeyListener(userIdEditText.text.toString(), keyCode, eve false } } ... "DUJWJUZΛ 7JFXΠϯλʔϑΣΠεͰӅṭ 7JFX͔ΒQSFTFOUFSʹ ϥΠϑαΠΫϧͳͲͷ ΠϕϯτΛ௨͢Δ͚ͩ $MJDLΠϕϯτͳͲ 1SFTFOUFSʹ௨͢ΔͷΈ -
... override fun showErrorToast(text: String) { Toast.makeText(this, text, Toast.LENGTH_SHORT).show() }
override fun setProgressBarVisibility(visibility: Int) { progressBar.visibility = visibility } override fun setParentLayoutVisibility(visibility: Int) { parentLayout.visibility = visibility } override fun hideKeyBoard() { val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(currentFocus!!.windowToken, InputMethodManager.HIDE_NOT_ALWAYS) } override fun setUserName(name: String) { nameText.text = name } override fun setLocationTextAndVisibility(visibility: Int, text: String) { locationText.visibility = visibility locationText.text = text } override fun setLinkTextAndVisibility(visibility: Int, text: String) { linkText.visibility = visibility linkText.text = text } override fun setMailTextAndVisibility(visibility: Int, text: String) { mailText.visibility = visibility mailText.text = text } override fun setIcon(iconUrl: String) { Picasso.with(this).load(iconUrl).fit().into(iconImage) } override fun setIconVisibility(visibility: Int) { iconImage.visibility = visibility } ... ϩδοΫΛ࣋ͨͳ͍ 7JFXΛૢ࡞͢Δ ͍ϝιου܈Λ࣋ͭ -
} fun initView() fun showErrorToast(text: String) fun setProgressBarVisibility(visibility: Int) fun
setParentLayoutVisibility(visibility: Int) fun hideKeyBoard() fun setUserName(name: String) fun setLocationTextAndVisibility(visibility: Int, text: String) fun setMailTextAndVisibility(visibility: Int, text: String) fun setLinkTextAndVisibility(visibility: Int, text: String) fun setIcon(iconUrl: String) fun setIconVisibility(visibility: Int) fun addLanguageView(language: Language) fun addRepositoryView(repository: Repository) } ͦΕΒͷϝιου *OUFSGBDFʹఆٛ͞Ε 1SFTFOUFS͔ΒΞΫηεՄೳ -
class TopPresenter(val view: TopView, val useCase: TopUseCase) { lateinit var
disposables: CompositeDisposable fun onCreate() { disposables = CompositeDisposable() view.initView() } fun onDestroy() { disposables.clear() } fun onSubmitButtonClick(text: String) { getUserInfo(text) } fun onUserIdEditTextKeyListener(text: String, keyCode: Int, action: Int) { if (KeyEvent.KEYCODE_ENTER == keyCode && action == KeyEvent.ACTION_UP) { getUserInfo(text) } } ... "DUJWJUZΛ7JFXΠϯλʔϑΣΠε ͱͯ͠อ࣋ Πϕϯτ௨Λड͚औͬͨ 1SFTFOUFSΠϕϯτʹԠͨ͡ॲཧΛߦ͏ -
private fun getUserInfo(id: String) { if (id.isEmpty()) { view.showErrorToast("validation error")
return } view.hideKeyBoard() view.setProgressBarVisibility(View.VISIBLE) view.setParentLayoutVisibility(View.GONE) useCase.fetchUserInfo(id).subscribe({ user -> view.setProgressBarVisibility(View.GONE) view.setParentLayoutVisibility(View.VISIBLE) view.setUserName(user.name) if (user.location.isNullOrEmpty()) { view.setLocationTextAndVisibility(View.GONE, "") } else { view.setLocationTextAndVisibility(View.VISIBLE, user.location!!) } if (user.mail.isNullOrEmpty()) { view.setMailTextAndVisibility(View.GONE, "") } else { view.setMailTextAndVisibility(View.VISIBLE, user.mail!!) } if (user.link.isNullOrEmpty()) { view.setLinkTextAndVisibility(View.GONE, "") } else { view.setLinkTextAndVisibility(View.VISIBLE, user.link!!) } if (user.iconUrl != null) { view.setIconVisibility(View.VISIBLE) view.setIcon(user.iconUrl) } else { view.setIconVisibility(View.INVISIBLE) } getLanguages(user.repositories).forEach { view.addLanguageView(it) } sortRepositories(user.repositories).forEach { view.addRepositoryView(it) } }, { view.setProgressBarVisibility(View.GONE) view.showErrorToast("network error") }).also { disposables.add(it) } } औಘͨ͠σʔλΛ1SFTFOUFSଆͰఆ͠ɺ ࠷ऴతͳ݁ՌΛ7JFXʹରͯ͠ࢦࣔ͢Δ 1SFTFOUFS͔Β6TF$BTFʹ σʔλͷऔಘΛґཔ -
ςετͷྨ • ςετʹେ͖͘Θ͚ͯUIς ετɺ౷߹ςετɺϢχοτ ςετͷ3͕ͭ͋Δ • 3ͭϐϥϛοτͷؔͰɺ ্ʹߦ͘΄Ͳ࣮ɾ࣮ߦίε τ͕ߴ͍͕ຊ൪ڥʹ͍ۙ UIςετ
౷߹ςετ Ϣχοτςετ by ॳΊͯͷࣗಈςετ webγεςϜͷͨΊͷࣗಈςετೖ -
PresenterʹςετΛॻ͘ ϝϦοτ • Viewͷදग़ఆͳͲͷը໘શମͷϩδοΫͷਖ਼͠͞ΛUI ςετʹཻ͍ۙͰνΣοΫͰ͖Δ • ͍ൣғͰͷσάϨΛݕ͘͢͠ͳΔ • PresenterʹϩδοΫΛدͤςετΛॻ͘ࣄͰɺPresenter ͷॲཧͷϦϑΝΫλϞσϧΫϥεͷΓग़͠ͳͲ͕
Γ͘͢ͳΔ • ϩʔΧϧϢχοτςετͱͯ͠ΤϛϡϨʔλʔແ͠Ͱૉૣ ࣮͘ߦग़དྷΔͨΊɺؾܰʹ࣮ߦͯ֬͠ೝग़དྷΔ -
1SFTFOUFS 7JFX 6TF$BTF ඳը "OESPJE '8 %# "1* 4IBSFE 1SFGFSFODFT
͓͞Β͍ • Androidʹґଘͨ͠ॲཧσʔλΞΫηεॲཧΛ Presenter͔Β͠ɺPresenterΛϐϡΞʹอͭ -
MockitoΛ͏ • JavaͷϝδϟʔͳϞοΫϥΠϒϥϦ • ΫϥεΠϯλʔϑΣΠεͷϞοΫΠϯελϯεΛੜ ͠ɺϝιουʹҙͷΓΛࢦఆͨ͠Γɺϝιο υ͕ݺΕͨճΛ֬ೝͨ͠Γग़དྷΔ // TopViewͷϞοΫΠϯελϯεΛੜ viewMock
= mock(TopView::class.java) // presenterͷonCreate()ϝιουΛςετͰݺͿ presenter = TopPresenter(viewMock, useCaseMock) presenter.onCreate() // ݁ՌviewMockͷinitView()ϝιου͕ݺΕ͍ͯΔࣄΛ֬ೝ verify(viewMock, Mockito.times(1)).initView() -
class TopPresenterTest { lateinit var viewMock: TopView lateinit var useCaseMock:
TopUseCase lateinit var presenter: TopPresenter @Before fun setup() { viewMock = mock(TopView::class.java) useCaseMock = mock(TopUseCase::class.java) presenter = TopPresenter(viewMock, useCaseMock) } @Test fun onCreateTest() { presenter.onCreate() // initView()ϝιου͕ݺΕ͍ͯΔࣄΛ֬ೝ verify(viewMock, Mockito.times(1)).initView() } @Test fun onSubmitButtonClickTestWithEmptyText() { presenter.onCreate() presenter.onSubmitButtonClick("") // ೖྗ͞Εͨจࣈྻ͕ۭͷ߹Τϥʔ͕දࣔ͞Εॲཧ͕ऴྃ͢Δ͜ͱΛ֬ೝ verify(viewMock, times(1)).showErrorToast("validation error") verify(viewMock, never()).setProgressBarVisibility(View.VISIBLE) verify(useCaseMock, never()).fetchUserInfo(anyString()) } @Test fun onSubmitButtonClickTestWithText() { Mockito.`when`(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.never()) presenter.onCreate() presenter.onSubmitButtonClick("kirimin") // ೖྗ͞Εͨจࣈྻ͕ۭͷ߹Τϥʔ͕දࣔ͞Εͳ͍ࣄΛ֬ೝ verify(viewMock, never()).showErrorToast("validation error") // ೖྗ͞ΕͨจࣈྻΛ༻ͯ͠σʔλͷऔಘ͕ߦΘΕΔ͜ͱΛ֬ೝ verify(viewMock, times(1)).setParentLayoutVisibility(View.GONE) verify(viewMock, times(1)).setProgressBarVisibility(View.VISIBLE) verify(viewMock, never()).setParentLayoutVisibility(View.VISIBLE) verify(viewMock, never()).setProgressBarVisibility(View.GONE) verify(useCaseMock, times(1)).fetchUserInfo("kirimin") } @Test fun onFetchUserInfoSuccessMinCaseTest() { // UseCase͕ฦ͢ΛϞοΫ val userEntity = UserEntity(name = "kirimin", location = null, company = null, blog = null, email = null, avatar_url = null) val repoEntity = RepositoryEntity() Mockito.`when`(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.just(User(userEntity, listOf(repoEntity)))) presenter.onCreate() presenter.onSubmitButtonClick("kirimin") verify(viewMock, times(1)).setProgressBarVisibility(View.GONE) verify(viewMock, times(1)).setParentLayoutVisibility(View.VISIBLE) verify(viewMock, times(1)).setUserName("kirimin") verify(viewMock, times(1)).setLocationTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setMailTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setLinkTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setIconVisibility(View.INVISIBLE) } -
@Test fun onSubmitButtonClickTestWithEmptyText() { presenter.onCreate() presenter.onSubmitButtonClick("") // ೖྗ͞Εͨจࣈྻ͕ۭͷ߹Τϥʔ͕දࣔ͞Εॲཧ͕ऴྃ͢Δ͜ͱΛ֬ೝ verify(viewMock, times(1)).showErrorToast("validation
error") verify(viewMock, never()).setProgressBarVisibility(View.VISIBLE) verify(useCaseMock, never()).fetchUserInfo(anyString()) } @Test fun onSubmitButtonClickTestWithText() { Mockito.`when`(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.n presenter.onCreate() presenter.onSubmitButtonClick("kirimin") // ೖྗ͞Εͨจࣈྻ͕ۭͷ߹Τϥʔ͕දࣔ͞Εͳ͍ࣄΛ֬ೝ verify(viewMock, never()).showErrorToast("validation error") // ೖྗ͞ΕͨจࣈྻΛ༻ͯ͠σʔλͷऔಘ͕ߦΘΕΔ͜ͱΛ֬ೝ verify(viewMock, times(1)).setParentLayoutVisibility(View.GONE) verify(viewMock, times(1)).setProgressBarVisibility(View.VISIBLE) verify(viewMock, never()).setParentLayoutVisibility(View.VISIBLE) verify(viewMock, never()).setProgressBarVisibility(View.GONE) verify(useCaseMock, times(1)).fetchUserInfo("kirimin") } -
@Test fun onFetchUserInfoSuccessMinCaseTest() { val userEntity = UserEntity(name = "kirimin",
location = null, company = null, blog = null, email = null, a val repoEntity = RepositoryEntity() Mockito.`when`(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.just(User(userEntity, listOf(repoEn presenter.onCreate() presenter.onSubmitButtonClick("kirimin") verify(viewMock, times(1)).setProgressBarVisibility(View.GONE) verify(viewMock, times(1)).setParentLayoutVisibility(View.VISIBLE) verify(viewMock, times(1)).setUserName("kirimin") verify(viewMock, times(1)).setLocationTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setMailTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setLinkTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setIconVisibility(View.INVISIBLE) } @Test fun onFetchUserInfoMaxCaseTest() { val userEntity = UserEntity(name = "kirimin", location = "tokyo, japan", company = "kirimin inc.", blog = " email = "[email protected]", avatar_url = "http://kirimin.me/face_icon.png") val repoEntity = RepositoryEntity() Mockito.`when`(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.just(User(userEntity, listOf(repoEn presenter.onCreate() presenter.onSubmitButtonClick("kirimin") verify(viewMock, times(1)).setProgressBarVisibility(View.GONE) verify(viewMock, times(1)).setParentLayoutVisibility(View.VISIBLE) verify(viewMock, times(1)).setUserName("kirimin") verify(viewMock, times(1)).setLocationTextAndVisibility(View.VISIBLE, "tokyo, japan") verify(viewMock, times(1)).setMailTextAndVisibility(View.VISIBLE, "[email protected]") verify(viewMock, times(1)).setLinkTextAndVisibility(View.VISIBLE, "http://kirimin.me") verify(viewMock, times(1)).setIconVisibility(View.VISIBLE) verify(viewMock, times(1)).setIcon("http://kirimin.me/face_icon.png") } -
@Test fun onFetchUserInfoSuccessMinCaseTest() { // UseCase͕ฦ͢ΛϞοΫ val userEntity = UserEntity(name
= "kirimin", location = null, company = null, blog = null, email = null, avatar_url = null) val repoEntity = RepositoryEntity() Mockito.`when`(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.just(User(userEntity, listOf(repoEnti presenter.onCreate() presenter.onSubmitButtonClick("kirimin") verify(viewMock, times(1)).setProgressBarVisibility(View.GONE) verify(viewMock, times(1)).setParentLayoutVisibility(View.VISIBLE) verify(viewMock, times(1)).setUserName("kirimin") verify(viewMock, times(1)).setLocationTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setMailTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setLinkTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setIconVisibility(View.INVISIBLE) } @Test fun onFetchUserInfoMaxCaseTest() { // UseCase͕ฦ͢ΛϞοΫ val userEntity = UserEntity(name = "kirimin", location = "tokyo, japan", company = "kirimin inc.", blog = "h kirimin.me", email = "[email protected]", avatar_url = "http://kirimin.me/face_icon.png") val repoEntity = RepositoryEntity() Mockito.`when`(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.just(User(userEntity, listOf(repoEnti presenter.onCreate() presenter.onSubmitButtonClick("kirimin") verify(viewMock, times(1)).setProgressBarVisibility(View.GONE) verify(viewMock, times(1)).setParentLayoutVisibility(View.VISIBLE) verify(viewMock, times(1)).setUserName("kirimin") verify(viewMock, times(1)).setLocationTextAndVisibility(View.VISIBLE, "tokyo, japan") verify(viewMock, times(1)).setMailTextAndVisibility(View.VISIBLE, "[email protected]") verify(viewMock, times(1)).setLinkTextAndVisibility(View.VISIBLE, "http://kirimin.me") verify(viewMock, times(1)).setIconVisibility(View.VISIBLE) verify(viewMock, times(1)).setIcon("http://kirimin.me/face_icon.png") } σʔλͷύλʔϯ͝ͱʹ ςετϝιουΛॻ͍͍ͯΔ ͦΕͧΕͷςετͰ ॏෳίʔυΛॻ͍ͯΔ σʔλύλʔϯ ঢ়ଶ͕૿͑Δ΄Ͳ ςετίʔυ͕ංେԽ͢Δ -
@Test fun userIdSubmitTest() { whenever(useCaseMock.fetchUserInfo(anyString())).thenReturn(Single.never()) fun isError() { verify(viewMock, times(1)).showErrorToast(anyString())
verify(viewMock, never()).setProgressBarVisibility(View.VISIBLE) verify(useCaseMock, never()).fetchUserInfo(anyString()) } fun isSuccess() { verify(viewMock, never()).showErrorToast(anyString()) verify(viewMock, times(1)).setParentLayoutVisibility(View.GONE) verify(viewMock, times(1)).setProgressBarVisibility(View.VISIBLE) verify(viewMock, never()).setParentLayoutVisibility(View.VISIBLE) verify(viewMock, never()).setProgressBarVisibility(View.GONE) verify(useCaseMock, times(1)).fetchUserInfo("kirimin") } fun isIgnore() { verify(viewMock, never()).showErrorToast(anyString()) verify(viewMock, never()).setProgressBarVisibility(anyInt()) verify(viewMock, never()).setParentLayoutVisibility(anyInt()) verify(useCaseMock, never()).fetchUserInfo(anyString()) } fun isSubmitButtonClick() { initializeMocks() presenter.onSubmitButtonClick("") isError() initializeMocks() presenter.onSubmitButtonClick("kirimin") isSuccess() } fun withEditTextKeyEnter() { initializeMocks() presenter.onUserIdEditTextKeyListener("", KeyEvent.KEYCODE_ENTER, KeyEvent.ACTION_UP) isError() initializeMocks() presenter.onUserIdEditTextKeyListener("kirimin", KeyEvent.KEYCODE_ENTER, KeyEvent.ACTION_UP) isSuccess() initializeMocks() presenter.onUserIdEditTextKeyListener("kirimin", KeyEvent.KEYCODE_0, KeyEvent.ACTION_UP) isIgnore() initializeMocks() presenter.onUserIdEditTextKeyListener("kirimin", KeyEvent.KEYCODE_ENTER, KeyEvent.ACTION_DOWN) isIgnore() } presenter.onCreate() isSubmitButtonClick() withEditTextKeyEnter() } ,PUMJOͷϩʔΧϧؔʹΑͬͯ ߏԽ͞Εͨςετέʔε -
@Test fun userIdSubmitTest() { whenever(useCaseMock.fetchUserInfo(anyString())).thenReturn(Single.never()) fun isError() { ... }
fun isSuccess() { ... } fun isIgnore() { ... } fun isSubmitButtonClick() { initializeMocks() presenter.onSubmitButtonClick("") isError() initializeMocks() presenter.onSubmitButtonClick("kirimin") isSuccess() } fun withEditTextKeyEnter() { initializeMocks() presenter.onUserIdEditTextKeyListener("", KeyEvent.KEYCODE_ENTER, KeyEvent.ACTION_UP) isError() initializeMocks() presenter.onUserIdEditTextKeyListener("kirimin", KeyEvent.KEYCODE_ENTER, KeyEvent.ACTION_UP) isSuccess() initializeMocks() presenter.onUserIdEditTextKeyListener("kirimin", KeyEvent.KEYCODE_0, KeyEvent.ACTION_UP) isIgnore() initializeMocks() presenter.onUserIdEditTextKeyListener("kirimin", KeyEvent.KEYCODE_ENTER, KeyEvent.ACTION_DOWN) isIgnore() } presenter.onCreate() isSubmitButtonClick() withEditTextKeyEnter() } ϘλϯΛԡͯ͠ ϦΫΤετͨ͠߹ͷέʔε ΤϯλʔΩʔͰ ϦΫΤετͨ͠߹ͷέʔε ͦΕͧΕೖྗʹΑͬͯ Ͳ͏͍͏݁ՌʹͳΔ͔Λఆٛ -
@Test fun userIdSubmitTest() { whenever(useCaseMock.fetchUserInfo(anyString())).thenReturn(Single.never()) fun isError() { verify(viewMock, times(1)).showErrorToast(anyString())
verify(viewMock, never()).setProgressBarVisibility(View.VISIBLE) verify(useCaseMock, never()).fetchUserInfo(anyString()) } fun isSuccess() { verify(viewMock, never()).showErrorToast(anyString()) verify(viewMock, times(1)).setParentLayoutVisibility(View.GONE) verify(viewMock, times(1)).setProgressBarVisibility(View.VISIBLE) verify(viewMock, never()).setParentLayoutVisibility(View.VISIBLE) verify(viewMock, never()).setProgressBarVisibility(View.GONE) verify(useCaseMock, times(1)).fetchUserInfo("kirimin") } fun isIgnore() { verify(viewMock, never()).showErrorToast(anyString()) verify(viewMock, never()).setProgressBarVisibility(anyInt()) verify(viewMock, never()).setParentLayoutVisibility(anyInt()) verify(useCaseMock, never()).fetchUserInfo(anyString()) } fun isSubmitButtonClick() { ... } fun withEditTextKeyEnter() { ... } presenter.onCreate() isSubmitButtonClick() withEditTextKeyEnter() } -
@Test fun onFetchUserInfoTest() { fun success() { fun hideProgressAndShowUserInfoLayout() {
verify(viewMock, times(1)).setProgressBarVisibility(View.GONE) verify(viewMock, times(1)).setParentLayoutVisibility(View.VISIBLE) } fun setUserInfoMinCase() { val userEntity = UserEntity(name = "kirimin", location = null, company = null, blog = null, email = null, avatar_url = null) whenever(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.just(User(userEntity, listOf(RepositoryEntity())))) initializeMocks() presenter.onSubmitButtonClick("kirimin") hideProgressAndShowUserInfoLayout() verify(viewMock, times(1)).setUserName("kirimin") verify(viewMock, times(1)).setLocationTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setMailTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setLinkTextAndVisibility(eq(View.GONE), anyString()) verify(viewMock, times(1)).setIconVisibility(View.INVISIBLE) } fun setUserInfoMaxCase() { val userEntity = UserEntity(name = "kirimin", location = "tokyo, japan", company = "kirimin inc.", blog = "http://kirimin.me", email = "cc@kirimin. avatar_url = "http://kirimin.me/face_icon.png") whenever(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.just(User(userEntity, listOf(RepositoryEntity())))) initializeMocks() presenter.onSubmitButtonClick("kirimin") hideProgressAndShowUserInfoLayout() verify(viewMock, times(1)).setUserName("kirimin") verify(viewMock, times(1)).setLocationTextAndVisibility(View.VISIBLE, "tokyo, japan") verify(viewMock, times(1)).setMailTextAndVisibility(View.VISIBLE, "[email protected]") verify(viewMock, times(1)).setLinkTextAndVisibility(View.VISIBLE, "http://kirimin.me") verify(viewMock, times(1)).setIconVisibility(View.VISIBLE) verify(viewMock, times(1)).setIcon("http://kirimin.me/face_icon.png") } setUserInfoMinCase() setUserInfoMaxCase() } fun failed() { whenever(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.error(Exception("the error"))) initializeMocks() presenter.onSubmitButtonClick("kirimin") verify(viewMock, times(1)).setProgressBarVisibility(View.GONE) verify(viewMock, never()).setParentLayoutVisibility(View.VISIBLE) verify(viewMock, times(1)).showErrorToast(anyString()) } presenter.onCreate() success() failed() } -
@Test fun onFetchUserInfoTest() { fun success() { fun hideProgressAndShowUserInfoLayout() {
verify(viewMock, times(1)).setProgressBarVisibility(View.GONE) verify(viewMock, times(1)).setParentLayoutVisibility(View.VISIBLE) } fun setUserInfoMinCase() { val userEntity = UserEntity(name = "kirimin", location = null, company = null, blog = null, email = null, avatar_url = null) whenever(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.just(User(userEntity, listOf(RepositoryEntity())))) initializeMocks() presenter.onSubmitButtonClick("kirimin") hideProgressAndShowUserInfoLayout() verify(viewMock, times(1)).setUserName("kirimin") ... } fun setUserInfoMaxCase() { val userEntity = UserEntity(name = "kirimin", location = "tokyo, japan", company = "kirimin inc.", blog = "http://kirimin.me = "[email protected]", avatar_url = "http://kirimin.me/face_icon.png") whenever(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.just(User(userEntity, listOf(RepositoryEntity())))) initializeMocks() presenter.onSubmitButtonClick("kirimin") hideProgressAndShowUserInfoLayout() verify(viewMock, times(1)).setUserName("kirimin") ... } setUserInfoMinCase() setUserInfoMaxCase() } fun failed() { whenever(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.error(Exception("the error"))) initializeMocks() presenter.onSubmitButtonClick("kirimin") verify(viewMock, times(1)).setProgressBarVisibility(View.GONE) ... } presenter.onCreate() success() failed() } ؔʹΓग़ͨ͠ ڞ௨ͷςετέʔε औಘͨ͠σʔλ͝ͱͷ ςετέʔε -
ςετͷϐϥϛουΛࢥ͍ग़͢ • ౷߹ςετڧྗͰศར͕ͩɺ Ϟσϧʹର͢ΔγϯϓϧͳΞ αʔγϣϯςετΑΓॻ͖ ͘͢ਫ਼ߴ͍ • ຊདྷϢχοτςετͷൺॏ ͕େ͖͍ͷ͕݈શ •
Presenter͔ΒϞσϧʹॲཧ ͱςετΛҠ͍ͯ͜͠͏ UIςετ ౷߹ςετ Ϣχοτςετ -
private fun getUserInfo(id: String) { ... getLanguages(user.repositories).forEach { view.addLanguageView(it) }
sortRepositories(user.repositories).forEach { view.addRepositoryView(it) } }, { view.setProgressBarVisibility(View.GONE) view.showErrorToast("network error") }).also { disposables.add(it) } } /** * ϦϙδτϦΛݴޠ͝ͱʹݴޠ໊ͱϦϙδτϦϦετͷPairʹ·ͱΊͨListΛฦ͢ */ private fun getLanguages(repositories: List<Repository>): List<Language> { return repositories .filter { it.language != null } .groupBy { it.language!! } .toList().sortedByDescending { language -> language.second.count() } .map { Language(it.first, it.second) } } /** * ϦϙδτϦΛελʔͷͱfork͔൱͔Ͱιʔτͯ͠ฦ͢ */ private fun sortRepositories(repositories: List<Repository>): List<Repository> { return repositories .sortedByDescending(Repository::starCount) .sortedBy(Repository::isFork) } 1SFTFOUFSʹॻ͔Εͨσʔλ ܗͷQSJWBUFϝιου -
data class User(private val entity: UserEntity, private val repos: List<RepositoryEntity>)
{ val id = entity.id val name = entity.name val location = entity.location val mail = entity.email val link = entity.blog val iconUrl = entity.avatar_url val repositories = repos.map(::Repository) val languages = repositories .filter { it.language != null } .groupBy { it.language!! } .toList().sortedByDescending { language -> language.second.count() } .map { Language(it.first, it.second) } val sortedRepositories = repositories .sortedByDescending(Repository::starCount) .sortedBy(Repository::isFork) } υϝΠϯϞσϧͰॲཧΛߦ͍ Λอ࣋͢ΔΑ͏ʹมߋ -
class UserTest { lateinit var user: User lateinit var a:
RepositoryEntity lateinit var b: RepositoryEntity lateinit var c: RepositoryEntity lateinit var d: RepositoryEntity lateinit var e: RepositoryEntity @Before fun setup() { a = RepositoryEntity(name = "a", stargazers_count = 5, fork = false, language = "Java") b = RepositoryEntity(name = "b", stargazers_count = 2, fork = false, language = null) c = RepositoryEntity(name = "c", stargazers_count = 7, fork = true, language = "Kotlin") d = RepositoryEntity(name = "d", stargazers_count = 3, fork = false, language = "Kotlin") e = RepositoryEntity(name = "e", stargazers_count = 7, fork = false, language = "PHP") user = User(UserEntity(), listOf(a, b, c, d, e)) } @Test fun sortedRepositoriesTest() { user.sortedRepositories.should be listOf(e, a, d, b, c).map(::Repository) } @Test fun languagesTest() { user.languages.should be listOf( Language(name = "Kotlin", includeRepositories = listOf(c, d).map(::Repository)), Language(name = "Java", includeRepositories = listOf(a).map(::Repository)), Language(name = "PHP", includeRepositories = listOf(e).map(::Repository)) ) } } ςετϞσϧʹର͢Δ ΑΓγϯϓϧͳϢχοτςετʹมߋ -
@Test fun addRepositoryViewTest() { val repositoryEntities = listOf( RepositoryEntity(name =
"a"), RepositoryEntity(name = "b"), RepositoryEntity(name = "c") ) whenever(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.just(User(UserEntity(name = "kirimin repositoryEntities))) presenter.onCreate() presenter.onSubmitButtonClick("kirimin") verify(viewMock, times(1)).addRepositoryView(Repository(RepositoryEntity(name = "a"))) verify(viewMock, times(1)).addRepositoryView(Repository(RepositoryEntity(name = "b"))) verify(viewMock, times(1)).addRepositoryView(Repository(RepositoryEntity(name = "c"))) } @Test fun addLanguageViewTest() { val a = mock<Language>() val b = mock<Language>() val c = mock<Language> { on { name } doReturn "c" } val languagesMock = listOf(a, b, c) val userMock = mock<User> { on { languages } doReturn languagesMock } whenever(useCaseMock.fetchUserInfo("kirimin")).thenReturn(Single.just(userMock)) presenter.onCreate() presenter.onSubmitButtonClick("kirimin") verify(viewMock, times(1)).addLanguageView(a) verify(viewMock, times(1)).addLanguageView(b) verify(viewMock, times(1)).addLanguageView(c) } 1SFTFOUFSଆͷςετ .PDLΛ༻ͨ͠؆୯ͳͷ͚ͩʹ -
·ͱΊ • MockitoΛͬͨ౷߹ςετΑΓΞαʔγϣϯʹΑΔϢχο τςετͷํ͕ίετ͕͘ਫ਼ߴ͍ • Presenter͔ΒυϝΠϯϞσϧͱͯ͠Γग़ͤΔॲཧΛݟ͚ͭ Α͏ • গͣͭ͠ϢχοτςετΛ૿͍ͯ͘͜͠ͱͰɺPresenterʹ ର͢Δςετ͕ංେԽ͠ͳ͍Α͏ʹ͢Δ
• ຊʹΫϦςΟΧϧͳ෦ͷςετEspressoͳͲΛར༻͠ ͨUIςετΛ͏બ͋Δ