내용 정리
XCTest를 사용해보기 위해 공부하다가 마주한 TDD라는 개념에 대해 정리를 해보려고 한다.
TestDrivenDevelopment(TDD)는 소프트웨어 개발 방법론으로, 테스트를 먼저 작성하고 그 테스트를 통과하기 위한 코드를 작성하는 방식이다.
이는 높은 코드 품질과 유지보수를 목표로 하며, 개발 과정에서 빠르고 지속적인 피드백을 제공해준다.
즉, 개발 코드를 작성하기 전에 테스트 코드를 먼저 작성하여 테스트를 진행하고, 설계를 수정하는 과정을 거친 후에 개발에 착수하는 방식이다.
Fail: 테스트를 작성하고 실행한다(처음은 실패)Pass: 테스트를 통과하기 위해 최소한의 코드를 작성한다Refactor: 테스트가 통과하면 코드를 리팩토링하여 개선한다. 단, 이 과정에서 테스트가 계속 통과해야 한다.TDD 프로세스를 따를 때의 장점은 아래와 같다.
TDD 프로세스를 따르면 테스트 코드를 먼저 작성해야 하기 때문에 명확한 기능과 구조를 설계할 수 있게 된다.
또, 원활한 테스트 환경을 위해 최대한 재사용이 가능한 기능들(메소드)를 구현하게 되고, 이 덕분에 객체지향적인 코드를 개발하는데 도움이 된다.
테스트 코드 실행을 통해 코드가 의도한 대로 동작하는지 즉각적인 피드백을 받을 수 있기 때문에 문제가 있는 경우 설계를 빠르게 수정할 수 있다.
테스트 코드를 통해 버그 등을 미리 발견하면 버그를 해결하기 위해 설계를 수정하거나 기능을 수정하게 되고, 이를 통해 유지보수 비용을 절감할 수 있다.
기존 테스트가 리팩토링 후에도 통과하는 것이 TDD의 원칙이므로, 코드 변경의 안정성이 보장된다.
TDD 프로세스를 따를 때의 단점은 아래와 같다.
개발을 시작하기 전에 테스트 코드를 먼저 작성해야 하므로 초기 개발 속도가 느리게 느껴질 수 있다.
또, XCTest에 지식이 부족하거나 테스트 코드의 경험이 적은 경우 학습 시간이 필요하기 때문에 개발 작업에 딜레이가 발생할 수 있다.
테스트가 필요한 기능이 많아지고 프로젝트가 커질 수록 유지보수와 테스트 실행 시간이 늘어나게 된다.
때문에 이를 주기적으로 관리해주어야 한다.
내용 정리
TDD에서 학습한 내용을 바탕으로 Xcode에서 XCTest 코드를 작성해보자.
XCTest는 Swift와 Objective-C 애플리케이션에서 사용되는 기본 단위 테스트 및 UI 테스트 프레임워크이다.
애플리케이션의 코드 품질을 보장하고, 기능이 기대대로 동작하는지 검증할 수 있도록 도와준다.
단위 테스트
UI 테스트
성능 테스트
통합 테스트
XCTestCase를 상속받아 구현한다. 각 테스트는 test 접두어를 사용하는 메소드로 작성하는 것이 원칙이다.// 예시
import XCTest
class MyTests: XCTestCase {
func testExample() {
let sum = 2 + 3
XCTAssertEqual(sum, 5, "Sum calculation failed")
}
}
XCTAssertTrue(condition)XCTAssertFalse(condition)XCTAssertEqual(value1, value2)XCTAssertNotEqual(value1, value2)XCTAssertNil(value)XCTAssertNotNil(value)XCTFail("message"): 강제로 실패 처리// 예시
func testStringIsEmpty() {
let string = ""
XCTAssertTrue(string.isEmpty, "String is not empty")
}
setup에서는 주로 테스트할 클래스를 초기화 시키는 작업을 수행하고, teardown에서는 테스트한 클래스를 nil로 변환하여 메모리에서 해체시키는 작업을 수행한다.// 예시
class MyTests: XCTestCase {
var sut: TestModel!
override func setUp() {
super.setUp()
// 테스트 전에 실행할 코드
sut = TestModel()
}
override func tearDown() {
super.tearDown()
// 테스트 후에 실행할 코드
sut = nil
}
}
// 예시
func testPerformanceExample() {
self.measure {
// 측정하려는 코드
_ = Array(0...1000).sorted()
}
}
XCUIApplication 객체를 사용하여 앱을 제어할 수 있다.// 예시
import XCTest
class MyUITests: XCTestCase {
func testButtonTap() {
let app = XCUIApplication()
app.launch()
let button = app.buttons["MyButton"]
XCTAssertTrue(button.exists)
button.tap()
}
}
위에서 말했 듯 테스트코드를 작성할 때는 항상 test라는 키워드를 접두사로 사용해야 하며, 키워드 뒤에 무엇을 테스트 하는지 설명하는 텍스트를 사용한다.
또, 테스트 코드 안에는 given, when, then 섹션으로 테스트 형식을 지정하는 것이 좋은데, 이것은 테스트의 흐름을 이해하기 쉽도록 하기 위한 컨벤션이다.
given: 테스트를 위해 필요한 모든 값을 설정한다when: 테스트 중인 코드를 실행한다(메소드 등)then: 테스트 실행 결과에 대한 예상과 비교에 대한 작업을 진행한다Given - When - Then 테스트의 구조는 클라이언트 친화적인 행동 주도적인 개발(BDD: Behavior Driven Development)에서 시작된 방식이다.
Xcode에서 XCTest를 추가하는 방법은 매우 간단하다. 처음 프로젝트를 생성할 때, Testing System에서 XCTest for Unit and Tests를 선택해주면 된다.

사진을 보면 Swift Testing with XCTest UI Tests도 있는데, 이건 나중에 또 공부를 해봐야겠다.
어쨌든 이렇게 옵션을 선택하고 프로젝트를 생성하면 아래 사진처럼 테스트 파일이 자동 생성되는 것을 확인할 수 있다.

만약 프로젝트 도중에 테스트 파일을 추가하고 싶다면 몇 가지 추가 작업이 필요하다.
먼저 프로젝트 타켓으로 이동한다.

그리고 우측 하단에 있는 '+' 버튼을 누르면 템플릿을 추가할 수 있는 윈도우가 표시되는데, 여기서 filter에 test를 입력해 테스트 템플릿을 찾아 추가해주면 된다.


그럼 TARGETS에 테스트 파일이 추가되고 왼쪽의 네비게이터에 자동으로 테스트 파일이 추가된 것을 확인할 수 있다.


이제 추가된 파일을 가지고 테스트 코드를 작성하며 테스트를 진행해보면 된다.
현재 진행 중인 프로젝트에서 구현한 모달 뷰의 기능을 몇 가지 테스트 하기 위해 작성한 테스트 코드이다.
import XCTest
import RxSwift
import RxCocoa
@testable import TripLog
final class ModalViewTests: XCTestCase {
private var sut: ModalView!
override func setUpWithError() throws {
sut = ModalView(state: .createNewCashBook)
try super.setUpWithError()
}
override func tearDownWithError() throws {
sut = nil
try super.tearDownWithError()
}
// 모달뷰의 현태 status를 가져오는 메소드 테스트
func testCheckModalViewStatus() throws {
// given (modalView State = .createNewCashBook)
// when
let result = sut.checkModalStatus().modalTitle
// then
XCTAssertEqual(result, "새 가계부 만들기", "🚨 ModalView's Status is not createNewCashBook")
XCTAssertEqual(result, "새 지출내역 추가하기", "🚨 ModalView's Status is not createNewbudget")
XCTAssertEqual(result, "지출내역 수정하기", "🚨 ModalView's Status is not editBudget")
}
// 모달뷰의 active 버튼 이벤트 방출 테스트
func testModalViewActiveButtonTapped() throws {
// given
let input = sut.rx.activeButtonTapped
let disposeBag = DisposeBag()
var result: String = ""
// when
input.subscribe(onNext: {
result = "activeButtonTapped"
}).disposed(by: disposeBag)
input.accept(())
// then
XCTAssertEqual(result, "activeButtonTapped", "🚨 activeButtonTapped function is wrong")
}
// 모달뷰의 취소 버튼 이벤트 방출 테스트
func testModalViewCancelButtonTapped() throws {
// given
let input = sut.rx.cancelButtonTapped
let disposeBag = DisposeBag()
var result: String = ""
// when
input
.subscribe(onNext: {
result = "cancelButtonTapped"
}, onError: { error in
result = "\(error)"
}).disposed(by: disposeBag)
input.accept(())
// then
XCTAssertEqual(result, "cancelButtonTapped", "🚨 cancelButtonTapped function is wrong")
}
}
XCTest는 처음 사용해 보았는데, 생각한 것만큼 어렵지는 않았다.
다만 어떤 경우에 테스트를 해야 하는지, 어떻게 활용할 수 있는지가
아직 어려운 부분이다.
테스트코드를 작성하는 것이 프로젝트에서 큰 도움이 될 것 같기에
앞으로 꾸준히 공부하고 사용해 보도록 해야겠다.