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_setsAvailableFunds와 testAddItem_oneItem_addsCostToTransactionTotal 두가지 메소드는availableFunds와 CashRegister를 사용하고 있는 것을 볼 수 있다. 이는 클래스 프로퍼티로 빼줄 수 있다.
class CashRegisterTests: XCTestCase {
var availableFunds: Decimal!
var sut: CashRegister!
// ...
}
위와 같이 프로퍼티로 빼게 될경우, setup과 teardown 메소드를 이용해서 초기화해줄 수 있다.
tearDown메소드에서 nil처리를 하는 이유는 XCTestCase를 서브클래싱하는 클래스는 내부의 테스트 케이스가 모두 끝날때까지 프로퍼티를 가지고 있는다. 프로퍼티의 메모리를 계속 유지하고 있기 때문에 테스트코드를 돌릴 때 메모리와 성능 이슈가 생길 수 있다. 따라서 각 테스트 케이스가 끝나는 시점에 메모리에서 릴리즈해준다.
setUpWithErrortearDownWithError 메소드는 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)
}
}
Chapter 2 The TDD Cycle
레이먼드 아저씨 책 - iOS Test-Driven Development by Tutorials 의 두번째 챕터 TDD Cycle에서 배운 내용
TDD는 아주 간단한 프로세스로 이루어진다.
Red-Green-Refactor-Repeat (RGRR)
각 단계에서 어떤 작업이 필요한지 하나씩 예제를 통해 보자.
Example
1️⃣ Red : 실패하는 코드 작성하기
CashRegister 클래스를 초기화하는 테스트코드를 작성하자.
2️⃣ Green : 테스트를 성공하게 만들기
CashRegister클래스를 작성하여 성공하게 만들자.
3️⃣ Refactor: 코드 정리하기
아래의 테스트 코드가 있다고 해보자.
하나씩 자세히 살펴보면
testInitAvailableFunds_setsAvailableFunds
와testAddItem_oneItem_addsCostToTransactionTotal
두가지 메소드는availableFunds
와CashRegister
를 사용하고 있는 것을 볼 수 있다. 이는 클래스 프로퍼티로 빼줄 수 있다.위와 같이 프로퍼티로 빼게 될경우,
setup
과teardown
메소드를 이용해서 초기화해줄 수 있다.setup()
: 테스트 메소드가 동작하자마자 호출되는 메소드 (before)tearDown()
: 각 테스트 메소드가 불리우자마자 호출되는 메소드 (after)tearDown메소드에서 nil처리를 하는 이유는 XCTestCase를 서브클래싱하는 클래스는 내부의 테스트 케이스가 모두 끝날때까지 프로퍼티를 가지고 있는다. 프로퍼티의 메모리를 계속 유지하고 있기 때문에 테스트코드를 돌릴 때 메모리와 성능 이슈가 생길 수 있다. 따라서 각 테스트 케이스가 끝나는 시점에 메모리에서 릴리즈해준다.
setUpWithError
tearDownWithError
메소드는 setup, tearDown할 때 오류가 날 수 있는 경우에 사용한다.프로퍼티를 밖으로 빼면 테스트 함수가 간단해진다.
4️⃣ Repeat : 다시 반복하자
Basic Concept
1. 테스트 메소드 네이밍 규칙
test
로 시작testInit
testAdd_Int타입만 작성이 가능
testInit_create객체명
2. 컴파일 실패 또한 테스트 실패로 간주
3. Refactor할 때 규칙
Duplicate logic : 프로퍼티와 메소드 클래스를 만들어 반복되는 코드를 제거하자
Comments : "어떻게" 보다는 "왜"에 초점을 맞추어 작성
Code smells : Class와 Method를 만들고 코드를 리네이밍과 재구조하면서 냄새나는 코드 제거
4. Give/When/Then
위 코드를 해석해보면, 100인
avaiableFunds
가 주어졌고 (given) init(availableFunds:)를 통해 초기화한 sut가 생성된 상황이면 (when) sut의 avaiableFunds와 availableFunds는 같다. (then)정리
setup()
,tearDown()
: XCTest를 서브클래싱하는 클래스내에 존재하는 메소드, setup을 통해 각 테스트 케이스가 동작할 때 클래스 프로퍼티를 초기화하고, tearDown을 통해 각 테스트케이스마다 프로퍼티를 해제하여 메모리와 성능 이슈 방지를 방지한다.완성된 테스트 코드 예제