✅ 단위 테스트(Unit Test)란?
- 정의: 코드의 작은 단위(예: 함수, 메서드)가 예상대로 동작하는지 자동으로 확인하는 것
// 예를 들어, 이런 함수가 있다면
func add(a: Int, b: Int) -> Int {
return a + b
}
// 테스트는 이런 식으로 검증
// "add(2, 3)을 호출하면 5가 나와야 한다"
✅ 테스트의 핵심 개념
1. Given-When-Then 패턴
모든 테스트는 이 3단계로 구성
func test_saveSummaryState_shouldSaveCorrectly() {
// Given (준비): 테스트에 필요한 데이터 준비
let bookTitle = "Harry Potter"
let isExpanded = true
// When (실행): 실제로 테스트할 동작
repository.saveSummaryState(isExpanded, of: bookTitle)
// Then (검증): 예상 결과와 비교
let savedValue = repository.loadSummaryState(of: bookTitle)
XCTAssertEqual(savedValue, isExpanded)
}
2. 주요 XCTAssert 메서드들
XCTAssertEqual(a, b) // a == b
XCTAssertNotEqual(a, b) // a != b
XCTAssertTrue(condition) // condition이 true
XCTAssertFalse(condition) // condition이 false
XCTAssertNil(value) // value가 nil
XCTAssertNotNil(value) // value가 nil이 아님
3. setUp과 tearDown
class MyTests: XCTestCase {
var repository: UserDefaultsRepository!
override func setUp() {
super.setUp()
// 각 테스트 실행 전 초기화
repository = UserDefaultsRepositoryImp()
}
override func tearDown() {
super.tearDown()
// 각 테스트 실행 후 정리
repository = nil
}
}
✅ 적용해보기
Xcode에서 테스트 파일 만들기
- File > New > Target
- iOS Unit Testing Bundle 선택
- UserDefaultsTests.swift 파일 생성
UserDefaults 예시
- saveState(_:of:): 제대로 저장되었는가?
- loadState(of:): 저장된 값을 제대로 불러오는가?
- Mock 객체를 사용한 의존성 주입 테스트: UserDefaults.standard는 실제 저장소라 테스트 간 값이 섞일 수 있음
final class MockUserDefaultsRepository: UserDefaultsRepository {
var savedStates: [String: Bool] = [:]
func saveState(_ isExpanded: Bool, of bookTitle: String) {
savedStates[bookTitle] = isExpanded
}
func loadState(of bookTitle: String) -> Bool {
return savedStates[bookTitle] ?? false
}
}
// 일부 예시 코드
final class BookViewModelTests: XCTestCase {
var mockUserDefaults: MockUserDefaultsRepository!
var mockBookRepository: MockBookRepository!
var viewModel: BookViewModel!
override func setUp() {
super.setUp()
mockUserDefaults = MockUserDefaultsRepository()
mockBookRepository = MockBookRepository()
viewModel = BookViewModel(
bookRepository: mockBookRepository,
userDefaultsRepository: mockUserDefaults
)
}
func test_saveState_savesCorrectValue() {
// Given
mockBookRepository.booksToReturn = [
Book(title: "HP1", author: "J.K", releaseDate: Date(),
pages: 300, dedication: "", summary: "", chapters: [])
]
viewModel.loadBooks()
viewModel.selectBook(at: 0)
// When
viewModel.saveSummaryExpandedState(true)
// Then
XCTAssertEqual(mockUserDefaults.savedStates["HP1"], true)
}
}
👉 tearDown() 없는 이유: Mock 객체들은 메모리상의 데이터라서 테스트 끝나면 자동으로 해제되기 때문에 불필요
프로토콜 + DI 구조로 설계한 덕분에 생각보다 어렵지 않게 시도해볼 수 있었다
'iOS' 카테고리의 다른 글
[iOS] 클로저에서 async/await로 리팩토링하기 (1) | 2025.07.08 |
---|---|
[iOS] 성능 최적화: @inlinable과 lazy (1) | 2025.06.25 |
[iOS] MVVM 패턴에서 데이터 로딩 책임 분리 (0) | 2025.06.18 |
[아키텍처] Clean Architecture + MVVM (0) | 2025.06.13 |
[Swift] SOLID 원칙 iOS에 적용해보기 (1) | 2025.06.10 |