라이브 슀트리밍 ꡬ쑰 개편, TDD 첫 λ„μž…κΈ°

hayeon
  • #TDD
  • #TCA
  • #CleanSwift
  • #iOS

λ“€μ–΄κ°€λ©°

μ•ˆλ…•ν•˜μ„Έμš”. μ €λŠ” fromm μ•±μ—μ„œ iOS κ°œλ°œμ„ λ‹΄λ‹Ήν•˜κ³  μžˆλŠ” μ΄ν•˜μ—°μž…λ‹ˆλ‹€.

λ°°κ²½: λ³΅μž‘ν•΄μ§„ ꡬ쑰, 그리고 λ³€ν™”μ˜ ν•„μš”μ„±

μž‘λ…„, fromm 앱에 SwiftUI와 TCA μ•„ν‚€ν…μ²˜λ₯Ό μ μš©ν•΄ 라이브 슀트리밍 κΈ°λŠ₯을 처음 λ„μž…ν–ˆμŠ΅λ‹ˆλ‹€.

TCAλŠ” λ‹€μ–‘ν•œ κΈ°λŠ₯을 μ œκ³΅ν•˜κ³ , λ§Žμ€ νŒ€λ“€μ΄ 효과적으둜 ν™œμš©ν•˜κ³  μžˆλŠ” μ•„ν‚€ν…μ²˜μ§€λ§Œ, 저희 νŒ€μ—κ²ŒλŠ” μ‹œκ°„μ΄ μ§€λ‚ μˆ˜λ‘ 둜직 λΆ„μ‚°, λ””λ²„κΉ…μ˜ 어렀움, λ³΅μž‘ν•œ μƒνƒœ 관리 λ“± λͺ‡ κ°€μ§€ μΈ‘λ©΄μ—μ„œ 점차 어렀움이 λˆ„μ λ˜κΈ° μ‹œμž‘ν–ˆμŠ΅λ‹ˆλ‹€.

이둜 인해, νŒ€ λ‚΄λΆ€ λ…Όμ˜ 끝에 λ‹€μŒκ³Ό 같은 큰 결정을 λ‚΄λ ΈμŠ΅λ‹ˆλ‹€.

1. 라이브 λͺ¨λ“ˆμ˜ 전면적인 ꡬ쑰 개편
    - κΈ°μ‘΄ TCA μ•„ν‚€ν…μ²˜ 제거
    - νŒ€μ˜ μš”κ΅¬μ— 맞좰 λ³€ν˜•ν•œ Clean Swift νŒ¨ν„΄ λ„μž…
2. TDD 방식 λ„μž…

Clean Swift νŒ¨ν„΄μ„ μ±„νƒν•˜λ˜, Clean Architectureλ₯Ό 기반으둜 κ°œμ„ ν•˜μ—¬ μ μš©ν–ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό 기반으둜 ꡬ쑰λ₯Ό μ „ν™˜ν•˜λ©΄μ„œ Interactor μ€‘μ‹¬μœΌλ‘œ λ‘œμ§μ„ λͺ¨μœΌκ³ , View β†’ Interactor β†’ Presenter κ°„ μ±…μž„μ„ λͺ…ν™•νžˆ 뢄리할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. κ·Έ κ²°κ³Ό, ν…ŒμŠ€νŠΈ μž‘μ„±κ³Ό μœ μ§€λ³΄μˆ˜λ„ ν•œκ²° μˆ˜μ›”ν•΄μ‘ŒμŠ΅λ‹ˆλ‹€.

특히 ꡬ쑰 개편과 ν•¨κ»˜, TDD 방식도 λ„μž…ν•΄λ΄€λŠ”λ°μš”. 기쑴처럼 κ΅¬ν˜„ μœ„μ£Όλ‘œ μž‘μ—…ν•˜λ˜ λ°©μ‹μ—μ„œ λ²—μ–΄λ‚˜, ν…ŒμŠ€νŠΈλ₯Ό λ¨Όμ € μž‘μ„±ν•˜κ³  그에 맞좰 κΈ°λŠ₯을 κ΅¬ν˜„ν•˜λŠ” 흐름을 μ‹€μ œ ν”„λ‘œμ νŠΈμ— μ μš©ν•΄λ³Έ 건 이번이 μ²˜μŒμ΄μ—ˆμŠ΅λ‹ˆλ‹€.

이번 κΈ€μ—μ„œλŠ” 라이브 슀트리밍 ꡬ쑰 개편 κ³Όμ •μ—μ„œ TDDλ₯Ό μ‹€μ œλ‘œ μ–΄λ–»κ²Œ μ μš©ν–ˆλŠ”μ§€, 그리고 저희 νŒ€μ΄ μ–΄λ–€ κΈ°μ€€μœΌλ‘œ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν–ˆλŠ”μ§€, κ·Έ κ²½ν—˜μ„ 톡해 무엇을 배우고 λŠκΌˆλŠ”μ§€ 이야기해보렀 ν•©λ‹ˆλ‹€.

πŸ‘‰ μ°Έκ³  - ꡬ쑰 개편 과정은 이전 λΈ”λ‘œκ·Έ κΈ€μ—μ„œ μžμ„Ένžˆ λ‹€λ£¨μ—ˆμŠ΅λ‹ˆλ‹€. ( 링크 )

TDD, 우리 νŒ€μ—μ„  μ΄λ ‡κ²Œ μ‹œμž‘ν–ˆμŠ΅λ‹ˆλ‹€

μƒˆλ‘œμš΄ μ•„ν‚€ν…μ²˜ λ„μž…λ§ŒμœΌλ‘œλ„ νŒ€μ—κ²ŒλŠ” 큰 λ³€ν™”μ˜€λŠ”λ°, 여기에 TDDκΉŒμ§€ λ”ν•΄μ§€λ‹ˆ 적응이 μ‰½μ§€λ§Œμ€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.

νŒ€μž₯λ‹˜ μ™Έμ—λŠ” ν…ŒμŠ€νŠΈ μ½”λ“œ κ²½ν—˜μ΄ μ—†μ—ˆκ³ , μ„Έ λͺ…μ˜ νŒ€μ›μ΄ 각자 λ‹€λ₯Έ μŠ€νƒ€μΌλ‘œ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ‹€ λ³΄λ‹ˆ 일관성을 μœ μ§€ν•˜λŠ” 데도 쉽지 μ•Šμ•˜μŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, μ–΄λ–€ νŒ€μ›μ€ it 블둝을 잘게 λ‚˜λˆ„λŠ” 방식을 μ„ ν˜Έν•˜λŠ” 반면, λ‹€λ₯Έ νŒ€μ›μ€ ν•˜λ‚˜μ˜ it 블둝 μ•ˆμ— λͺ¨λ“  검증을 λ‹΄λŠ” 방식을 μ‚¬μš©ν•˜κΈ°λ„ ν–ˆμŠ΅λ‹ˆλ‹€.

이런 상황 μ†μ—μ„œ νŒ€μž₯λ‹˜κ»˜μ„œ ν…ŒμŠ€νŠΈ μž‘μ„± κ°€μ΄λ“œλ₯Ό λͺ…ν™•νžˆ 정리해주셨고, μ €ν¬λŠ” 이λ₯Ό λ°”νƒ•μœΌλ‘œ λ‹€μŒκ³Ό 같은 5단계 TDD 루틴을 μ‹€μ²œν–ˆμŠ΅λ‹ˆλ‹€.

5단계 TDD 루틴

1. ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ μž‘μ„±ν•˜κΈ°
2. μ‹€νŒ¨ν•˜λŠ” ν…ŒμŠ€νŠΈ λ§Œλ“€κΈ°
3. μ„±κ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈ λ§Œλ“€κΈ°
4. ν˜‘λ ₯ 객체(router, presenter λ“±) κ΅¬ν˜„
5. λ¦¬νŒ©ν† λ§ 및 ν…ŒμŠ€νŠΈ μœ μ§€

Interactor ν…ŒμŠ€νŠΈ, μ΄λ ‡κ²Œ μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€

이번 ꡬ쑰 κ°œνŽΈμ—μ„œλŠ” Clean Swiftμ—μ„œ 핡심 역할을 λ‹΄λ‹Ήν•˜λŠ” Interactor μ€‘μ‹¬μœΌλ‘œ λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€.

또, Quick + Nimble + Cuckoo 쑰합을 ν™œμš©ν•΄ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„± ν•˜κ³ , μƒνƒœ 검증과 ν–‰μœ„κ²€μ¦μ„ μˆ˜ν–‰ν–ˆμŠ΅λ‹ˆλ‹€.

πŸ™‹πŸ»β€β™‚οΈ 잠깐, Interactorκ°€ λ­”κ°€μš”?

Clean Swift ꡬ쑰도 Clean Swiftμ—μ„œ InteractorλŠ” μ‚¬μš©μž 이벀트λ₯Ό μ²˜λ¦¬ν•˜κ³ , λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μˆ˜ν–‰ν•˜λ©°, μƒνƒœλ₯Ό κ΄€λ¦¬ν•˜λŠ” 핡심 역할을 λ§‘κ³  μžˆμŠ΅λ‹ˆλ‹€.

예λ₯Ό λ“€μ–΄, μ‚¬μš©μžκ°€ λ²„νŠΌμ„ λˆ„λ₯΄κ±°λ‚˜ λ©”μ‹œμ§€λ₯Ό μž…λ ₯ν•˜λ©΄, ViewλŠ” ν•΄λ‹Ή 이벀트λ₯Ό Interactor에 μ „λ‹¬ν•˜κ³ , InteractorλŠ” ν•„μš”ν•œ λ‘œμ§μ„ μˆ˜ν–‰ν•œ λ’€ Presenterλ‚˜ Routerλ₯Ό 톡해 ν™”λ©΄ 갱신을 μš”μ²­ν•©λ‹ˆλ‹€.

저희 νŒ€μ€ ꡬ쑰 개편 κ³Όμ •μ—μ„œ Interactorκ°€ ν•˜λ‚˜μ˜ 이벀트 흐름을 μ±…μž„μ§€κ³  μ²˜λ¦¬ν•  수 μžˆλ„λ‘ ꡬ쑰λ₯Ό μ •λΉ„ν–ˆμŠ΅λ‹ˆλ‹€. κ·Έ κ²°κ³Ό, 이벀트 λ‹¨μœ„λ‘œ ν…ŒμŠ€νŠΈ 흐름을 μ •μ˜ν•˜κ³ , ν˜‘λ ₯ κ°μ²΄μ™€μ˜ μƒν˜Έμž‘μš©μ„ κ²€μ¦ν•˜κΈ°μ—λ„ μ ν•©ν•œ ꡬ쑰인 Interactorλ₯Ό μ€‘μ‹¬μœΌλ‘œ ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

πŸ”‘ μ •λ¦¬ν•˜λ©΄, InteractorλŠ” Clean Swift ꡬ쑰 μ•ˆμ—μ„œ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직의 μ€‘μ‹¬μ μ΄μž, TDD ν…ŒμŠ€νŠΈ νλ¦„μ—μ„œ κ°€μž₯ λ¨Όμ € 검증이 μ΄λ€„μ§€λŠ” 지점

πŸ™‹πŸ» 잠깐, Quick, Nimble, Cuckooκ°€ λ­”κ°€μš”?

  • Quick: iOS ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬λ‘œ, describe, context, it λ“±μ˜ 문법을 톡해 Given-When-Then νŒ¨ν„΄μ„ μžμ—°μŠ€λŸ½κ²Œ μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ν…ŒμŠ€νŠΈλ₯Ό μ€‘λ³΅λ˜λŠ” λ‚΄μš© 없이 κ΅¬μ‘°ν™”ν•˜κΈ°μ— μœ μš©ν•©λ‹ˆλ‹€. ( 링크 )
  • Nimble: iOS ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬λ‘œ, μƒνƒœ 검증 λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€. λ‹€μ–‘ν•œ matcherλ₯Ό 톡해 κΈ°λŒ€ν•˜λŠ” μƒνƒœλ₯Ό 더 ν‘œν˜„λ ₯ 있게 검증할 수 μžˆμŠ΅λ‹ˆλ‹€. ( 링크 )
  • Cuckoo: Swift용 Mock 생성 λ„κ΅¬μž…λ‹ˆλ‹€. λΉŒλ“œ μ‹œμ μ— Mock 객체λ₯Ό μžλ™μœΌλ‘œ 생성해주기 λ•Œλ¬Έμ—, ν…ŒμŠ€νŠΈ μ½”λ“œ λ‚΄μ—μ„œ λ³„λ„λ‘œ μž‘μ„±ν•  ν•„μš”κ°€ μ—†μŠ΅λ‹ˆλ‹€. μƒμ„±λœ Mock은 Stub으둜 ν™œμš©μ΄ κ°€λŠ₯ν•˜μ—¬, ν–‰μœ„ 기반 검증도 μ†μ‰½κ²Œ μˆ˜ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ( 링크 )

더 μžμ„Έν•œ λ‚΄μš©μ€ 곡식 λ¬Έμ„œλ₯Ό μ°Έκ³ ν•΄μ£Όμ„Έμš”!

πŸ’πŸ»β€β™€οΈ μ‹€μ œλ‘œ μ΄λ ‡κ²Œ μ μš©ν–ˆμ–΄μš”!

λ‹€μŒμ€ μ‹€μ œλ‘œ μž‘μ„±ν•œ Interactor ν…ŒμŠ€νŠΈ μ½”λ“œ 쀑 일뢀λ₯Ό κ°μƒ‰ν•œ μ˜ˆμ‹œμž…λ‹ˆλ‹€. 이 μ˜ˆμ‹œλ₯Ό 톡해 5단계 TDD 루틴을 μ–΄λ–»κ²Œ μ μš©ν–ˆλŠ”μ§€ ꡬ체적으둜 μ†Œκ°œν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€

1. ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±

λ¨Όμ € describe, context, it ꡬ쑰λ₯Ό μ‚¬μš©ν•΄ ν…ŒμŠ€νŠΈ μ‹œλ‚˜λ¦¬μ˜€λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€. 이 ꡬ쑰λ₯Ό ν™œμš©ν•˜λ©΄ 쑰건(Given) - 이벀트(When) - κΈ°λŒ€ κ²°κ³Ό(Then) 흐름에 따라 ν…ŒμŠ€νŠΈ λͺ©μ μ„ λͺ…ν™•ν•˜κ²Œ ν‘œν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

그리고, 저희 νŒ€μ—μ„œλŠ” it 블둝을 μ—¬λŸ¬ 개둜 λ‚˜λˆ„κΈ°λ³΄λ‹€λŠ” ν•˜λ‚˜μ˜ it 블둝 μ•ˆμ—μ„œ μ—¬λŸ¬ 검증을 μˆ˜ν–‰ν•˜κ³ , 각 검증은 μ£Όμ„μœΌλ‘œ κ΅¬λΆ„ν•˜λŠ” 방식을 μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. 이 방식은 describe 및 context 블둝에 μž‘μ„±λœ μ½”λ“œκ°€ 쀑볡 μ‹€ν–‰λ˜λŠ” 경우λ₯Ό 쀄여주어, ν…ŒμŠ€νŠΈ μ‹€ν–‰ μ‹œκ°„μ„ 쀄일 수 μžˆλ‹€λŠ” μž₯점이 μžˆμŠ΅λ‹ˆλ‹€.

describe("λ©”μ‹œμ§€λ₯Ό μž…λ ₯ν•  λ•Œ") {
    it("λ™μž‘ κ²€μ¦ν•œλ‹€") {
        await sut.process(LiveChatRequest.OnChangeMessage(inputText: inputText))
        
        // μž…λ ₯된 λ©”μ‹œμ§€κ°€ μ΅œλŒ€ κΈ€μž 수λ₯Ό μ΄ˆκ³Όν•˜μ§€ μ•Šλ„λ‘ μ œν•œν•œλ‹€
    }

    context("μž…λ ₯된 λ©”μ‹œμ§€κ°€ μ΅œλŒ€ κΈ€μž 수λ₯Ό μ΄ˆκ³Όν–ˆλ‹€λ©΄") {        
        it("λ™μž‘ κ²€μ¦ν•œλ‹€") {
            await sut.process(LiveChatRequest.OnChangeMessage(inputText: inputText))
            
            // κΈ€μžμˆ˜ μ œν•œ ν† μŠ€νŠΈλ₯Ό λ…ΈμΆœν•œλ‹€
            
            // chatInputTextλ₯Ό μ œν•œν•œ λ©”μ‹œμ§€λ‘œ κ°±μ‹ ν•΄μ€€λ‹€
        }
    }
}

2. μ‹€νŒ¨ν•˜λŠ” ν…ŒμŠ€νŠΈ λ§Œλ“€κΈ°

1λ‹¨κ³„μ—μ„œ μž‘μ„±ν•œ μ‹œλ‚˜λ¦¬μ˜€μ— 따라 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.

이 μ‹œμ μ—λŠ” μ‹€μ œ κ΅¬ν˜„μ΄ μ—†κΈ° λ•Œλ¬Έμ— ν…ŒμŠ€νŠΈλŠ” λ‹Ήμ—°νžˆ μ‹€νŒ¨ν•˜κ²Œ λ˜λŠ”λ°, 이후 이λ₯Ό λ°”νƒ•μœΌλ‘œ ν…ŒμŠ€νŠΈλ₯Ό ν†΅κ³Όμ‹œν‚€κΈ° μœ„ν•΄ μ΅œμ†Œν•œμ˜ κΈ°λŠ₯을 ν•˜λ‚˜μ”© κ΅¬ν˜„ν•΄λ‚˜κ°€κ²Œ λ©λ‹ˆλ‹€.

이 과정이 TDD의 ν•΅μ‹¬μœΌλ‘œ, ν…ŒμŠ€νŠΈκ°€ μš”κ΅¬μ‚¬ν•­μ„ λͺ…ν™•νžˆ μ •μ˜ν•΄μ£Όκ³  κ΅¬ν˜„ λ°©ν–₯을 μžμ—°μŠ€λŸ½κ²Œ μž‘μ•„μ€λ‹ˆλ‹€.

describe("λ©”μ‹œμ§€λ₯Ό μž…λ ₯ν•  λ•Œ") {
    
    beforeEach {
        inputText = liveChatStub.newInputText
    }

    it("λ™μž‘ κ²€μ¦ν•œλ‹€") {
        await sut.process(LiveChatRequest.OnChangeMessage(inputText: inputText))
        
        // μž…λ ₯된 λ©”μ‹œμ§€κ°€ μ΅œλŒ€ κΈ€μž 수λ₯Ό μ΄ˆκ³Όν•˜μ§€ μ•Šλ„λ‘ μ œν•œν•œλ‹€
        verify(liveMessageOperation).restrictToMaxLength(text: inputText)
    }

    context("μž…λ ₯된 λ©”μ‹œμ§€κ°€ μ΅œλŒ€ κΈ€μž 수λ₯Ό μ΄ˆκ³Όν–ˆλ‹€λ©΄") {
        var limitedText: String!
        
        beforeEach {
            limitedText = liveChatStub.limitedText

            stub(liveMessageOperation) {
                $0.restrictToMaxLength(text: inputText).thenReturn(limitedText)
            }
        }
        
        it("λ™μž‘ κ²€μ¦ν•œλ‹€") {
            await sut.process(LiveChatRequest.OnChangeMessage(inputText: inputText))
            
            // κΈ€μžμˆ˜ μ œν•œ ν† μŠ€νŠΈλ₯Ό λ…ΈμΆœν•œλ‹€
            verify(presenter).present(LiveChatResponse.toast(.limitedTextError))
            
            // chatInputTextλ₯Ό μ œν•œν•œ λ©”μ‹œμ§€λ‘œ κ°±μ‹ ν•΄μ€€λ‹€
            verify(presenter).present(LiveChatResponse.chatInputText(limitedText))
        }
    }
}

3. μ„±κ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈ λ§Œλ“€κΈ°

이제 2λ‹¨κ³„μ—μ„œ μž‘μ„±ν•œ ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όλ˜λ„λ‘ μ‹€μ œ κ΅¬ν˜„ μ½”λ“œλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.

이 κ³Όμ •μ—μ„œλŠ” ν…ŒμŠ€νŠΈμ— 맞좰 핡심 λ‘œμ§μ„ μ€‘μ‹¬μœΌλ‘œ μžμ—°μŠ€λŸ½κ²Œ μ½”λ“œλ₯Ό μ •λ¦¬ν•˜κ²Œ λ˜μ—ˆκ³ , μ‹€μ œ μš”κ΅¬μ‚¬ν•­μ— 맞좘 μ΅œμ†Œν•œμ˜ κ΅¬ν˜„μ„ λΉ λ₯΄κ²Œ 확보할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 또 λΆˆν•„μš”ν•œ λΆ„κΈ°λ‚˜ κ³Όλ„ν•œ λ°©μ–΄ μ½”λ“œλ₯Ό λ„£μ§€ μ•Šμ•„λ„ λ˜μ–΄, 전체 ꡬ쑰도 더 λ‹¨μˆœν•˜κ³  λͺ…ν™•ν•΄μ‘ŒμŠ΅λ‹ˆλ‹€.

protocol LiveMessageOperator: Sendable {
        func verifyMessage(content: String) async throws -> Bool
}
enum LiveChatRequest {
        ...
        struct OnChangeMessage {
            let inputText: String
        
        init(inputText: String) {
            self.inputText = inputText
        }
    }
}

enum LiveChatResponse: Equatable {
        ...
    case chatInputText(String)
    case toast(ToastKind)

        enum ToastKind: Equatable {
        case limitedTextError
    }
}

func process(_ request: LiveChatRequest.OnChangeMessage) async {
    if let limitedText = await dependency.liveMessageOperation.restrictToMaxLength(text: request.inputText) {
        presenter?.present(.toast(.limitedTextError))
        presenter?.present(.chatInputText(limitedText))
    }
}

4. ν˜‘λ ₯ 객체(router, presenter λ“±) κ΅¬ν˜„

이제 μ‹€μ œ 의쑴 객체듀을 κ΅¬ν˜„ν•  μ°¨λ‘€λ‘œ, λ‹€μŒκ³Ό 같은 ν˜‘λ ₯ 객체 LiveMessageOperation, Presenter λ“±μ˜ ꡬ성 μš”μ†Œλ₯Ό 이 μ‹œμ μ—μ„œ μ±„μ›Œλ‚˜κ°‘λ‹ˆλ‹€.

final actor LiveMessageOperation: LiveMessageOperator {
        ...

    func restrictToMaxLength(text: String) async -> String? {
        // κ΅¬ν˜„ μž‘μ„± - μƒλž΅
    }
}
extension LiveChatPresenter: LiveChatPresentationLogic {
    func present(_ response: LiveChatResponse) {
        switch response {
                case let .toast(kind):
            switch kind {
            case .messageNotSent:
                // κ΅¬ν˜„ μž‘μ„± - μƒλž΅ 
            }
            
            case let .chatInputText(text):
                // κ΅¬ν˜„ μž‘μ„± - μƒλž΅ 
        }
    }
}

5. λ¦¬νŒ©ν† λ§ 및 ν…ŒμŠ€νŠΈ μœ μ§€

λͺ¨λ“  ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όν•œ ν›„μ—λŠ” κ΅¬ν˜„ μ½”λ“œμ™€ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μ •λ¦¬ν•˜κ³  κ°œμ„ ν•©λ‹ˆλ‹€.

이 λ‹¨κ³„μ—μ„œλŠ” μ€‘λ³΅λœ μ½”λ“œλ‚˜ λΆˆν•„μš”ν•œ μž„μ‹œ κ΅¬ν˜„ 등을 μ œκ±°ν•˜κ³ , 더 λͺ…ν™•ν•˜κ³  μΌκ΄€λœ ꡬ쑰둜 λ¦¬νŒ©ν† λ§ν•˜λŠ” μž‘μ—…μ΄ μ΄λ€„μ§‘λ‹ˆλ‹€.

예λ₯Ό λ“€μ–΄, ν…ŒμŠ€νŠΈ μ½”λ“œμ—μ„œλŠ” μ€‘λ³΅λ˜λŠ” mock μ„ΈνŒ…μ΄λ‚˜ assertion μ½”λ“œλ₯Ό beforeEachλ‚˜ μ»€μŠ€ν…€ 헬퍼 ν•¨μˆ˜λ‘œ 정리해, ν…ŒμŠ€νŠΈμ˜ λͺ©μ μ΄ 더 λšœλ ·ν•˜κ²Œ λ“œλŸ¬λ‚˜λ„λ‘ κ°œμ„ ν•©λ‹ˆλ‹€.

μ΄λŸ¬ν•œ λ¦¬νŒ©ν† λ§ 과정을 톡해 ν…ŒμŠ€νŠΈμ™€ κ΅¬ν˜„ μ½”λ“œ λͺ¨λ‘ 가독성과 μœ μ§€λ³΄μˆ˜μ„±μ„ κ°–μΆ˜ ꡬ쑰둜 닀듬을 수 μžˆμŠ΅λ‹ˆλ‹€.


μ΄λ ‡κ²Œ 5단계 ν…ŒμŠ€νŠΈ 과정을 λ°ŸμœΌλ©΄μ„œ, iOSνŒ€μ€ 라이브 슀트리밍 ꡬ쑰 개편 μž‘μ—…μ„ μ•ˆμ •μ μœΌλ‘œ λ§ˆλ¬΄λ¦¬ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

λŠλ‚€ 점

TDDλ₯Ό 처음 λ„μž…ν•˜λ©΄μ„œ 인상 κΉŠμ—ˆλ˜ 점을 κ³΅μœ λ“œλ¦¬μžλ©΄,

  • ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ λͺ…ν™•ν•œ 개발 κ°€μ΄λ“œ 역할을 ν•΄μ£Όμ–΄, κ΅¬ν˜„ λ°©ν–₯을 μžƒμ§€ μ•Šκ³  κ°œλ°œν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
  • ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ λ§ˆλ ¨λ˜μ–΄ μžˆμœΌλ―€λ‘œ, λ¦¬νŒ©ν† λ§ 이후에도 κΈ°μ‘΄ κΈ°λŠ₯이 정상 λ™μž‘ν•˜λŠ”μ§€ λΉ λ₯΄κ²Œ 검증할 수 μžˆμŠ΅λ‹ˆλ‹€.
  • ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•˜λ©΄μ„œ κΈ°λŠ₯ κ΅¬ν˜„λΏλ§Œ μ•„λ‹ˆλΌ μ½”λ“œμ˜ ν’ˆμ§ˆκ³Ό κ΅¬μ‘°κΉŒμ§€ ν•¨κ»˜ 점검할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
  • μ•„ν‚€ν…μ²˜ 개편 κ³Όμ •μ—μ„œ 미처 놓칠 수 μžˆλŠ” λΆ€λΆ„(μŠ€νŽ™, μ‚¬μ΄λ“œ μ΄νŽ™νŠΈ λ“±)을 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜μ—¬ 보닀 μ•ˆμ „ν•˜κ²Œ μ§„ν–‰ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

λ¬Όλ‘  μ²˜μŒμ—λŠ” λ‹€μ†Œ 느리고 번거둭게 λŠκ»΄μ§„ 적도 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ λ§ˆμ§€λ§‰μ— ν…ŒμŠ€νŠΈ 컀버리지λ₯Ό ν™•μΈν•˜κ³  μˆ˜λ™ ν…ŒμŠ€νŠΈκΉŒμ§€ μ§„ν–‰ν•œ κ²°κ³Ό, 큰 문제 없이 μ•ˆμ •μ μœΌλ‘œ λ™μž‘ν•œλ‹€λŠ” 것을 ν™•μΈν•˜κ³ λ‚˜μ„œ ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ μ•ˆμ •μ„±κ³Ό μœ μ§€λ³΄μˆ˜μ„± 확보에 μ–Όλ§ˆλ‚˜ μ€‘μš”ν•œ 역할을 ν•˜λŠ”μ§€ 싀감할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

λ§ˆλ¬΄λ¦¬ν•˜λ©°

TDD λ„μž…λΏ μ•„λ‹ˆλΌ, μ•„ν‚€ν…μ²˜ ꡬ쑰 개편, μƒˆλ‘œμš΄ νŒ€μ›λ“€κ³Όμ˜ ν˜‘μ—…μ΄λΌλŠ” 3κ°€μ§€ 큰 λ³€ν™”λ₯Ό λ™μ‹œμ— λ§ˆμ£Όν•œ iOSνŒ€μ—κ²Œλ„ μ €μ—κ²Œλ„ 의미 μžˆλŠ” μ‹œκ°„μ΄μ˜€λ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€. 이 글이 TDDλ₯Ό 처음 μ‹œλ„ν•΄λ³΄κ±°λ‚˜, ν…ŒμŠ€νŠΈ λ¬Έν™”λ₯Ό λ„μž…ν•˜λ €λŠ” 개발자 λΆ„λ“€κ»˜ μž‘κ²Œλ‚˜λ§ˆ 도움이 λ˜μ—ˆκΈ°λ₯Ό λ°”λžλ‹ˆλ‹€.

λκΉŒμ§€ μ½μ–΄μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€!

← λͺ©λ‘μœΌλ‘œ λŒμ•„κ°€κΈ°

Art Changes Life

λ…Έλ¨ΈμŠ€μ™€ ν•¨κ»˜ μ—”ν„°ν…Œν¬ 산업을 ν˜μ‹ ν•΄λ‚˜κ°ˆ 멀버λ₯Ό μ°ΎμŠ΅λ‹ˆλ‹€.

μ±„μš© 쀑인 곡고 보기