TDD란? 무엇인가??
TDD에 대해서는 가볍게만 알아보자면
Test-Driven-Development 소프트웨어 개발 방법론 중 하나임.

테스트 코드를 먼저 작성하고 그에 맞게 실제 코드를 구현하는 방식
TDD는 아래와 같은 순서로 진행

1. red - 실패하는 테스트 코드 작성(요구사항 충족하지 않는 테스트 케이스 작성)
2. Green - 테스트를 통과하는 최소한의 코드 작성
3. Refactor - 테스트의 성공을 유지하면서 작성한 코드 개선, 중복 제거 및 품질 향상
위 과정을 반복하면서 요구사항을 충족하는 코드를 점점 디벨롭.
TDD를 실천하는 방법??으로 Unit Test가 있다고 함!!!!
유닛테스트(Unit Test)란?
유닛 테스트는 컴퓨터 프로그래밍에서 소스 코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차임. 즉, 모든 함수와 메서드에 대한 테스트 케이스를 작성하는 절차를 말함.
유닛 테스트는 작성한 프로그램이 의도한 대로 동작하는지 검증하는 가장 작은 단위의 테스트임.
이를 통해서 각 모듈(클래스, 메서드)들이 잘 동작하는지 확인할 수 있음.
왜 써??
보통 개발할 때 분명히 빌드를 하며 제대로 동작하는 것을 확인하고 커밋을 할 텐데?
오히려 테스트 코드를 작성하는 시간이 더 오래 걸릴 것 같아..🤔 비효율적일꺼 같지만 이미 많은 기업에서 유닛 테스트를 적용하고 있다고 함.
1. 각각의 모둘을 부분적으로 확인할 수 있어 어떤 모듈에서 문제가 발생하는지 빠른 확인이 가능
2. 전체 프로그램을 빌드하는 대신 유닛 단위로 빌드해 확인하므로 시간 절약
유닛 테스트에는 "FIRST"라는 원칙
속도(Fast)
느린 테스트는 개발자가 코드를 수정하고 결과를 확인하기까지 시간이 많이 걸리므로 생산성을 저하시킴. 따라서 유닛 테스트에서는 빠른 속도로 테스트를 수행할 수 있도록 설계해야 함.
독립적(Independent/ Isolated)
유닛 테스트는 각각의 테스트가 서로 독립적으로 실행될 수 있어야 함. 이것은 특정 테스트의 결과가 다른 테스트에 영향을 미치지 않고 독립적으로 실행한다는 것을 의미함.
이러한 방식으로 작성된 유닛 테스트는 코드 변경에 대해 신속하게 반응할 수 있음.
반복적 (Repeatable)
유닛 테스트는 반복적으로 실행될 수 있어야 함. 실행 순서나 실행환경이 달라져도 동일한 결과를 보여주어야 함. 이러한 특징은 테스트의 신뢰성을 높일 수 있음.
자동화 (Self-Automation)
자동화란, 테스트를 실행하고 결과를 확인하는 모든 과정을 자동으로 처리하는 것을 의미함. 수동으로 테스트를 수행하는 것보다 자동화된 테스트를 수행하면 시간과 비용을 절약할 수 있음. 또한 자동화된 테스트는 개발자가 코드를 변경하거나 기능을 추가할 때마다 쉽게 실행할 수 있어서 버그를 신속하게 찾을 수 있음.
로그 파일에 대한 프로그램의 해석보다는 "pass" or "fail" 출력
시점 (Timely)
코드를 수정을 했을 때 해당 수정으로 인해 다른 기능이 영향을 받는지 확인하기 위해서 즉, 코드가 수정되어 다른 부분에서 영향을 미치지 않았는지 확인할 수 있는 시점에 테스트를 실행해야 함. (ex. 수정된 코드를 다른 부분에서 호출하기 전에 유닛 테스트를 실행) 코드 수정 후 변경 사항을 더욱 빠르게 확인할 수 있도록 도와줌.
사용
우선 Test코드를 작성할 파일이 필요함
1. 프로젝트를 처음 만들 때 include Unit Test를 체크
2. 기존 프로젝트에서 추가하고 싶으면
프로젝트 파일 선택 -> Editor -> Add Target > Unit Testing Bundle
하면 아래와 같은 파일이 생성됨.
import XCTest
final class SwiftStudy_17Tests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
// Any test you write for XCTest can be annotated as throws and async.
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
measure {
// Put the code you want to measure the time of here.
}
}
}

XCTest란 Xcode에서 단위 테스트, 성능 테스트, UI 테스트를 지원하는 프레임워크임.
각각의 테스트가 서로 영향을 미치지 않도록(독립적으로 실행되도록) 메서드를 통해 테스트를 도와줌.

XCTest 프레임워크는 XCTest, XCTestCase 크게 두개의 클래스로 이루어져 있음
XCTestCase - 테스트 케이스, 테스트 방법, 성능 테스트를 정의하기 위한 기본 클래스임.
XCTest - 테스트 생성, 관리 및 실행을 위한 추상 기본 클래스임.

XCTestCase의 정의를 살펴보면 XCTest를 서브클래싱하고 있음.
import XCTest
final class SwiftStudy_17Tests: XCTestCase {
}
테스트 파일을 생성하고 기본적으로 완성되어 있는 클래스를 보면, XCTestCase를 상속받아 두 가지 클래스(XCTestCase, XCTest)를 서브클래싱하여 사용할 수 있게 됨.
메서드
setUpWithError
테스트 시작 직후, 가장 먼저 실행되는 코드, 테스트를 위한 모델이나 시스템을 정의하는 역할
초기화 코드를 작성하는 함수 -> 각 테스트 함수의 호출 전에 호출되는 함수
tearDownWithError
테스트가 끝나기 직전, 가장 마지막에 실행되는 코드 release, deinit, dispose 등의 역할
해제 코드를 작성하는 함수 -> 테스트가 다 끝나면 이곳에 해제를 해주면 됨.
testExample
테스트 함수 예시
testXXX로 명명된 함수들이 UnitTest시 모두 실행됨.
setUpWithError 실행 > 각종 testXXX 함수를 실행 > tearDownWithError 순으로 진행됨.
테스트 케이스를 작성하는 함수 -> 테스트하려는 함수가(기능) 올바른 결과를 내는지 확인하는 함수임.
testPerformanceExample
퍼포먼스를 테스트를 하는 함수의 예시.
성능 테스트 케이스를 작성하는 함수 -> 시간을 측정하는 코드를 작성하는 함수임.
순서
1. setUpWithError()에서 테스트하고자 할 Class를 초기화해 주고
2. testExample()에서 테스트 코드가 실행되며
3. 테스트가 완료되면 tearDownWithError()에서 해제시켜 줌

테스트해 볼 프로젝트

아래는 테스트할 updateCurrentPage 함수임. Page를 업데이트하는 기능을 함.
struct NextAndPreviousButton: View {
@Binding var currentPage: Int
var body: some View {
Spacer()
HStack {
Button {
currentPage = updateCurrentPage(isNext: false, currentPage: currentPage)
} label: {
Text("Previous")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
Button {
currentPage = updateCurrentPage(isNext: true, currentPage: currentPage)
} label: {
Text("Next")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
}
.padding(20)
}
private func updateCurrentPage(isNext: Bool, currentPage: Int) -> Int {
var newPage = currentPage
if isNext {
newPage += 1
} else {
if currentPage < 1 {
newPage = 2
} else {
newPage -= 1
}
}
newPage %= 3
return newPage
}
}
우선 @testable import로 테스트할 함수가 있는 모듈을 불러옴.

final class SwiftStudy_17Tests: XCTestCase {
var nextAndPreviousButtonView: NextAndPreviousButton?
}
테스트할 객체를 정해줌.
override func setUpWithError() throws {
try super.setUpWithError()
nextAndPreviousButtonView = NextAndPreviousButton(currentPage: .constant(0))
}
아까 위에서 setUpWithError()는 초기화 코드를 작성하는 함수라고 했음.
테스트가 실행되기 전에 먼저 실행되는 부분으로 객체를 할당함.
override func tearDownWithError() throws {
nextAndPreviousButtonView = nil
try super.tearDownWithError()
}
tearDownWithError()는 해제 코드를 작성하는 함수임.
테스트가 종료되면 실행되는 부분으로 객체를 해제함.
테스트 코드로 작성 전!!!
internal func updateCurrentPage(isNext: Bool, currentPage: Int) -> Int
private를 internal로 변경해 주어 테스트 코드가 접근할 수 있게 만들어 줌.
private 비공개 접근 수준 - 정의한 블록 내부에서만 접근 가능
internal 내부 접근 수준 - 하나의 모듈 내부에서만 접근 가능
테스트할 함수의 이름은 test로 시작해야 함!!!
func testNextButton_2To0() {
let isNext = true
var currentPage = 2
currentPage = nextAndPreviousButtonView!.updateCurrentPage(isNext: isNext, currentPage: currentPage)
XCTAssertEqual(0, currentPage)
}
XCTAssertEqual은 currentPage 값이 0이라고 확인하는 걸 의미함.
즉, 0이 아니면 틀리기 때문에 오류가 나올 것이고, 0이 맞다면 무사히 넘어갈 것임.
테스트를 실행해 보면

테스트가 통과하면 ✅ 체크 표시가 되며
테스트를 모두 통과하면

passed나 나옴!!
만약 틀린 값을 넣으면

오류가 남.
테스트 케이스 작성법
테스트 메서드는 항상 test로 시작하고, 무엇을 테스트하는지에 대해 설명해야 함!!!
테스트 메서드는 given, when, then 부분으로 나누어 작성하는 것이 좋음.
given - 테스트에 필요한 값을 설정하는 부분.
when - 테스트할 코드를 실행하는 부분.
then - 테스트에 기대되는 결괏값을 단정하는 부분.
아직 모든 메서드를 사용해보지 않았지만 테스트 코드의 필요성과 왜?? 실무에서 테스트코드를 작성하는지 알게 되었음.
다만 TDD에서 실패하는 테스트 코드를 먼저 작성한다는 말은 아직 와닿지가 않는달까...
참고 및 인용
https://leeari95.tistory.com/60
https://ios-daniel-yang.tistory.com/63
https://shwoghk14.blogspot.com/2023/11/ios-swiftui-unit-test.html
'Swift' 카테고리의 다른 글
| [Swift] Dictionary 부족한 점 추가 (2) | 2024.06.21 |
|---|---|
| [Swift] stride() 알아보기 (1) | 2024.05.27 |
| [Swift] replacingOccurrences(of:with:) (2) | 2024.05.12 |
| [Swift] 클로저(Closures) (3) | 2024.05.05 |
| [Swift] 옵셔널(Optional)[4] nil 병합 연산자(??) (Nil-Coalescing Operation) (0) | 2024.03.28 |