Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Android TDD 적용기

Android TDD 적용기

드로이드나이츠 2019 - [Android TDD 적용기] 발표자료입니다.

Given at DroidKnight 2019.

앱 개발을 하며 느리고 반복적인 디바이스 테스트에 회의를 느끼고, 테스트를 도입하려고 마음먹었습니다. 단위테스트 작성부터 TDD를 적용하는 것을 목표로 많은 시행착오를 겪었으며, 최근 실무 프로젝트의 대부분을 TDD로 개발하였습니다. 이번 프로젝트의 일부를 실례로 제시하며, 안드로이드 앱 개발과정에 TDD를 적용하면서 느끼고 경험했던 것을 공유합니다.

Avatar for Kim sung-il

Kim sung-il

April 05, 2019
Tweet

Other Decks in Programming

Transcript

  1. %FWFMPQFNBJMMPHJOXJUI5%% Email & Password ੑ۱ ۽Ӓੋ ߡౡ ௿ܼ ੑ۱ ч

    Ѩૐ Login API
 (Server) ۽Ӓੋ ࢿҕ য়ܨݫद૑ ࠁৈ઱ӝ
  2. $POUSBDU class LoginContract { interface View { } interface Presenter

    { } interface Repository { } } class LoginContract { interface View { } interface Presenter { } } interface Repository { }
  3. *NQMFNFOUBUJPOPG-PHJO$POUSBDU class LoginActivity : AppCompatActivity(), LoginContract.View { override fun onCreate(savedInstanceState:

    Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) } } class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter {} class LoginRepository : LoginContract.Repository {} private lateinit var presenter: LoginContract.Presenter presenter = LoginPresenter(this, Injector.provideLoginRepository())
  4. -PHJO1SFTFOUFS5FTU class LoginPresenterTest { } @RunWith(MockitoJUnitRunner::class) private lateinit var inOrder:

    InOrder inOrder = Mockito.inOrder(view, repository) @Mock private lateinit var view: LoginContract.View @Mock private lateinit var repository: LoginContract.Repository private lateinit var presenter: LoginPresenter @Before fun setup() { presenter = LoginPresenter(view, repository) }
  5. 5FTU$BTF4DFOBSJPT Email & Password ੑ۱ ۽Ӓੋ ߡౡ ௿ܼ ੑ۱ ч

    Ѩૐ Login API
 (Server) ۽Ӓੋ ࢿҕ য়ܨݫद૑ ࠁৈ઱ӝ Email & Password ੑ۱ ۽Ӓੋ ߡౡ ௿ܼ ੑ۱ ч Ѩૐ Login API
 (Server) ۽Ӓੋ ࢿҕ য়ܨݫद૑ ࠁৈ઱ӝ Email & Password ੑ۱ ۽Ӓੋ ߡౡ ௿ܼ ੑ۱ ч Ѩૐ Login API
 (Server) ۽Ӓੋ ࢿҕ য়ܨݫद૑ ࠁৈ઱ӝ [ੑ۱ч(੉ݫੌ/ಁझਕ٘) Ѩૐ पಁ] [۽Ӓੋ ࢿҕ] [۽Ӓੋ पಁ]
  6. పझ౟੘ࢿߨ஗ Given When Then - ౠ੿ ࢚ട੉ ઱য૑Ҋ.. - (పझ౟ೞ۰ח)

    ౠ੿ ঘ࣌੉ ߊࢤ೮ਸ ٸ.. - ߸ച ػ ࢚కա ࣻ೯غח ೯زਸ Ѩૐೠ׮.
  7. పझ౟੘ࢿߨ஗ Given When Then - ౠ੿ ࢚ട੉ ઱য૑Ҋ.. - (పझ౟ೞ۰ח)

    ౠ੿ ঘ࣌੉ ߊࢤ೮ਸ ٸ.. - ߸ച ػ ࢚కա ࣻ೯غח ೯زਸ Ѩૐೠ׮. -> ੜޅ ػ ੉ݫੌਸ ੑ۱ೠ׮. -> ۽Ӓੋ ߡౡਸ ௿ܼೠ׮. -> ੉ݫੌ Ѩૐ पಁ ݫद૑ܳ ࠁৈળ׮.
  8. 5FTU$BTF4DFOBSJPੜޅػ੉ݫੌഋधੑ۱ @Test fun ੉ݫੌ۽Ӓੋ_पಁா੉झ_ੜޅػ੉ݫੌഋधੑ۱() { //given ੜޅ ػ ੉ݫੌਸ ੑ۱ೠ׮.

    //when ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. //then ਬബࢿ Ѩૐ पಁ & ੉ݫੌ Ѩૐ য়ܨ ݫद૑ܳ ࠁৈળ׮. } Email & Password ੑ۱ ۽Ӓੋ ߡౡ ௿ܼ ੑ۱ ч Ѩૐ Login API
 (Server) ۽Ӓੋ ࢿҕ য়ܨݫद૑ ࠁৈ઱ӝ
  9. 5FTU$BTF8SJUFUFTUDPEF @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectEmail() { } @Test fun ੉ݫੌ۽Ӓੋ_पಁா੉झ_ੜޅػ੉ݫੌഋधੑ۱() {

    //given ੜޅ ػ ੉ݫੌਸ ੑ۱ೠ׮. //when ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. //then ਬബࢿ Ѩૐ पಁ & ੉ݫੌ Ѩૐ য়ܨ ݫद૑ܳ ࠁৈળ׮. }
  10. 5FTU$BTF8SJUFUFTUDPEF @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectEmail() { } `when`(view.getInputEmail()).thenReturn(“roy.kim@goodoc") @Test fun ੉ݫੌ۽Ӓੋ_पಁா੉झ_ੜޅػ੉ݫੌഋधੑ۱()

    { //given ੜޅ ػ ੉ݫੌਸ ੑ۱ೠ׮. //when ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. //then ਬബࢿ Ѩૐ पಁ & ੉ݫੌ Ѩૐ য়ܨ ݫद૑ܳ ࠁৈળ׮. }
  11. 5FTU$BTF8SJUFUFTUDPEF @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectEmail() { } `when`(view.getInputEmail()).thenReturn(“roy.kim@goodoc") presenter.onClickEmailLogin() @Test fun

    ੉ݫੌ۽Ӓੋ_पಁா੉झ_ੜޅػ੉ݫੌഋधੑ۱() { //given ੜޅ ػ ੉ݫੌਸ ੑ۱ೠ׮. //when ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. //then ਬബࢿ Ѩૐ पಁ & ੉ݫੌ Ѩૐ য়ܨ ݫद૑ܳ ࠁৈળ׮. }
  12. 5FTU$BTF8SJUFUFTUDPEF @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectEmail() { } `when`(view.getInputEmail()).thenReturn(“roy.kim@goodoc") presenter.onClickEmailLogin() verify(view).showMessageForIncorrectEmail() @Test

    fun ੉ݫੌ۽Ӓੋ_पಁா੉झ_ੜޅػ੉ݫੌഋधੑ۱() { //given ੜޅ ػ ੉ݫੌਸ ੑ۱ೠ׮. //when ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. //then ਬബࢿ Ѩૐ पಁ & ੉ݫੌ Ѩૐ য়ܨ ݫद૑ܳ ࠁৈળ׮. }
  13. 5FTU$BTF @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectEmail() { `when`(view.getInputEmail()).thenReturn("roy.kim@goodoc") presenter.onClickEmailLogin() verify(view).showMessageForIncorrectEmail() } @Test

    fun ੉ݫੌ۽Ӓੋ_पಁா੉झ_ੜޅػ੉ݫੌഋधੑ۱() { //given ੜޅ ػ ੉ݫੌਸ ੑ۱ೠ׮. //when ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. //then ਬബࢿ Ѩૐ पಁ & ੉ݫੌ Ѩૐ য়ܨ ݫद૑ܳ ࠁৈળ׮. } $PNQJMFFSSPS
  14. class LoginContract { interface View { } interface Presenter {

    } interface Repository { } } @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectEmail() { `when`(view.getInputEmail()).thenReturn("roy.kim@goodoc") presenter.onClickEmailLogin() verify(view).showMessageForIncorrectEmail() } fun getInputEmail(): String fun showMessageForIncorrectEmail() fun onClickEmailLogin() 5FTU$BTF %FGJOFDPOUSBDU
  15. class LoginActivity : AppCompatActivity(), LoginContract.View { private lateinit var presenter:

    LoginContract.Presenter override fun onCreate(savedInstanceState: Bundle?) { (……) } } class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter { } override fun getInputEmail(): String { TODO("not implemented”) } override fun showMessageForIncorrectEmail() { TODO("not implemented”) } override fun onClickEmailLogin() { } emailLoginButton.setOnClickListener { presenter.onClickEmailLogin() } 5FTU$BTF *NQMFNFOUDPOUSBDU
  16. class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter {

    override fun onClickEmailLogin() { } } @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectEmail() { `when`(view.getInputEmail()).thenReturn("roy.kim@goodoc") presenter.onClickEmailLogin() verify(view).showMessageForIncorrectEmail() } val inputEmail = view.getInputEmail() val patternString = "^[A-Z0-9a-z\\._%+-]+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2,}$" val pattern = Pattern.compile(patternString) val matcher = pattern.matcher(inputEmail) if(!matcher.matches()){ return } view.showMessageForIncorrectEmail() 5FTU$BTF 8SJUFPOMZFOPVHIDPEF
  17. class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter {

    override fun onClickEmailLogin() { val inputEmail = view.getInputEmail() val patternString = "^[A-Z0-9a-z\\._%+-]+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2,}$" val pattern = Pattern.compile(patternString) val matcher = pattern.matcher(inputEmail) if(!matcher.matches()){ view.showMessageForIncorrectEmail() return } } } 5FTU$BTF 8SJUFPOMZFOPVHIDPEF
  18. class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter {

    override fun onClickEmailLogin() { val inputEmail = view.getInputEmail() if(!matcher.matches()){ view.showMessageForIncorrectEmail() return } } } val patternString = "^[A-Z0-9a-z\\._%+-]+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2,}$" val pattern = Pattern.compile(patternString) val matcher = pattern.matcher(inputEmail) 5FTU$BTF 3FGBDUPSJOH
  19. class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter {

    override fun onClickEmailLogin() { val inputEmail = view.getInputEmail() } } class Validator { companion object { fun isValidEmail(email: String): Boolean { return matcher.matches() } } val patternString = "^[A-Z0-9a-z\\._%+-]+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2,}$" val pattern = Pattern.compile(patternString) val matcher = pattern.matcher(inputEmail) 5FTU$BTF 3FGBDUPSJOH
  20. class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter {

    override fun onClickEmailLogin() { val inputEmail = view.getInputEmail() if (!Validator.isValidEmail(inputEmail)) { view.showMessageForIncorrectEmail() return } } } class Validator { companion object { fun isValidEmail(email: String): Boolean { val patternString = "^[A-Z0-9a-z\\._%+-]+@([A-Za-z0-9-]+\\.)+[A-Za-z]{2,}$" val pattern = Pattern.compile(patternString) val matcher = pattern.matcher(email) return matcher.matches() } } 5FTU$BTF 3FGBDUPSJOH
  21. @Test fun ੉ݫੌ۽Ӓੋ_पಁா੉झ_ੜޅػ੉ݫੌഋधੑ۱() { ৢ߄ܲ ഋध੄ ੉ݫੌਸ ੑ۱ೠ׮. ੜޅ ػ

    ഋध੄ ࠺޻ߣഐܳ ੑ۱ೠ׮. ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. ੑ۱чਸ Ѩૐೠ׮. ࠺޻ߣഐ Ѩૐ য়ܨী ؀ೠ ݫद૑ܳ ࠁৈળ׮. } Email & Password ੑ۱ ۽Ӓੋ ߡౡ ௿ܼ ੑ۱ ч Ѩૐ Login API
 (Server) ۽Ӓੋ ࢿҕ য়ܨݫद૑ ࠁৈ઱ӝ 5FTU$BTF 4DFOBSJPੜޅػ࠺޻ߣഐഋधੑ۱
  22. @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectPassword() { } @Test fun ੉ݫੌ۽Ӓੋ_पಁா੉झ_ੜޅػ੉ݫੌഋधੑ۱() { ৢ߄ܲ

    ഋध੄ ੉ݫੌਸ ੑ۱ೠ׮. ੜޅ ػ ഋध੄ ࠺޻ߣഐܳ ੑ۱ೠ׮. ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. ੑ۱чਸ Ѩૐೠ׮. ࠺޻ߣഐ Ѩૐ য়ܨী ؀ೠ ݫद૑ܳ ࠁৈળ׮. } 5FTU$BTF 8SJUFUFTUDPEF
  23. @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectPassword() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("123") } @Test fun ੉ݫੌ۽Ӓੋ_पಁா੉झ_ੜޅػ੉ݫੌഋधੑ۱()

    { ৢ߄ܲ ഋध੄ ੉ݫੌਸ ੑ۱ೠ׮. ੜޅ ػ ഋध੄ ࠺޻ߣഐܳ ੑ۱ೠ׮. ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. ੑ۱чਸ Ѩૐೠ׮. ࠺޻ߣഐ Ѩૐ য়ܨী ؀ೠ ݫद૑ܳ ࠁৈળ׮. } 5FTU$BTF 8SJUFUFTUDPEF
  24. @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectPassword() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("123") presenter.onClickEmailLogin() } @Test fun

    ੉ݫੌ۽Ӓੋ_पಁா੉झ_ੜޅػ੉ݫੌഋधੑ۱() { ৢ߄ܲ ഋध੄ ੉ݫੌਸ ੑ۱ೠ׮. ੜޅ ػ ഋध੄ ࠺޻ߣഐܳ ੑ۱ೠ׮. ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. ੑ۱чਸ Ѩૐೠ׮. ࠺޻ߣഐ Ѩૐ য়ܨী ؀ೠ ݫद૑ܳ ࠁৈળ׮. } 5FTU$BTF 8SJUFUFTUDPEF
  25. @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectPassword() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("123") presenter.onClickEmailLogin() verify(view).showMessageForIncorrectPassword() } @Test

    fun ੉ݫੌ۽Ӓੋ_पಁா੉झ_ੜޅػ੉ݫੌഋधੑ۱() { ৢ߄ܲ ഋध੄ ੉ݫੌਸ ੑ۱ೠ׮. ੜޅ ػ ഋध੄ ࠺޻ߣഐܳ ੑ۱ೠ׮. ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. ੑ۱чਸ Ѩૐೠ׮. ࠺޻ߣഐ Ѩૐ য়ܨী ؀ೠ ݫद૑ܳ ࠁৈળ׮. } 5FTU$BTF 8SJUFUFTUDPEF
  26. class LoginContract { interface View { fun getInputEmail(): String fun

    showMessageForIncorrectEmail() } interface Presenter { fun onClickEmailLogin() } interface Repository { } } @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectPassword() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("123") presenter.onClickEmailLogin() verify(view).showMessageForIncorrectPassword() } 5FTU$BTF %FGJOFDPOUSBDU
  27. @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectPassword() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("123") presenter.onClickEmailLogin() verify(view).showMessageForIncorrectPassword() } class

    LoginContract { interface View { fun getInputEmail(): String fun showMessageForIncorrectEmail() fun getInputPassword(): String fun showMessageForIncorrectPassword() } interface Presenter { fun onClickEmailLogin() } interface Repository { } } 5FTU$BTF %FGJOFDPOUSBDU
  28. class LoginActivity : AppCompatActivity(), LoginContract.View { private lateinit var presenter:

    LoginContract.Presenter override fun onCreate(savedInstanceState: Bundle?) {……} override fun getInputEmail(): String { TODO("not implemented") } override fun showMessageForIncorrectEmail() { TODO("not implemented") } } 5FTU$BTF *NQMFNFOUDPOUSBDU
  29. class LoginActivity : AppCompatActivity(), LoginContract.View { private lateinit var presenter:

    LoginContract.Presenter override fun onCreate(savedInstanceState: Bundle?) {……} override fun getInputEmail(): String { TODO("not implemented") } override fun showMessageForIncorrectEmail() { TODO("not implemented") } override fun getInputPassword(): String { TODO("not implemented") } override fun showMessageForIncorrectPassword() { TODO("not implemented") } } 5FTU$BTF *NQMFNFOUDPOUSBDU
  30. class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter {

    override fun onClickEmailLogin() { val inputEmail = view.getInputEmail() if (!Validator.isValidEmail(inputEmail)) { view.showMessageForIncorrectEmail() return } } } @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectPassword() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("123") presenter.onClickEmailLogin() verify(view).showMessageForIncorrectPassword() } 5FTU$BTF 8SJUFPOMZFOPVHIDPEF
  31. @Test fun test_onClickEmailLogin__FailLogin__EnterIncorrectPassword() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("123") presenter.onClickEmailLogin() verify(view).showMessageForIncorrectPassword() } val

    inputPassword = view.getInputPassword() if (!Validator.isValidPassword(inputPassword)) { view.showMessageForIncorrectPassword() return } 5FTU$BTF 8SJUFPOMZFOPVHIDPEF class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter { override fun onClickEmailLogin() { val inputEmail = view.getInputEmail() if (!Validator.isValidEmail(inputEmail)) { view.showMessageForIncorrectEmail() return } } }
  32. 5FTU$BTF4DFOBSJP۽Ӓੋࢿҕ Email & Password ੑ۱ ۽Ӓੋ ߡౡ ௿ܼ ੑ۱ ч

    Ѩૐ Login API
 (Server) ۽Ӓੋ ࢿҕ য়ܨݫद૑ ࠁৈ઱ӝ @Test fun ੉ݫੌ۽Ӓੋ_ࢿҕ() { ৢ߄ܲ ഋध੄ ੉ݫੌਸ ੑ۱ೠ׮. ৢ߄ܲ ഋध੄ ࠺޻ߣഐܳ ੑ۱ೠ׮. ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. ੑ۱чਸ Ѩૐೠ׮. ఃࠁ٘ܳ ऀӟ׮. ۽٬ ׮੉঴۽Ӓܳ ࠁৈળ׮. ࢲߡী ই੉٣/࠺޻ߣഐܳ ੹׳೧ࢲ ۽Ӓੋ ୊ܻೠ׮. (۽Ӓੋ ࢿҕ) ۽٬ ׮੉঴۽Ӓܳ х୸׮. ۽Ӓੋ ࢿҕ ݫद૑ܳ ࠁৈળ׮. ۽Ӓੋ ചݶਸ ઙܐೠ׮. }
  33. @Test fun test_onClickEmailLogin__SuccessLogin() { ৢ߄ܲ ഋध੄ ੉ݫੌਸ ੑ۱ೠ׮. ৢ߄ܲ ഋध੄

    ࠺޻ߣഐܳ ੑ۱ೠ׮. ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. ੑ۱чਸ Ѩૐೠ׮. ఃࠁ٘ܳ ऀӟ׮. ۽٬ ׮੉঴۽Ӓܳ ࠁৈળ׮. ࢲߡী ই੉٣/࠺޻ߣഐܳ ੹׳೧ࢲ ۽Ӓੋ ୊ܻೠ׮. (۽Ӓੋ ࢿҕ) ۽٬ ׮੉঴۽Ӓܳ х୸׮. ۽Ӓੋ ࢿҕ ݫद૑ܳ ࠁৈળ׮. ۽Ӓੋ ചݶਸ ઙܐೠ׮. } 5FTU$BTF8SJUFUFTUDPEF
  34. @Test fun test_onClickEmailLogin__SuccessLogin() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮.

    ੑ۱чਸ Ѩૐೠ׮. ఃࠁ٘ܳ ऀӟ׮. ۽٬ ׮੉঴۽Ӓܳ ࠁৈળ׮. ࢲߡী ই੉٣/࠺޻ߣഐܳ ੹׳೧ࢲ ۽Ӓੋ ୊ܻೠ׮. (۽Ӓੋ ࢿҕ) ۽٬ ׮੉঴۽Ӓܳ х୸׮. ۽Ӓੋ ࢿҕ ݫद૑ܳ ࠁৈળ׮. ۽Ӓੋ ചݶਸ ઙܐೠ׮. } 5FTU$BTF8SJUFUFTUDPEF
  35. @Test fun test_onClickEmailLogin__SuccessLogin() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮.

    ੑ۱чਸ Ѩૐೠ׮. ఃࠁ٘ܳ ऀӟ׮. ۽٬ ׮੉঴۽Ӓܳ ࠁৈળ׮. ࢲߡী ই੉٣/࠺޻ߣഐܳ ੹׳೧ࢲ ۽Ӓੋ ୊ܻೠ׮. (۽Ӓੋ ࢿҕ) ۽٬ ׮੉঴۽Ӓܳ х୸׮. ۽Ӓੋ ࢿҕ ݫद૑ܳ ࠁৈળ׮. ۽Ӓੋ ചݶਸ ઙܐೠ׮. } 5FTU$BTF8SJUFUFTUDPEF
  36. @Test fun test_onClickEmailLogin__SuccessLogin() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") presenter.onClickEmailLogin() ੑ۱чਸ Ѩૐೠ׮. ఃࠁ٘ܳ

    ऀӟ׮. ۽٬ ׮੉঴۽Ӓܳ ࠁৈળ׮. ࢲߡী ই੉٣/࠺޻ߣഐܳ ੹׳೧ࢲ ۽Ӓੋ ୊ܻೠ׮. (۽Ӓੋ ࢿҕ) ۽٬ ׮੉঴۽Ӓܳ х୸׮. ۽Ӓੋ ࢿҕ ݫद૑ܳ ࠁৈળ׮. ۽Ӓੋ ചݶਸ ઙܐೠ׮. } 5FTU$BTF8SJUFUFTUDPEF
  37. @Test fun test_onClickEmailLogin__SuccessLogin() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") presenter.onClickEmailLogin() ੑ۱чਸ Ѩૐೠ׮. ఃࠁ٘ܳ

    ऀӟ׮. ۽٬ ׮੉঴۽Ӓܳ ࠁৈળ׮. ࢲߡী ই੉٣/࠺޻ߣഐܳ ੹׳೧ࢲ ۽Ӓੋ ୊ܻೠ׮. (۽Ӓੋ ࢿҕ) ۽٬ ׮੉঴۽Ӓܳ х୸׮. ۽Ӓੋ ࢿҕ ݫद૑ܳ ࠁৈળ׮. ۽Ӓੋ ചݶਸ ઙܐೠ׮. } 5FTU$BTF8SJUFUFTUDPEF
  38. @Test fun test_onClickEmailLogin__SuccessLogin() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") presenter.onClickEmailLogin() inOrder.verify(view).hideSoftKeyboard() inOrder.verify(view).showLoadingDialog() ࢲߡী

    ই੉٣/࠺޻ߣഐܳ ੹׳೧ࢲ ۽Ӓੋ ୊ܻೠ׮. (۽Ӓੋ ࢿҕ) ۽٬ ׮੉঴۽Ӓܳ х୸׮. ۽Ӓੋ ࢿҕ ݫद૑ܳ ࠁৈળ׮. ۽Ӓੋ ചݶਸ ઙܐೠ׮. } 5FTU$BTF8SJUFUFTUDPEF
  39. @Test fun test_onClickEmailLogin__SuccessLogin() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") presenter.onClickEmailLogin() inOrder.verify(view).hideSoftKeyboard() inOrder.verify(view).showLoadingDialog() ࢲߡী

    ই੉٣/࠺޻ߣഐܳ ੹׳೧ࢲ ۽Ӓੋ ୊ܻೠ׮. (۽Ӓੋ ࢿҕ) ۽٬ ׮੉঴۽Ӓܳ х୸׮. ۽Ӓੋ ࢿҕ ݫद૑ܳ ࠁৈળ׮. ۽Ӓੋ ചݶਸ ઙܐೠ׮. } 5FTU$BTF8SJUFUFTUDPEF
  40. @Captor private lateinit var loginResultListener: ArgumentCaptor<LoginResultCallback> @Test fun test_onClickEmailLogin__SuccessLogin() {

    val mockUserInfo = mock<UserInfo> { } `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") presenter.onClickEmailLogin() inOrder.verify(view).hideSoftKeyboard() inOrder.verify(view).showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() inOrder.verify(repository).login(eq(email), eq(password), loginResultListener.capture()) loginResultListener.firstValue.onSuccess(mockUserInfo) ۽٬ ׮੉঴۽Ӓܳ х୸׮. ۽Ӓੋ ࢿҕ ݫद૑ܳ ࠁৈળ׮. ۽Ӓੋ ചݶਸ ઙܐೠ׮. } 5FTU$BTF8SJUFUFTUDPEF
  41. @Captor private lateinit var loginResultListener: ArgumentCaptor<LoginResultCallback> @Test fun test_onClickEmailLogin__SuccessLogin() {

    val mockUserInfo = mock<UserInfo> { } `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") presenter.onClickEmailLogin() inOrder.verify(view).hideSoftKeyboard() inOrder.verify(view).showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() inOrder.verify(repository).login(eq(email), eq(password), loginResultListener.capture()) loginResultListener.firstValue.onSuccess(mockUserInfo) ۽٬ ׮੉঴۽Ӓܳ х୸׮. ۽Ӓੋ ࢿҕ ݫद૑ܳ ࠁৈળ׮. ۽Ӓੋ ചݶਸ ઙܐೠ׮. } 5FTU$BTF8SJUFUFTUDPEF
  42. @Captor private lateinit var loginResultListener: ArgumentCaptor<LoginResultCallback> @Test fun test_onClickEmailLogin__SuccessLogin() {

    val mockUserInfo = mock<UserInfo> { } `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") presenter.onClickEmailLogin() inOrder.verify(view).hideSoftKeyboard() inOrder.verify(view).showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() inOrder.verify(repository).login(eq(email), eq(password), loginResultListener.capture()) loginResultListener.firstValue.onSuccess(mockUserInfo) inOrder.verify(view).hideLoadingDialog() inOrder.verify(view).showMessageForSuccessLogin() inOrder.verify(view).finishActivity() } 5FTU$BTF8SJUFUFTUDPEF
  43. @Captor private lateinit var loginResultListener: ArgumentCaptor<LoginResultCallback> @Test fun test_onClickEmailLogin__SuccessLogin() {

    val mockUserInfo = mock<UserInfo> { } `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") presenter.onClickEmailLogin() inOrder.verify(view).hideSoftKeyboard() inOrder.verify(view).showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() inOrder.verify(repository).login(eq(email), eq(password), loginResultListener.capture()) loginResultListener.firstValue.onSuccess(mockUserInfo) inOrder.verify(view).hideLoadingDialog() inOrder.verify(view).showMessageForSuccessLogin() inOrder.verify(view).finishActivity() } 5FTU$BTF8SJUFUFTUDPEF
  44. @Captor private lateinit var loginResultListener: ArgumentCaptor<LoginResultCallback> @Test fun test_onClickEmailLogin__SuccessLogin() {

    val mockUserInfo = mock<UserInfo> { } `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") presenter.onClickEmailLogin() inOrder.verify(view).hideSoftKeyboard() inOrder.verify(view).showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() inOrder.verify(repository).login(eq(email), eq(password), loginResultListener.capture()) loginResultListener.firstValue.onSuccess(mockUserInfo) inOrder.verify(view).hideLoadingDialog() inOrder.verify(view).showMessageForSuccessLogin() inOrder.verify(view).finishActivity() } 5FTU$BTF$PNQJMFFSSPS
  45. class LoginContract { interface View { fun getInputEmail(): String fun

    showMessageForIncorrectEmail() fun getInputPassword(): String fun showMessageForIncorrectPassword() fun hideSoftKeyboard() fun showLoadingDialog() fun hideLoadingDialog() fun showMessageForSuccessLogin() fun finishActivity() } interface Presenter { fun onClickEmailLogin() } interface Repository { fun login(email: String, password: String, callback: LoginResultCallback) } } 5FTU$BTF%FGJOFDPOUSBDU interface LoginResultCallback { fun onSuccess(userInfo: UserInfo) fun onFail(code: Int, message: String) }
  46. class LoginActivity : AppCompatActivity(), LoginContract.View { (……) override fun hideSoftKeyboard()

    { TODO("not implemented") } override fun showLoadingDialog() { TODO("not implemented") } override fun hideLoadingDialog() { TODO("not implemented") } override fun showMessageForSuccessLogin() { TODO("not implemented") } override fun finishActivity() { TODO("not implemented") } } 5FTU$BTF*NQMFNFOUDPOUSBDU
  47. 5FTU$BTF*NQMFNFOUDPOUSBDU class LoginRepository : LoginContract.Repository { override fun login(email: String,

    password: String, callback: LoginResultCallback) { TODO("not implemented") } }
  48. 5FTU$BTF8SJUFPOMZFOPVHIDPEF class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter

    { override fun onClickEmailLogin() { (… ੉ݫੌ/࠺޻ߣഐ ੑ۱ ч Ѩૐ…) inOrder.verify(view).hideSoftKeyboard() inOrder.verify(view).showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() inOrder.verify(repository).login(eq(email), eq(password), loginResultListener.capture()) loginResultListener.firstValue.onSuccess(mockUserInfo) inOrder.verify(view).hideLoadingDialog() inOrder.verify(view).showMessageForSuccessLogin() inOrder.verify(view).finishActivity() } }
  49. 5FTU$BTF8SJUFPOMZFOPVHIDPEF class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter

    { override fun onClickEmailLogin() { (… ੉ݫੌ/࠺޻ߣഐ ੑ۱ ч Ѩૐ…) inOrder.verify(view).hideSoftKeyboard() inOrder.verify(view).showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() inOrder.verify(repository).login(eq(email), eq(password), loginResultListener.capture()) loginResultListener.firstValue.onSuccess(mockUserInfo) inOrder.verify(view).hideLoadingDialog() inOrder.verify(view).showMessageForSuccessLogin() inOrder.verify(view).finishActivity() } }
  50. 5FTU$BTF8SJUFPOMZFOPVHIDPEF class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter

    { override fun onClickEmailLogin() { (… ੉ݫੌ/࠺޻ߣഐ ੑ۱ ч Ѩૐ…) view.hideSoftKeyboard() view.showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() inOrder.verify(repository).login(eq(email), eq(password), loginResultListener.capture()) loginResultListener.firstValue.onSuccess(mockUserInfo) inOrder.verify(view).hideLoadingDialog() inOrder.verify(view).showMessageForSuccessLogin() inOrder.verify(view).finishActivity() } }
  51. 5FTU$BTF8SJUFPOMZFOPVHIDPEF class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter

    { override fun onClickEmailLogin() { (… ੉ݫੌ/࠺޻ߣഐ ੑ۱ ч Ѩૐ…) view.hideSoftKeyboard() view.showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() inOrder.verify(repository).login(eq(email), eq(password), loginResultListener.capture()) loginResultListener.firstValue.onSuccess(mockUserInfo) inOrder.verify(view).hideLoadingDialog() inOrder.verify(view).showMessageForSuccessLogin() inOrder.verify(view).finishActivity() } }
  52. 5FTU$BTF8SJUFPOMZFOPVHIDPEF class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter

    { override fun onClickEmailLogin() { (… ੉ݫੌ/࠺޻ߣഐ ੑ۱ ч Ѩૐ…) view.hideSoftKeyboard() view.showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() repository.login(inputEmail, inputPassword, object : LoginResultCallback { override fun onSuccess(userInfo: UserInfo) { inOrder.verify(view).hideLoadingDialog() inOrder.verify(view).showMessageForSuccessLogin() inOrder.verify(view).finishActivity() } override fun onFail(code: Int, message: String) { TODO("not implemented") } }) } }
  53. 5FTU$BTF8SJUFPOMZFOPVHIDPEF class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter

    { override fun onClickEmailLogin() { (… ੉ݫੌ/࠺޻ߣഐ ੑ۱ ч Ѩૐ…) view.hideSoftKeyboard() view.showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() repository.login(inputEmail, inputPassword, object : LoginResultCallback { override fun onSuccess(userInfo: UserInfo) { inOrder.verify(view).hideLoadingDialog() inOrder.verify(view).showMessageForSuccessLogin() inOrder.verify(view).finishActivity() } override fun onFail(code: Int, message: String) { TODO("not implemented") } }) } }
  54. 5FTU$BTF8SJUFPOMZFOPVHIDPEF class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter

    { override fun onClickEmailLogin() { (… ੉ݫੌ/࠺޻ߣഐ ੑ۱ ч Ѩૐ…) view.hideSoftKeyboard() view.showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() repository.login(inputEmail, inputPassword, object : LoginResultCallback { override fun onSuccess(userInfo: UserInfo) { view.hideLoadingDialog() view.showMessageForSuccessLogin() view.finishActivity() } override fun onFail(code: Int, message: String) { TODO("not implemented") } }) } }
  55. 5FTU$BTF8SJUFPOMZFOPVHIDPEF class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter

    { override fun onClickEmailLogin() { (… ੉ݫੌ/࠺޻ߣഐ ੑ۱ ч Ѩૐ…) view.hideSoftKeyboard() view.showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() repository.login(inputEmail, inputPassword, object : LoginResultCallback { override fun onSuccess(userInfo: UserInfo) { view.hideLoadingDialog() view.showMessageForSuccessLogin() view.finishActivity() } override fun onFail(code: Int, message: String) { TODO("not implemented") } }) } }
  56. 5FTU$BTF4DFOBSJP۽Ӓੋपಁ Email & Password ੑ۱ ۽Ӓੋ ߡౡ ௿ܼ ੑ۱ ч

    Ѩૐ Login API
 (Server) ۽Ӓੋ ࢿҕ য়ܨݫद૑ ࠁৈ઱ӝ @Test fun ੉ݫੌ۽Ӓੋ_पಁ_оੑغ૑ঋ਷ഥਗ੿ࠁ() { ৢ߄ܲ ഋध੄ ੉ݫੌਸ ੑ۱ೠ׮. ৢ߄ܲ ഋध੄ ࠺޻ߣഐܳ ੑ۱ೠ׮. ੉ݫੌ ۽Ӓੋ ߡౡ ௿ܼೠ׮. ੑ۱чਸ Ѩૐೠ׮. ఃࠁ٘ܳ ऀӟ׮. ۽٬ ׮੉঴۽Ӓܳ ࠁৈળ׮. ࢲߡী ই੉٣/࠺޻ߣഐܳ ੹׳೧ࢲ ۽Ӓੋ ୊ܻೠ׮. (۽Ӓੋ पಁ - ࢲߡ য়ܨ) ۽٬ ׮੉঴۽Ӓܳ х୸׮. ۽Ӓੋ पಁ ݫद૑ܳ ࠁৈળ׮. }
  57. 5FTU$BTF8SJUFUFTUDPEF @Test fun test_onClickEmailLogin__FailLogin() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") val errorCode =

    1021 val errorMsg = "Server error message." presenter.onClickEmailLogin() verify(view).hideSoftKeyboard() verify(view).showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() verify(repository).login(eq(email), eq(password), loginResultListener.capture()) loginResultListener.firstValue.onFail(errorCode, errorMsg) verify(view).hideLoadingDialog() verify(view).showMessageForFailLogin(errorMsg) }
  58. 5FTU$BTF$PNQJMFFSSPS @Test fun test_onClickEmailLogin__FailLogin() { `when`(view.getInputEmail()).thenReturn("[email protected]") `when`(view.getInputPassword()).thenReturn("@Abcd1234") val errorCode =

    1021 val errorMsg = "Server error message." presenter.onClickEmailLogin() verify(view).hideSoftKeyboard() verify(view).showLoadingDialog() val email = view.getInputEmail() val password = view.getInputPassword() verify(repository).login(eq(email), eq(password), loginResultListener.capture()) loginResultListener.firstValue.onFail(errorCode, errorMsg) verify(view).hideLoadingDialog() verify(view).showMessageForFailLogin(errorMsg) }
  59. 5FTU$BTF%FGJOFDPOUSBDU class LoginContract { interface View { fun getInputEmail(): String

    fun showMessageForIncorrectEmail() fun getInputPassword(): String fun showMessageForIncorrectPassword() fun hideSoftKeyboard() fun showLoadingDialog() fun hideLoadingDialog() fun showMessageForSuccessLogin() fun finishActivity() fun showMessageForFailLogin(errorMsg: String) } interface Presenter { fun onClickEmailLogin() } interface Repository { fun login(email: String, password: String, callback: LoginResultCallback) } }
  60. 5FTU$BTF8SJUFPOMZFOPVHIDPEF class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter

    { override fun onClickEmailLogin() { (… ੉ݫੌ/࠺޻ߣഐ ੑ۱ ч Ѩૐ…) view.hideSoftKeyboard() view.showLoadingDialog() repository.login(inputEmail, inputPassword, object : LoginResultCallback { override fun onSuccess(userInfo: UserInfo) { view.hideLoadingDialog() view.showMessageForSuccessLogin() view.finishActivity() } override fun onFail(code: Int, message: String) { TODO("not implemented") } }) } }
  61. 5FTU$BTF8SJUFPOMZFOPVHIDPEF class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter

    { override fun onClickEmailLogin() { (… ੉ݫੌ/࠺޻ߣഐ ੑ۱ ч Ѩૐ…) view.hideSoftKeyboard() view.showLoadingDialog() repository.login(inputEmail, inputPassword, object : LoginResultCallback { override fun onSuccess(userInfo: UserInfo) { view.hideLoadingDialog() view.showMessageForSuccessLogin() view.finishActivity() } override fun onFail(code: Int, message: String) { view.hideLoadingDialog() view.showMessageForFailLogin(message) } }) } }
  62. class LoginPresenter(val view: LoginContract.View, val repository: LoginContract.Repository) : LoginContract.Presenter {

    override fun onClickEmailLogin() { val inputEmail = view.getInputEmail() if (!Validator.isValidEmail(inputEmail)) { view.showMessageForIncorrectEmail() return } val inputPassword = view.getInputPassword() if (!Validator.isValidPassword(inputPassword)) { view.showMessageForIncorrectPassword() return } view.hideSoftKeyboard() view.showLoadingDialog() repository.login(inputEmail, inputPassword, object : LoginResultCallback { override fun onSuccess(userInfo: UserInfo) { view.hideLoadingDialog() view.showMessageForSuccessLogin() view.finishActivity() } override fun onFail(code: Int, message: String) { view.hideLoadingDialog() view.showMessageForFailLogin(message) } }) } }
  63. class LoginRepository : LoginContract.Repository { override fun login(email: String, password:

    String, callback: LoginResultCallback) { TODO("not implemented") } }
  64. class LoginRepository : LoginContract.Repository { override fun login(email: String, password:

    String, callback: LoginResultCallback) { val client = OkHttpClient() val request = // TODO build POST request… client.newCall(request).execute() } }
  65. class LoginActivity : AppCompatActivity(), LoginContract.View { private lateinit var presenter:

    LoginContract.Presenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) presenter = LoginPresenter(this, Injector.provideLoginRepository()) textViewEmailLogin.setOnClickListener { presenter.onClickEmailLogin() } } override fun getInputEmail(): String { TODO("not implemented") } override fun showMessageForIncorrectEmail() { TODO("not implemented") } override fun getInputPassword(): String { TODO("not implemented") } override fun showMessageForIncorrectPassword() { TODO("not implemented") } override fun hideSoftKeyboard() { TODO("not implemented") } override fun showLoadingDialog() { TODO("not implemented") } override fun hideLoadingDialog() { TODO("not implemented") } override fun showMessageForSuccessLogin() { TODO("not implemented") } override fun finishActivity() { TODO("not implemented") } override fun showMessageForFailLogin(errorMsg: String) { TODO("not implemented") } }
  66. class LoginActivity : AppCompatActivity(), LoginContract.View { private lateinit var presenter:

    LoginContract.Presenter override fun onCreate(savedInstanceState: Bundle?) {……} override fun getInputEmail() = editTextEmail.text.toString() override fun showMessageForIncorrectEmail() { showToast(R.string.msg_for_incorrect_email) } override fun getInputPassword() = editTextPassword.text.toString() override fun showMessageForIncorrectPassword() { showToast(R.string.msg_for_incorrect_password) } override fun hideSoftKeyboard() { KeyboardManager.hideKeyboard(editTextEmail) } override fun showLoadingDialog() { ProgressDialog(this).show() } override fun hideLoadingDialog() { ProgressDialog(this).hide() } override fun showMessageForSuccessLogin() { showToast(R.string.msg_for_success_login) } override fun finishActivity() { finish() } override fun showMessageForFailLogin(errorMsg: String) { showToast(R.string.msg_for_fail_login) } private fun showToast(@StringRes resId: Int) { Toast.makeText(this, resId, Toast.LENGTH_SHORT).show() } }
  67. 6*UFTUDPEFTUSVDUVSF 1. Find a view 2. Perform an action 3.

    Inspect the result onView(Matcher) .perform(ViewAction) .check(ViewAssertion)
  68. 6*5FTU4DFOBSJP class LoginActivityTest { } @get:Rule val loginActivityRule = ActivityTestRule(LoginActivity::class.java,

    true, true) @Test fun test_SuccessEmailLogin() { } ചݶী Email ੑ۱ହ੉ ࠁ੉ח૑ ഛੋೠ׮. ചݶী Password ੑ۱ହ੉ ࠁ੉ח૑ ഛੋೠ׮. Email ੑ۱ହী ഥਗ ੉ݫੌਸ ੑ۱ೠ׮. Password ੑ۱ହী ഥਗ ࠺޻ߣഐܳ ੑ۱ೠ׮. ۽Ӓੋ ߡౡਸ ௿ܼೠ׮. ۽Ӓੋ੉ ࢿҕ೮׮ח ݫद૑ܳ ࠁৈળ׮.
  69. 6*5FTU4DFOBSJP class LoginActivityTest { @get:Rule val loginActivityRule = ActivityTestRule(LoginActivity::class.java, true,

    true) @Test fun test_SuccessEmailLogin() { ചݶী Email ੑ۱ହ੉ ࠁ੉ח૑ ഛੋೠ׮. ചݶী Password ੑ۱ହ੉ ࠁ੉ח૑ ഛੋೠ׮. Email ੑ۱ହী ഥਗ ੉ݫੌਸ ੑ۱ೠ׮. Password ੑ۱ହী ഥਗ ࠺޻ߣഐܳ ੑ۱ೠ׮. ۽Ӓੋ ߡౡਸ ௿ܼೠ׮. ۽Ӓੋ੉ ࢿҕ೮׮ח ݫद૑ܳ ࠁৈળ׮. } }
  70. 6*5FTU4DFOBSJP class LoginActivityTest { @get:Rule val loginActivityRule = ActivityTestRule(LoginActivity::class.java, true,

    true) @Test fun test_SuccessEmailLogin() { onView(withId(R.id.editTextEmail)).check(matches(isDisplayed())) onView(withId(R.id.editTextPassword)).check(matches(isDisplayed())) Email ੑ۱ହী ഥਗ ੉ݫੌਸ ੑ۱ೠ׮. Password ੑ۱ହী ഥਗ ࠺޻ߣഐܳ ੑ۱ೠ׮. ۽Ӓੋ ߡౡਸ ௿ܼೠ׮. ۽Ӓੋ੉ ࢿҕ೮׮ח ݫद૑ܳ ࠁৈળ׮. } }
  71. 6*5FTU4DFOBSJP class LoginActivityTest { @get:Rule val loginActivityRule = ActivityTestRule(LoginActivity::class.java, true,

    true) @Test fun test_SuccessEmailLogin() { onView(withId(R.id.editTextEmail)).check(matches(isDisplayed())) onView(withId(R.id.editTextPassword)).check(matches(isDisplayed())) Email ੑ۱ହী ഥਗ ੉ݫੌਸ ੑ۱ೠ׮. Password ੑ۱ହী ഥਗ ࠺޻ߣഐܳ ੑ۱ೠ׮. ۽Ӓੋ ߡౡਸ ௿ܼೠ׮. ۽Ӓੋ੉ ࢿҕ೮׮ח ݫद૑ܳ ࠁৈળ׮. } }
  72. 6*5FTU4DFOBSJP class LoginActivityTest { @get:Rule val loginActivityRule = ActivityTestRule(LoginActivity::class.java, true,

    true) @Test fun test_SuccessEmailLogin() { onView(withId(R.id.editTextEmail)).check(matches(isDisplayed())) onView(withId(R.id.editTextPassword)).check(matches(isDisplayed())) onView(withId(R.id.editTextEmail)).perform(typeText("[email protected]")) onView(withId(R.id.editTextPassword)).perform(typeText("@Abcd1234")) ۽Ӓੋ ߡౡਸ ௿ܼೠ׮. ۽Ӓੋ੉ ࢿҕ೮׮ח ݫद૑ܳ ࠁৈળ׮. } }
  73. 6*5FTU4DFOBSJP class LoginActivityTest { @get:Rule val loginActivityRule = ActivityTestRule(LoginActivity::class.java, true,

    true) @Test fun test_SuccessEmailLogin() { onView(withId(R.id.editTextEmail)).check(matches(isDisplayed())) onView(withId(R.id.editTextPassword)).check(matches(isDisplayed())) onView(withId(R.id.editTextEmail)).perform(typeText("[email protected]")) onView(withId(R.id.editTextPassword)).perform(typeText("@Abcd1234")) ۽Ӓੋ ߡౡਸ ௿ܼೠ׮. ۽Ӓੋ੉ ࢿҕ೮׮ח ݫद૑ܳ ࠁৈળ׮. } }
  74. 6*5FTU4DFOBSJP class LoginActivityTest { @get:Rule val loginActivityRule = ActivityTestRule(LoginActivity::class.java, true,

    true) @Test fun test_SuccessEmailLogin() { onView(withId(R.id.editTextEmail)).check(matches(isDisplayed())) onView(withId(R.id.editTextPassword)).check(matches(isDisplayed())) onView(withId(R.id.editTextEmail)).perform(typeText("[email protected]")) onView(withId(R.id.editTextPassword)).perform(typeText("@Abcd1234")) onView(withId(R.id.emailLoginButton)).perform(click()) ۽Ӓੋ੉ ࢿҕ೮׮ח ݫद૑ܳ ࠁৈળ׮. } }
  75. 6*5FTU4DFOBSJP class LoginActivityTest { @get:Rule val loginActivityRule = ActivityTestRule(LoginActivity::class.java, true,

    true) @Test fun test_SuccessEmailLogin() { onView(withId(R.id.editTextEmail)).check(matches(isDisplayed())) onView(withId(R.id.editTextPassword)).check(matches(isDisplayed())) onView(withId(R.id.editTextEmail)).perform(typeText("[email protected]")) onView(withId(R.id.editTextPassword)).perform(typeText("@Abcd1234")) onView(withId(R.id.emailLoginButton)).perform(click()) ۽Ӓੋ੉ ࢿҕ೮׮ח ݫद૑ܳ ࠁৈળ׮. } }
  76. 8SJUF6*UFTUDPEF class LoginActivityTest { @get:Rule val loginActivityRule = ActivityTestRule(LoginActivity::class.java, true,

    true) @Test fun test_SuccessEmailLogin() { onView(withId(R.id.editTextEmail)).check(matches(isDisplayed())) onView(withId(R.id.editTextPassword)).check(matches(isDisplayed())) onView(withId(R.id.editTextEmail)).perform(typeText("[email protected]")) onView(withId(R.id.editTextPassword)).perform(typeText("@Abcd1234")) onView(withId(R.id.emailLoginButton)).perform(click()) onView(withText(R.string.msg_for_success_login)).inRoot(ToastMatcher()) .check(matches(isDisplayed())) } }
  77. class LoginActivityTest { @get:Rule val loginActivityRule = ActivityTestRule(LoginActivity::class.java, true, true)

    @Test fun test_SuccessEmailLogin() { onView(withId(R.id.editTextEmail)).check(matches(isDisplayed())) onView(withId(R.id.editTextPassword)).check(matches(isDisplayed())) onView(withId(R.id.editTextEmail)).perform(typeText("[email protected]")) onView(withId(R.id.editTextPassword)).perform(typeText("@Abcd1234")) onView(withId(R.id.emailLoginButton)).perform(click()) onView(withText(R.string.msg_for_success_login)).inRoot(ToastMatcher()).check(matches(isDisplayed())) } @Test fun test_FailEmailLogin() { onView(withId(R.id.editTextEmail)).check(matches(isDisplayed())) onView(withId(R.id.editTextPassword)).check(matches(isDisplayed())) onView(withId(R.id.editTextEmail)).perform(typeText("[email protected]")) onView(withId(R.id.editTextPassword)).perform(typeText("@Abcd1")) onView(withId(R.id.emailLoginButton)).perform(click()) onView(withText(R.string.msg_for_fail_login)).inRoot(ToastMatcher()).check(matches(isDisplayed())) } }
  78. class LoginActivityTest { @get:Rule val loginActivityRule = ActivityTestRule(LoginActivity::class.java, true, true)

    @Test fun test_SuccessEmailLogin() { onView(withId(R.id.editTextEmail)).check(matches(isDisplayed())) onView(withId(R.id.editTextPassword)).check(matches(isDisplayed())) onView(withId(R.id.editTextEmail)).perform(typeText("[email protected]")) onView(withId(R.id.editTextPassword)).perform(typeText("@Abcd1234")) onView(withId(R.id.emailLoginButton)).perform(click()) onView(withText(R.string.msg_for_success_login)).inRoot(ToastMatcher()).check(matches(isDisplayed())) } @Test fun test_FailEmailLogin() { onView(withId(R.id.editTextEmail)).check(matches(isDisplayed())) onView(withId(R.id.editTextPassword)).check(matches(isDisplayed())) onView(withId(R.id.editTextEmail)).perform(typeText("[email protected]")) onView(withId(R.id.editTextPassword)).perform(typeText("@Abcd1")) onView(withId(R.id.emailLoginButton)).perform(click()) onView(withText(R.string.msg_for_fail_login)).inRoot(ToastMatcher()).check(matches(isDisplayed())) } }
  79. 1SFTFOUFS 7JFX 3FQPTJUPSZ Test Complete Testing Real Object %# 4FSWFS

    ID : [email protected] PW : @Abcd1234 э਷ੑ۱ী೦࢚э਷Ѿҗܳ߈ജೞח௏٘
  80. 1SFTFOUFS 7JFX 3FQPTJUPSZ Test Complete Testing Real Object %# 4FSWFS

    ID : [email protected] PW : @Abcd1234 'BLF 3FQPTJUPSZ э਷ੑ۱ী೦࢚э਷Ѿҗܳ߈ജೞח௏٘
  81. 'MBWPS৬%*ܳ੉ਊೠNPDL3FQPTJUPSZࢤࢿ class LoginActivity : AppCompatActivity(), LoginContract.View { private lateinit var

    presenter: LoginContract.Presenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) presenter = LoginPresenter(this, Injector.provideLoginRepository()) emailLoginButton.setOnClickListener { presenter.onClickEmailLogin() } } }
  82. 'MBWPS৬%*ܳ੉ਊೠNPDL3FQPTJUPSZࢤࢿ package com.droidknights.tdd import com.droidknights.tdd.data.FakeLoginRepository class Injector { companion object

    { fun provideLoginRepository(): LoginContract.Repository { return FakeLoginRepository() } } } package com.droidknights.tdd class Injector { companion object { fun provideLoginRepository(): LoginContract.Repository { return LoginRepository() } } }
  83. 'MBWPS৬%*ܳ੉ਊೠNPDL3FQPTJUPSZࢤࢿ class LoginRepository : LoginContract.Repository { override fun login(email: String,

    password: String, callback: LoginResultCallback) { val client = OkHttpClient() val request = // TODO build POST request… client.newCall(request).execute() } } class FakeLoginRepository : LoginContract.Repository { override fun login(email: String, password: String, callback: LoginResultCallback) { if (email == "[email protected]" && password == "@Abcd1234") { callback.onSuccess(UserInfo(“0001")) } else { callback.onFail(1012, "޷оੑ ഥਗੑפ׮.") } } }
  84. 8IBUBSFUIFCFOFGJUTPG5%% • ௏٘ܳ ࠁח ҙ੼ ߸҃ • ௏٘ܳ ࢎਊೞח ੑ੢ਵ۽

    ࢤп ੹ജ • ױੌ ଼੐੄ ਗ஗ • ஭ङച • য়ߡ ূ૑פয݂ ߑ૑