Upgrade to Pro — share decks privately, control downloads, hide ads and more …

테스트 관점으로 아키텍쳐 완성하기 (Testable Architecture)

테스트 관점으로 아키텍쳐 완성하기 (Testable Architecture)

- GDG DevFest Seoul 2019 (10.20)
- Line TechTalk 사내발표 (11.18)
위 행사들에서 발표한 자료입니다.

안드로이드 개발 3년차 김데페는 MVP를 다룹니다. RxJava로 API를 호출하고 DI도 알아요. 하지만 최근 공부를 시작한 테스트가 잘 짜여지지 않습니다. 그나마 동작하는 몇 테스트도 효용이 있는지 모르겠습니다.

테스트와 아키텍쳐는 함께 움직이는 것을 아시나요? 테스트가 불편하거나 효용성이 적은 것은 아키텍쳐가 불안정하기 때문입니다. 이 발표는 MVP 아키텍쳐를 테스트 관점으로 완성시켜봅니다.

1763273018a6f71ffe4162dd57b60a56?s=128

Seungmin - maryang

October 20, 2019
Tweet

More Decks by Seungmin - maryang

Other Decks in Programming

Transcript

  1. పझ౟ ҙ੼ਵ۽ ইఃఫ୛ ৮ࢿೞӝ Testable Architecture ੉थ޹, ߛ௼࢟۞٘/GDE Android Korea


    @maryangmin
  2. None
  3. - MVPܳ ׮ܛפ׮ - RxJava۽ APIܳ ഐ୹೤פ׮. - DIܳ ঌইਃ

    3֙ର ѐߊ੗ ӣؘಕח…
  4. ೞ૑݅୭Ӕҕࠗܳद੘ೠపझ౟оੜ૞ৈ૑૑ঋणפ׮ Ӓա݃ز੘ೞחݻపझ౟بബਊ੉੓ח૑ݽܰѷणפ׮

  5. None
  6. ߊ಴ীࢲ ࢎਊೞח ӝࠄ૑ध • উ٘۽੉٘ • MVP ইఃఫ୛ • RxJava

  7. ݾର 1. పझ౟ۆ? 2. పझ౟ܳ য۵ѱ ೞח Ѫ 3. Testable

    Architecture ٜ݅ӝ 4. Testable Architecture ৮ࢿೞӝ 5. Testable Culture 6. Ѿۿ
  8. HJUIVCDPNNBSZBOHNJO(%(5FTUBCMF"SD

  9. పझ౟ۆ?

  10. పझ౟ۆ? যڃ ز੘ਸ ഐ୹೮ਸ ٸ, 1. ч੉ ਗೞח ഋకо غח૑

    Ѩૐ 2. ਗೞח ز੘ਸ ো҅೧ࢲ ࣻ೯ೞחо Ѩૐ
  11. @Test fun loginTest() { loginHelper.login() // isLogin ч੉ Trueо غ঻חо?

    assertTrue(loginHelper.isLogin) }
  12. @Test fun loginTest() { loginHelper.login() // isLogin ч੉ Trueо غ঻חо?

    assertTrue(loginHelper.isLogin) }
  13. @Test fun loginTest() { loginHelper.login() // isLogin ч੉ Trueо غ঻חо?

    assertTrue(loginHelper.isLogin) }
  14. @Test fun searchTest() { presenter.searchGithubRepos(searchText) // view.showRepos(repo)о ز੘ೞחо? Mockito.verify(view).showRepos(repos) }

  15. @Test fun searchTest() { presenter.searchGithubRepos(searchText) // view.showRepos(repo)о ز੘ೞחо? Mockito.verify(view).showRepos(repos) }

  16. @Test fun searchTest() { presenter.searchGithubRepos(searchText) // view.showRepos(repo)о ز੘ೞחо? Mockito.verify(view).showRepos(repos) }

  17. పझ౟ ਗܻ ѨૐೞҊ੗ ೞח ݫࣗ٘ܳ ࣻ೯दௌਸ ٸ, 1. ਗೞח ч੄

    assert()о true 2. ਗೞח ز੘੄ verify()о true
  18. పझ౟ ਗܻ ѨૐೞҊ੗ ೞח ݫࣗ٘ܳ ࣻ೯दௌਸ ٸ, 1. ਗೞח ч੄

    assert()о true 2. ਗೞח ز੘੄ verify()о true
  19. അप਷ ޖ঱о పझ౟о ൨ٜ׮!

  20. పझ౟ܳ য۵ѱ ೞח Ѫ

  21. 1. ੄ઓ Class੄ ز੘ పझ౟ܳ য۵ѱ ೞח Ѫ

  22. class GithubReposPresenterTest { private lateinit var presenter: GithubReposPresenter @Before fun

    setUp() { presenter = GithubReposPresenter() } }
  23. class GithubReposPresenterTest { private lateinit var presenter: GithubReposPresenter @Before fun

    setUp() { presenter = GithubReposPresenter() } }
  24. class GithubRepoPresenter( view: GithubRepoView, repository: GithubRepository ) { fun issues(repo:

    GithubRepo) = repository.issues(owner, repo.name) .subscribe( { view.showIssues(it) }, { it.printStackTrace() } ) }
  25. class GithubRepoPresenter( view: GithubRepoView, repository: GithubRepository ) { fun issues(repo:

    GithubRepo) = repository.issues(owner, repo.name) .subscribe( { view.showIssues(it) }, { it.printStackTrace() } ) } ੄ઓ௿ېझ
  26. class GithubRepoPresenter( view: GithubRepoView, repository: GithubRepository ) { fun issues(repo:

    GithubRepo) = repository.issues(owner, repo.name) .subscribe( { view.showIssues(it) }, { it.printStackTrace() } ) } যڌѱز੘  ޖ঺ਸ߈ജ
  27. class GithubReposPresenterTest { private lateinit var presenter: GithubReposPresenter @Mock lateinit

    var view: GithubReposView @Mock lateinit var githubRepository: GithubRepository @Before fun setUp() { presenter = GithubReposPresenter( view, githubRepository ) } }
  28. class GithubReposPresenterTest { private lateinit var presenter: GithubReposPresenter @Mock lateinit

    var view: GithubReposView @Mock lateinit var githubRepository: GithubRepository @Before fun setUp() { presenter = GithubReposPresenter( view, githubRepository ) } } .PDLഝਊ
  29. // ߈ജ чਸ ૑੿ val repos: List<GithubRepo> = emptyList() //

    Mock Class੄ ز੘ਸ ੿੄ Mockito.`when`( githubRepository.searchGithubRepos(anyString()) ).thenReturn(Single.just(repos))
  30. // ߈ജ чਸ ૑੿ val repos: List<GithubRepo> = emptyList() //

    Mock Class੄ ز੘ਸ ੿੄ Mockito.`when`( githubRepository.searchGithubRepos(anyString()) ).thenReturn(Single.just(repos)) 4UVC .PDLJOH
  31. 1. ੄ઓ Class੄ ز੘ - Mocking, Stub పझ౟ܳ য۵ѱ ೞח

    Ѫ
  32. 1. ੄ઓ Class੄ ز੘ - Mocking, Stub 2. উ٘۽੉٘ ੄ઓ

    ز੘ పझ౟ܳ য۵ѱ ೞח Ѫ
  33. class GithubReposPresenter( private val connectivityManager: ConnectivityManager, ) : BasePresenter<GithubReposView>(view) {

    fun onCreate() { if (connectivityManager.isConnected().not()) view.showNoInternetWarning() } }
  34. class GithubReposPresenter( private val connectivityManager: ConnectivityManager, ) : BasePresenter<GithubReposView>(view) {

    fun onCreate() { if (connectivityManager.isConnected().not()) view.showNoInternetWarning() } } $POUFYU੄ઓೞח ੋఠ֔୓௼ӝמࢎਊ
  35. class GithubReposPresenter( private val connectivityManager: ConnectivityManager, ) : BasePresenter<GithubReposView>(view) {

    fun onCreate() { if (connectivityManager.isConnected().not()) view.showNoInternetWarning() } } @Test fun internetCheckTest() { Mockito.`when`(connectivityManagerMock.isConnected()).thenReturn(false) presenter.onCreate() Mockito.verify(view).showNoInternetWarning() }
  36. class GithubReposPresenter( private val connectivityManager: ConnectivityManager, ) : BasePresenter<GithubReposView>(view) {

    fun onCreate() { if (connectivityManager.isConnected().not()) view.showNoInternetWarning() } } @Test fun internetCheckTest() { Mockito.`when`(connectivityManagerMock.isConnected()).thenReturn(false) presenter.onCreate() Mockito.verify(view).showNoInternetWarning() } &SSPS উ٘۽੉٘੄ઓݫࣗ٘ࢎਊ
  37. .FUIPEOPUNPDLFE 5IFBOESPJEKBSGJMFUIBUJTVTFEUPSVOVOJUUFTUT EPFTOPUDPOUBJOBOZBDUVBMDPEF ୹୊: http://tools.android.com/tech-docs/unit-testing-support

  38. class NetworkHelper(context: Context) { private val connectivityManager: ConnectivityManager fun isConnected():

    Boolean = connectivityManager.isConnected() } class GithubReposPresenter( private val networkHelper: NetworkHelper, ) : BasePresenter<GithubReposView>(view) { fun onCreate() { if (networkHelper.isConnected().not()) view.showNoInternetWarning() } }
  39. class NetworkHelper(context: Context) { private val connectivityManager: ConnectivityManager fun isConnected():

    Boolean = connectivityManager.isConnected() } class GithubReposPresenter( private val networkHelper: NetworkHelper, ) : BasePresenter<GithubReposView>(view) { fun onCreate() { if (networkHelper.isConnected().not()) view.showNoInternetWarning() } } $POUFYUӝמ 8SBQQJOH
  40. @Test fun internetCheckTest() { Mockito.`when`(networkHelper.isConnected()) .thenReturn(false) presenter.onCreate() Mockito.verify(view).showNoInternetWarning() } 8SBQQJOH$MBTTܳ

    .PDLJOH
  41. 1. ੄ઓ Class੄ ز੘ - Mocking, Stub 2. উ٘۽੉٘ ੄ઓ

    ز੘ - Wrapping, Mocking పझ౟ܳ য۵ѱ ೞח Ѫ
  42. 1. ੄ઓ Class੄ ز੘ - Mocking, Stub 2. উ٘۽੉٘ ੄ઓ

    ز੘ - Wrapping, Mocking పझ౟ܳ য۵ѱ ೞח Ѫ ਤޙઁоऔѱ೧䟽غযঠపझ౟ܳೡࣻ੓׮
  43. 1. ੄ઓ Class੄ ز੘ - Mocking, Stub 2. উ٘۽੉٘ ੄ઓ

    ز੘ - Wrapping, Mocking పझ౟ܳ য۵ѱ ೞח Ѫ ਤޙઁоऔѱ೧䟽غযঠపझ౟ܳೡࣻ੓׮ 5FTUBCMF"SDIJUFDUVSF
  44. Testable Architecture ٜ݅ӝ

  45. Architectureۆ?

  46. ௏٘੄ ৉ೡਸ ܻ࠙ೞৈ ৈ۞ ੉੼ਸ ঳ח׮ 1. оةࢿ (֫਷ ਽૘ب)

    2. ߸҃ ਬোೣ (ծ਷ Ѿ೤ب) উ٘۽੉٘ীࢲח MVP, MVVMਸ ઱۽ ࢎਊ Architectureۆ?
  47. ௏٘੄ ৉ೡਸ ܻ࠙ೞৈ ৈ۞ ੉੼ਸ ঳ח׮ 1. оةࢿ (֫਷ ਽૘ب)

    2. ߸҃ ਬোೣ (ծ਷ Ѿ೤ب) উ٘۽੉٘ীࢲח MVP, MVVMਸ ઱۽ ࢎਊ Architectureۆ? Ӓրࢎਊೞݶ/PU5FTUBCMFഛܫ੉֫਺ ݻਗ஗ਸӝ߈ਵ۽ѐ䣜ೞݶ5FTUBCMF"SDIJUFDUVSF
  48. ਗ஗ 1 - ੄ઓࢿ ё୓ח ݽف ઱ੑ߉ח׮

  49. class GithubRepoBadPresenter( view: GithubRepoView ) : BasePresenter<GithubRepoView>(view) { private val

    repository = GithubRepository() fun save(repo: GithubRepo) = repository.save(repo) .subscribe() } Bad Case 1 - ੄ઓࢿ ࢤࢿ
  50. class GithubRepoBadPresenter( view: GithubRepoView ) : BasePresenter<GithubRepoView>(view) { private val

    repository = GithubRepository() fun save(repo: GithubRepo) = repository.save(repo) .subscribe() } ੄ઓࢿ੗୓ࢤࢿ Bad Case 1 - ੄ઓࢿ ࢤࢿ
  51. class GithubRepoBadPresenter( view: GithubRepoView ) : BasePresenter<GithubRepoView>(view) { private val

    repository = GithubRepository() fun save(repo: GithubRepo) = repository.save(repo) .subscribe() } .PDL3FQPTJUPSZ઱ੑࠛо పझ౟ࠛо Bad Case 1 - ੄ઓࢿ ࢤࢿ
  52. class GithubRepoGoodPresenter( view: GithubRepoView, private val repository: GithubRepository ) :

    BasePresenter<GithubRepoView>(view) class GithubRepoPresenterTest : BasePresenterTest() { @Mock lateinit var githubRepository: GithubRepository override fun setUp() { presenter = GithubRepoPresenter( view, githubRepository ) } Good Case
  53. class GithubRepoGoodPresenter( view: GithubRepoView, private val repository: GithubRepository ) :

    BasePresenter<GithubRepoView>(view) class GithubRepoPresenterTest : BasePresenterTest() { @Mock lateinit var githubRepository: GithubRepository override fun setUp() { presenter = GithubRepoPresenter( view, githubRepository ) } Good Case ੄ઓࢿ઱ੑ߉਺
  54. class GithubRepoGoodPresenter( view: GithubRepoView, private val repository: GithubRepository ) :

    BasePresenter<GithubRepoView>(view) class GithubRepoPresenterTest : BasePresenterTest() { @Mock lateinit var githubRepository: GithubRepository override fun setUp() { presenter = GithubRepoPresenter( view, githubRepository ) } Good Case .PDLഝਊ పझ౟оמ
  55. class GithubRepoActivity { override val presenter: GithubRepoPresenter by lazy {

    GithubRepoPresenter(this, GithubRepository()) } } ࠗ۾
  56. class GithubRepoActivity { override val presenter: GithubRepoPresenter by lazy {

    GithubRepoPresenter(this, GithubRepository()) } } ࠗ۾ 6*ী䞵%BUBଵઑ
  57. class GithubRepoActivity { override val presenter: GithubRepoPresenter by lazy {

    GithubRepoPresenter(this, GithubRepository()) } } ࠗ۾ 6*ী䞵%BUBଵઑ  %*
  58. class IssueCreatePresenter { fun createIssue(repo: GithubRepo, title: String, body: String)

    = repository.createIssue(owner, name, title, body) .subscribe({ DataObserver.post(it) view.onIssueCreated() }, { view.onIssueCreateFail() }) } Bad Case 2 - Static Class
  59. class IssueCreatePresenter { fun createIssue(repo: GithubRepo, title: String, body: String)

    = repository.createIssue(owner, name, title, body) .subscribe({ DataObserver.post(it) view.onIssueCreated() }, { view.onIssueCreateFail() }) } 4UBUJD$MBTT ઱ੑࠛоపझ౟ࠛо Bad Case 2 - Static Class
  60. class IssueCreatePresenter( view: IssueCreateView, private val dataObserver: DataObserver, private val

    repository: GithubRepository ) Good Case
  61. class IssueCreatePresenter( view: IssueCreateView, private val dataObserver: DataObserver, private val

    repository: GithubRepository ) Good Case 4UBUJD$MBTTܳੌ߈$MBTT۽߸҃ ੄ઓࢿ઱ੑ߉਺
  62. ਗ஗ 2 - উ٘۽੉٘ ੄ઓ ز੘ਸ Wrapping ೠ׮

  63. ਗ஗ 2 - উ٘۽੉٘ ੄ઓ ز੘ਸ Wrapping ೠ׮ 4%,.FUIPE6*%# "1*ాन

  64. class NetworkHelper(context: Context) { private val connectivityManager: ConnectivityManager fun isConnected():

    Boolean = connectivityManager.isConnected() } class GithubReposPresenter( private val networkHelper: NetworkHelper, ) : BasePresenter<GithubReposView>(view) { fun onCreate() { if (networkHelper.isConnected().not()) view.showNoInternetWarning() } } $POUFYUӝמ 8SBQQJOH SDK Method
  65. interface GithubReposView { fun showNoInternetWarning() fun showLoading() fun hideLoading() fun

    showRepos(repos: List<GithubRepo>) } fun onCreate() { if (networkHelper.isConnected().not()) view.showNoInternetWarning() } UI Method
  66. interface GithubReposView { fun showNoInternetWarning() fun showLoading() fun hideLoading() fun

    showRepos(repos: List<GithubRepo>) } fun onCreate() { if (networkHelper.isConnected().not()) view.showNoInternetWarning() } 7JFX*OUFSGBDF۽ 8SBQQJOH UI Method
  67. interface GithubRepository { fun save(repo: GithubRepo): Completable fun star(owner: String,

    repo: String): Completable fun unstar(owner: String, repo: String): Completable } class IssueCreatePresenter( private val repository: GithubRepository ) { fun createIssue(repo: GithubRepo, title: String, body: String) = repository.createIssue(owner, repo.name, title, body) .subscribe{ view.onIssueCreated() } } DB / API ాन
  68. interface GithubRepository { fun save(repo: GithubRepo): Completable fun star(owner: String,

    repo: String): Completable fun unstar(owner: String, repo: String): Completable } class IssueCreatePresenter( private val repository: GithubRepository ) { fun createIssue(repo: GithubRepo, title: String, body: String) = repository.createIssue(owner, repo.name, title, body) .subscribe{ view.onIssueCreated() } } DB / API ాन 3FQPTJUPSZ*OUFSGBDF۽ 8SBQQJOH
  69. ਗ஗ 2 - উ٘۽੉٘ ੄ઓ ز੘ਸ Wrapping ೠ׮ 4%,.FUIPE6*%# "1*ాन

    4%,.FUIPE8SBQQFS 7JFX*OUFSGBDF 3FQPTJUPSZ .71੄ӝࠄ
  70. ਗ஗ 3 - ݽٚ ز੘਷ Presenterܳ ాೠ׮

  71. override fun onCreate(savedInstanceState: Bundle?) { showRepo(intent.getParcelableExtra<GithubRepo>(KEY_REPO)) } private fun showRepo(repo:

    GithubRepo) { ownerName.text = repo.owner.userName starCount.text = repo.stargazersCount.toString() watcherCount.text = repo.watchersCount.toString() forksCount.text = repo.forksCount.toString() showStar(repo.star) presenter.issues(repo) } Bad Case 1 - ചݶ ղ private Method
  72. override fun onCreate(savedInstanceState: Bundle?) { // showRepo(intent.getParcelableExtra<GithubRepo>(KEY_REPO)) } private fun

    showRepo(repo: GithubRepo) { ownerName.text = repo.owner.userName starCount.text = repo.stargazersCount.toString() watcherCount.text = repo.watchersCount.toString() forksCount.text = repo.forksCount.toString() showStar(repo.star) presenter.issues(repo) } ഐ୹ৈࠗపझ౟ࠛо Bad Case 1 - ചݶ ղ private Method
  73. override fun onCreate(savedInstanceState: Bundle?) { presenter.onCreate(it) } fun onCreate(repo: GithubRepo)

    { this.repo = repo view.showRepo(repo) issues(repo) } Good Case
  74. override fun onCreate(savedInstanceState: Bundle?) { presenter.onCreate(it) } fun onCreate(repo: GithubRepo)

    { this.repo = repo view.showRepo(repo) issues(repo) } Good Case 1SFTFOUFSী䞵۽૒ࣻ೯ 7JFXח1SFTFOUFSप೯݅
  75. @Test fun onCreateTest() { presenter.onCreate(repo) verify(view).showRepo(repo) verify(view).showIssues(issues) } Good Case

    1SFTFOUFS۽૒పझ౟оמ
  76. star.onClick { val originalStar = repo.star showStar(!originalStar) showStarCount(repo.stargazersCount.let { if

    (originalStar) it - 1 else it + 1 }) presenter.onClickStar() } fun onClickStar() { (if (originalStar) repository.unstar(owner, repo.name) else repository.star(owner, repo.name)) ... } Bad Case 2 - ചݶ ղ ۽૒
  77. star.onClick { val originalStar = repo.star showStar(!originalStar) showStarCount(repo.stargazersCount.let { if

    (originalStar) it - 1 else it + 1 }) presenter.onClickStar() } fun onClickStar() { (if (originalStar) repository.unstar(owner, repo.name) else repository.star(owner, repo.name)) ... } Bad Case 2 - ചݶ ղ ۽૒ ഐ୹ৈࠗపझ౟ࠛо
  78. star.onClick { presenter.onClickStar() } fun onClickStar() { val originalStar =

    repo.star view.showStar(!originalStar) view.showStarCount(repo.stargazersCount.let { if (originalStar) it - 1 else it + 1 }) (if (originalStar) repository.unstar(owner, repo.name) else repository.star(owner, repo.name)) ... } Good Case
  79. star.onClick { presenter.onClickStar() } fun onClickStar() { val originalStar =

    repo.star view.showStar(!originalStar) view.showStarCount(repo.stargazersCount.let { if (originalStar) it - 1 else it + 1 }) (if (originalStar) repository.unstar(owner, repo.name) else repository.star(owner, repo.name)) ... } Good Case 1SFTFOUFSী䞵۽૒ࣻ೯ 7JFXח1SFTFOUFSप೯݅
  80. @Test fun clickStarTest() { repo.star = false val originalStarCount =

    repo.stargazersCount presenter.onClickStar() Mockito.verify(view).showStar(true) Mockito.verify(view).showStarCount(originalStarCount + 1) } Good Case 1SFTFOUFS۽૒పझ౟оמ
  81. ਗ஗ 3 - ݽٚ ز੘਷ Presenterܳ ాೠ׮ 7JFXח1SFTFOUFSप೯݅
 1SFTFOUFS۽૒పझ౟оמ 1BTTJWF7JFX

  82. Testable Architecture ৮ࢿೞӝ

  83. .FUIPEOPUNPDLFE 5IFBOESPJEKBSGJMFUIBUJTVTFEUPSVOVOJUUFTUT EPFTOPUDPOUBJOBOZBDUVBMDPEF

  84. .FUIPEOPUNPDLFE 5IFBOESPJEKBSGJMFUIBUJTVTFEUPSVOVOJUUFTUT EPFTOPUDPOUBJOBOZBDUVBMDPEF Ӓۢউ٘۽੉٘੄ઓӝמ਷যڌѱపझ౟

  85. - UI Test - DB Test Instrument Test

  86. @RunWith(AndroidJUnit4::class) class PreferenceTest { @Test fun preferenceTest() { val context

    = InstrumentationRegistry.getInstrumentation().context val pref = context.getSharedPreferences("preference") val userName = pref.getString("pref_user_name", "userName") assertEquals("userName", userName) } } $POUFYUദٙ
  87. @RunWith(AndroidJUnit4::class) class GithubRepoActivityTest { @get:Rule val activityRule = ActivityTestRule(GithubRepoActivity::class.java) @Test

    fun onCreate() { activityRule.launchActivity(null) } } "DUJWJUZദٙ
  88. - UI Test - DB Test Instrument Test 5FTUBCMF"SDIJUFDUVSF੉ۄ䢞 7JFX

    3FQPTJUPSZ݅୶о۽పझ౟
  89. Testable Architecture Map ੄ઓࢿ઱ੑ উ٘۽੉٘੄ઓࢿӝמ 8SBQQJOHغযز੘

  90. Testable Architecture Map 1SFTFOUFS7.5FTU 6OJU5FTU

  91. Testable Architecture Map with Instrument 6*5FTU *OTUSVNFOU 1SFTFOUFS7.5FTU 3FQPTJUPSZ5FTU *OTUSVNFOU

    6OJU5FTU
  92. Testable Culture ૓૞ Testable Architecture ৮ࢿೞӝ

  93. 1. ੄ઓ Class੄ ز੘ - Mocking, Stub 2. উ٘۽੉٘ ੄ઓ

    ز੘ - Wrapping, Mocking పझ౟ܳ য۵ѱ ೞח Ѫ
  94. 1. ੄ઓ Class੄ ز੘ - Mocking, Stub 2. উ٘۽੉٘ ੄ઓ

    ز੘ - Wrapping, Mocking పझ౟ܳ য۵ѱ ೞח Ѫ 2"۽䟰ૐೞחѪ੉ࡅܰ׮ Ҋוԑ૓׮  పझ౟ܳ୶оೡदр੉হ׮
  95. 1. ੄ઓ Class੄ ز੘ - Mocking, Stub 2. উ٘۽੉٘ ੄ઓ

    ز੘ - Wrapping, Mocking పझ౟ܳ য۵ѱ ೞח Ѫ పझ౟੄ബਊࢿਸݽܰѷ׮ ੌ੿੉ցޖ߄ࡅزӝо࢓૑ঋח׮
  96. 1. ز੘ ഛੋ పझ౟੄ ബਊࢿ

  97. 1. ز੘ ഛੋ 2. ߸҃ ਬোࢿ ഛࠁ పझ౟੄ ബਊࢿ

  98. పझ౟੄ ബਊࢿ పझ౟о੓ח$MBTTח߸҃೧بউब ੢ӝ䥄ਵ۽పझ౟੓חѪ੉ࢤ࢑ࢿ੉഻ঁࡅܰ׮ 1. ز੘ ഛੋ 2. ߸҃ ਬোࢿ

    ഛࠁ
  99. పझ౟੄ ബਊࢿ 1. ز੘ ഛੋ 2. ߸҃ ਬোࢿ ഛࠁ 3.

    ௏ܻ٘࠭ܳ ذח׮
  100. 1. ز੘ ഛੋ 2. ߸҃ ਬোࢿ ഛࠁ 3. ௏ܻ٘࠭ܳ ذח׮

    పझ౟੄ ബਊࢿ పझ౟ח੄بܳݺदച పझ౟ܳ౵ঈೞҊ௏٘ܳࠁݶܻ࠭оࣻ䤰ೞ׮
  101. పझ౟ زӝࠗৈ

  102. 1. ܻನ౟۽ അपਸ ӵײ੗ పझ౟ زӝࠗৈ $PEFDPW 13݃׮పझ౟ழߡܻ૑ܻನ౟

  103. 1. ܻನ౟۽ അपਸ ӵײ੗ 2. ੊ࣼ೧૑ӝө૑ ъઁࢿਸ ف੗ పझ౟ زӝࠗৈ

    ழߡܻ૑੉䥅ࠁ׮֫૑ঋਵݶ %POsU"QQSPWF
  104. 1. ܻನ౟۽ അपਸ ӵײ੗ 2. ੊ࣼ೧૑ӝө૑ ъઁࢿਸ ف੗ 3. ए਍

    Ѫࠗఠ ରӔରӔ పझ౟ زӝࠗৈ 1SFTFOUFS7.5FTU 6OJU5FTU 1SFTFOUFS6OJUపझ౟о ࣘبоࡅܰҊ ࣻݺ੉ӡ׮
  105. ࠺ૉפझܳן୶૑ঋਵݶࢲ  ೠ׳زউழߡܻ૑ૐо

  106. ೠߣणҙਸٜ੉ݶಞ೧૘פ׮ 5FTUBCMF$VMUVSFನӝೞ૑݃ࣁਃ ࠺ૉפझܳן୶૑ঋਵݶࢲ  ೠ׳زউழߡܻ૑ૐо

  107. Ѿۿ

  108. ਗ஗ 1. ੄ઓࢿ ё୓ח ݽف ઱ੑ߉ח׮ 2. উ٘۽੉٘ ੄ઓ ز੘ਸ

    Wrapping ೠ׮ 3. ݽٚ ز੘਷ Presenterܳ ాೠ׮
  109. Testable Architecture Map with Instrument 6*5FTU *OTUSVNFOU 1SFTFOUFS7.5FTU 3FQPTJUPSZ5FTU *OTUSVNFOU

    6OJU5FTU
  110. పझ౟ زӝࠗৈ 5FTUBCMF$VMUVSF 1. ܻನ౟۽ അपਸ ӵײ੗ 2. ੊ࣼ೧૑ӝө૑ ъઁࢿਸ

    ف੗ 3. ए਍ Ѫࠗఠ ରӔରӔ
  111. పझ౟ܳੜೞ۰ݶ䤠ೡ੉ੜܻ࠙ػ"SDIJUFDUVSFо೙ਃ పझ౟ܳب੹䞸ݶࢲ"SDIJUFDUVSFܳ৮ࢿ೤द׮

  112. పझ౟о৮߷ೞৈبߡӒחߊࢤ पઁࢲ࠺झח৔ೱਸ઱Ҋ߉חஶఫझ౟о݆ӝٸޙ FY֎౟ਕ௼ ࠛदઙܐ ݃੉Ӓۨ੉࣌١  2"੄೙ਃࢿ

  113. ࠂ੟ೞ૑ঋ਷ز੘਷పझ౟۽䟰ૐ೤द׮ పझ౟о৮߷ೞৈبߡӒחߊࢤ पઁࢲ࠺झח৔ೱਸ઱Ҋ߉חஶఫझ౟о݆ӝٸޙ FY֎౟ਕ௼ ࠛदઙܐ ݃੉Ӓۨ੉࣌١  2"੄೙ਃࢿ

  114. উ٘۽੉٘ѐߊ੗ݽ૘ ೣԋߛ௼䣓۞٘పझ౟௏٘૞ࠇद׮

  115. хࢎ೤פ׮! ੉थ޹, ߛ௼࢟۞٘/GDE Android Korea
 @maryangmin