Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TDD - TDD Cycle #40

Open
samsung-ga opened this issue Sep 8, 2022 · 0 comments
Open

TDD - TDD Cycle #40

samsung-ga opened this issue Sep 8, 2022 · 0 comments
Labels

Comments

@samsung-ga
Copy link
Owner

samsung-ga commented Sep 8, 2022

Chapter 2 The TDD Cycle

레이먼드 아저씨 책 - iOS Test-Driven Development by Tutorials 의 두번째 챕터 TDD Cycle에서 배운 내용


image

TDD는 아주 간단한 프로세스로 이루어진다.

  1. 실패하는 테스트 코드를 작성한다. (Red)
  2. 테스트가 성공하는 최소단위의 코드를 작성한다. (Green)
  3. 앱과 테스트 코드 두가지 모두 리팩토링 (Refactor)
  4. 모든 피쳐가 완료될떄까지 1~3단계를 반복한다. (Repeat)

Red-Green-Refactor-Repeat (RGRR)

각 단계에서 어떤 작업이 필요한지 하나씩 예제를 통해 보자.

Example

1️⃣ Red : 실패하는 코드 작성하기

CashRegister 클래스를 초기화하는 테스트코드를 작성하자.

스크린샷 2022-09-08 오후 2 19 42

2️⃣ Green : 테스트를 성공하게 만들기

CashRegister클래스를 작성하여 성공하게 만들자.

class CashRegister {
    init() {}
}

3️⃣ Refactor: 코드 정리하기

아래의 테스트 코드가 있다고 해보자.

class CashRegisterTests: XCTestCase {

    func testInit_setsDefaultAvailableFunds() {
        // when
        let sut = CashRegister()

        // then
        XCTAssertEqual(sut.availableFunds, 0)
    }

    func testInitAvailableFunds_setsAvailableFunds() {
        // given
        let availableFunds = Decimal(100)

        // when
        let sut = CashRegister(availableFunds: availableFunds)

        // then
        XCTAssertEqual(sut.availableFunds, availableFunds)
    }

    func testAddItem_oneItem_addsCostToTransactionTotal() {
        // given
        let availableFunds = Decimal(100)
        let sut = CashRegister(availableFunds: availableFunds)

        let itemCost = Decimal(42)

        // when
        sut.addItem(itemCost)

        // then
        XCTAssertEqual(sut.transactionTotal, itemCost)
    }
}

하나씩 자세히 살펴보면 testInitAvailableFunds_setsAvailableFundstestAddItem_oneItem_addsCostToTransactionTotal 두가지 메소드는availableFundsCashRegister를 사용하고 있는 것을 볼 수 있다. 이는 클래스 프로퍼티로 빼줄 수 있다.

class CashRegisterTests: XCTestCase {

    var availableFunds: Decimal!
    var sut: CashRegister!
		// ...
}

위와 같이 프로퍼티로 빼게 될경우, setupteardown 메소드를 이용해서 초기화해줄 수 있다.

setup() : 테스트 메소드가 동작하자마자 호출되는 메소드 (before)

tearDown() : 각 테스트 메소드가 불리우자마자 호출되는 메소드 (after)

override func setUp() {
  super.setUp()
  availableFunds = 100
  sut = CashRegister(availableFunds: availableFunds)
}
override func tearDown() {
  availableFunds = nil
  sut = nil
  super.tearDown()
}

tearDown메소드에서 nil처리를 하는 이유는 XCTestCase를 서브클래싱하는 클래스는 내부의 테스트 케이스가 모두 끝날때까지 프로퍼티를 가지고 있는다. 프로퍼티의 메모리를 계속 유지하고 있기 때문에 테스트코드를 돌릴 때 메모리와 성능 이슈가 생길 수 있다. 따라서 각 테스트 케이스가 끝나는 시점에 메모리에서 릴리즈해준다.

setUpWithError tearDownWithError 메소드는 setup, tearDown할 때 오류가 날 수 있는 경우에 사용한다.

    func testInitAvailableFunds_setsAvailableFunds() {
        // when
        let sut = CashRegister(availableFunds: availableFunds)

        // then
        XCTAssertEqual(sut.availableFunds, availableFunds)
    }

    func testAddItem_oneItem_addsCostToTransactionTotal() {
        // given
        let itemCost = Decimal(42)

        // when
        sut.addItem(itemCost)

        // then
        XCTAssertEqual(sut.transactionTotal, itemCost)
    }
}

프로퍼티를 밖으로 빼면 테스트 함수가 간단해진다.

4️⃣ Repeat : 다시 반복하자

Basic Concept

1. 테스트 메소드 네이밍 규칙

  • test 로 시작
  • 테스트할 것을 작성 ex) testInit
  • 언더바로 뒤에는 필요한 조건들을 작성 ex) testAdd_Int타입만 작성이 가능
  • 마지막으로, 나오는 결과를 작성 ex) testInit_create객체명

2. 컴파일 실패 또한 테스트 실패로 간주

3. Refactor할 때 규칙

  • Duplicate logic : 프로퍼티와 메소드 클래스를 만들어 반복되는 코드를 제거하자

  • Comments : "어떻게" 보다는 "왜"에 초점을 맞추어 작성

    // for문을 돌면서 매번 status을 확인하여 반환 (x)
    // status가 모두 true일 경우, 상태가 완료된 것으로 간주하여 true를 반환 (o)
  • Code smells : Class와 Method를 만들고 코드를 리네이밍과 재구조하면서 냄새나는 코드 제거

4. Give/When/Then

  • Given a certain conditions...
  • When a certain action happens...
  • Then an expected result occurs
func testInitAvailableFunds_setsAvailableFunds() {
  // given
  let availableFunds = Decimal(100)
  
  // when
  let sut = CashRegister(availableFunds: availableFunds)
  
  // then
  XCTAssertEqual(sut.availableFunds, availableFunds)
}

위 코드를 해석해보면, 100인 avaiableFunds가 주어졌고 (given) init(availableFunds:)를 통해 초기화한 sut가 생성된 상황이면 (when) sut의 avaiableFunds와 availableFunds는 같다. (then)

sut는 무슨 약자?
system under test

정리

  • 단계 : 실패하는 코드를 먼저 작성 -> 테스트 통과되게 코드를 작성 -> 리팩토링 -> 반복하기
  • given / when / then : 주어진 상황 속에서 어떤 액션이 주어지고 예상한 결과가 일어난다.
  • setup(), tearDown() : XCTest를 서브클래싱하는 클래스내에 존재하는 메소드, setup을 통해 각 테스트 케이스가 동작할 때 클래스 프로퍼티를 초기화하고, tearDown을 통해 각 테스트케이스마다 프로퍼티를 해제하여 메모리와 성능 이슈 방지를 방지한다.

완성된 테스트 코드 예제

import XCTest
@testable import TDDPractice

class CashRegisterTests: XCTestCase {

    var availableFunds: Decimal!
    var sut: CashRegister!
    var itemCost: Decimal!

    override func setUp() {
        super.setUp()
        availableFunds = 100
        sut = CashRegister(availableFunds: availableFunds)
        itemCost = Decimal(42)
    }

    override func tearDown() {
        availableFunds = nil
        sut = nil
        itemCost = nil
        super.tearDown()
    }

    func testInit_setsDefaultAvailableFunds() {
        // when
        let sut = CashRegister()

        // then
        XCTAssertEqual(sut.availableFunds, 0)
    }

    func testInitAvailableFunds_setsAvailableFunds() {
        // when
        let sut = CashRegister(availableFunds: availableFunds)

        // then
        XCTAssertEqual(sut.availableFunds, availableFunds)
    }

    func testAddItem_oneItem_addsCostToTransactionTotal() {
        // when
        sut.addItem(itemCost)

        // then
        XCTAssertEqual(sut.transactionTotal, itemCost)
    }

    func testAddItem_twoItems_addsCostsToTransactionTotal() {
        // given
        let itemCost = Decimal(20)
        let expectedTotal = itemCost

        // when
        sut.addItem(itemCost)

        // then
        XCTAssertEqual(sut.transactionTotal, expectedTotal)
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant