이번 게시물에서는 XCTest를 공부하며 생긴 궁금증들을 정리해보겠습니다. XCTest를 작성하는 방법에 대한 글은 이 다음에 바로 올리도록 하겠습니다.
테스트 코드를 작성하기 전에, 무엇을 테스트할지 파악하는 게 우선이다. 밑의 항목은 일반적으로 고려하는 테스트 대상이다.
- Core functionality: Model classes and methods and their interactions with the controller
- The most common UI workflows
- Boundary conditions
- Bug fixes
Test Code의 종류
- Unit Test
- UI Test
- Performance Test
XCTest란?
Unit Test, UI Test, Performance Test를 수행하게 해주는 iOS의 프레임워크.
XCTest 동작 흐름
- setUpWithError
- example1
- tearDownWithError
- setUpWithError
- example2
- tearDownWithError
- setUpWithError
- …
tearDown이란?
XCTest에서 tearDown()은 모든 테스트 메서드(이름이 "test"로 시작하는 메서드)의 실행이 완료된 후에 호출되는 메서드입니다.
tearDown()의 목적은 테스트 중에 생성된 모든 리소스나 객체를 정리하는 것입니다. 이렇게 하면 각 테스트가 격리되고 후속 테스트에 영향을 줄 수 있는 잔여 효과가 발생하지 않습니다.
예를 들어, 테스트 메서드가 객체의 인스턴스를 생성하는 경우 tearDown()을 사용하여 해당 객체의 할당을 해제하고 관련 메모리를 확보할 수 있습니다. 이렇게 하면 객체가 테스트 메서드의 범위를 벗어나 지속되어 다른 테스트를 방해하는 것을 방지할 수 있습니다.
요약하자면, tearDown()은 각 테스트 메서드 이후에 호출되는 메서드로, 테스트 중에 생성된 리소스를 정리하는 데 사용됩니다.
Test Code의 기본 원리 : FIRST
약어 FIRST는 효과적인 단위 테스트를 위한 간결한 기준 집합을 설명합니다. 이러한 기준은 다음과 같습니다.
- First, 빠르다 : 테스트는 빠르게 실행되어야 합니다.
- Independency/Isolation, 독립/격리 : 테스트가 서로 상태를 공유하지 않아야 합니다.
- Repeatable, 반복 가능한 : 테스트를 실행할 때마다 동일한 결과를 얻어야 합니다. 외부 데이터 공급자 또는 동시성 문제로 인해 간헐적인 오류가 발생할 수 있습니다.
- Self-validating, 자가 검증 : 테스트는 완전히 자동화되어야 합니다. 프로그래머의 로그 파일 해석에 의존하지 않고 '통과' 또는 '실패' 중 하나를 출력해야 합니다.
- Timely, 시기 적절성 : 이상적으로는 테스트를 테스트하는 프로덕션 코드를 작성하기 전에 테스트를 작성해야 합니다. 이를 테스트 중심 개발이라고 합니다. FIRST 원리를 따르면 테스트가 앱의 장애물이 되지 않고 명확하고 유용하게 유지됩니다.
비동기 테스트
비동기 테스트 사용 예시 : URLSession
비동기 테스트는 일반적으로 속도가 느리기 때문에, 빠른 테스트 코드와 분리해야 합니다.
비동기 테스트 중 Fast Fail을 위한 방법 → then의 상황을 when 밖으로 빼는 방법
XCTSkipUnless도 적절히 사용할 것! (전제 조건 자체가 충족되는지! 왜냐하면 전제조건 자체가 충족이 안되면 테스트를 진행하면 안됨. 테스트를 중지해야 함)
XCTest function에 따른 카테고리화
XCTest에는 여러 가지 function이 있습니다. 여기에 따라 XCTest의 수행을 카테고리화 할 수 있습니다.
- XCTAssertEqual(expression1, expression2, ...) - Use this function to compare two non-optional values of the same type.
- XCTAssertNil(expression, ...) - Asserts that an expression is nil
- XCTFail(_ message: String) - This function generates a failure immediately and unconditionally.
- XCTSkipUnless(expression, ...) - Skips remaining tests in a test method unless the specified condition is met.
Faking Objects and Interactions 상황의 예시
- iOS 앱을 빌드할 때 사용자가 카메라 롤에서 사진을 선택할 수 있도록 UIImagePickerController를 사용할 수 있습니다. 이 객체는 시스템에서 제공하는 객체이므로 그 동작이나 구현을 변경할 수 없습니다. UIImagePickerController와 상호 작용하는 테스트를 작성하는 경우 해당 테스트는 시스템 객체의 동작에 따라 달라지며 느리거나 반복할 수 없을 수 있습니다.
- 또 다른 예는 URLSession 객체를 사용하여 앱에서 네트워크 요청을 하는 것입니다. URLSession 객체의 동작은 시스템에 의해 결정되며 네트워크 연결 및 서버 응답 시간과 같은 다양한 요인에 의해 영향을 받을 수 있습니다. 이러한 외부 요인에 의존하는 경우 URLSession과 상호 작용하는 테스트가 느리거나 반복되지 않을 수 있습니다.
- 앱에 웹 콘텐츠를 표시하기 위해 UIWebView 또는 WKWebView와 같은 시스템 제공 개체를 사용할 수도 있습니다. 이러한 객체는 시스템에서 제공되며 표시되는 웹사이트 또는 디바이스의 네트워크 연결에 따라 예측할 수 없는 동작이 발생할 수 있습니다. 이러한 개체와 상호 작용하는 테스트는 웹뷰 또는 표시되는 콘텐츠의 동작에 따라 느리거나 반복되지 않을 수 있습니다.
이러한 모든 경우 개체는 시스템 또는 라이브러리에서 제공되며 앱 개발자가 제어할 수 없습니다. 이러한 개체와 상호 작용하는 테스트는 사용자가 제어할 수 없는 외부 요인에 따라 달라지므로 속도가 느리거나 반복할 수 없습니다.
→ mock objects, 임의의 객체를 만들어서 테스트해봐야 함!
→ 의존성을 가지는 객체가 존재하는 코드를 테스트하는 경우, 가짜 의존성을 가진 객체를 주입하면 됩니다!
여기서는 임의의 숫자를 받는 URLSession을 테스트하기 위해 Faking Objects and Interations을 합니다.
BDD(Behavior Driven Development)
- given
- 테스트하려는 상황 세팅
- 이 섹션에서는 테스트의 전제 조건을 설정합니다. 테스트 대상 시스템 또는 개체의 초기 상태를 설정합니다. 즉, 테스트가 수행될 단계를 설정합니다.
- 예시: 사용자가 로그인하여 앱의 홈페이지에 있다고 가정합니다.
- when
- 테스트하려는 구체적인 동작 서술
- 이 섹션에서는 테스트 중에 발생하는 작업 또는 이벤트를 설명합니다. 테스트의 실제 실행을 나타내며, 테스트 대상을 명확하게 설명하는 방식으로 작성해야 합니다.
- 예시: 사용자가 홈페이지에서 '항목 추가' 버튼을 클릭할 때.
- then
- 테스트 결과 설명. 결과에 대해 어떤 처리를 할 것인지
- 이 섹션에서는 테스트의 예상 결과 또는 결과를 설명합니다. 테스트가 실행될 때 어떤 일이 일어날지 지정하며, 일반적으로 어설션으로 표현됩니다.
- 예시: 그러면 사용자의 카트에 새 품목이 추가되고 그에 따라 카트 총액이 업데이트되어야 합니다.
func testAddToCart() {
// given
let user = User(username: "testuser", password: "password")
user.login()
let homePage = HomePage()
// when
homePage.addItemToCart(item: "Shirt")
// then
XCTAssertEqual(user.cart.items.count, 1)
XCTAssertEqual(user.cart.total, 20.0)
}
이러한 방식으로 테스트 케이스를 구조화하면 가독성과 유지 관리가 쉬워질 뿐만 아니라 테스트 중에 발생하는 문제를 쉽게 식별하고 디버깅할 수 있습니다!
stub과 mock의 의미 차이
- stub
스텁은 실제 객체를 대체하고 메서드 호출에 대해 미리 정의된 답변을 제공하는 간단한 객체입니다. 테스트 중인 코드를 종속성으로부터 분리하여 테스트가 코드 자체의 동작에 집중할 수 있도록 하는 데 사용됩니다. 예를 들어 코드에 일부 데이터를 검색하기 위해 네트워크 호출을 수행해야 하는 메서드가 있다고 가정해 보겠습니다. 메서드의 동작을 테스트하기 위해 네트워크 호출을 모방하고 항상 동일한 사전 정의된 응답을 반환하는 스텁 객체를 만들 수 있습니다. 이렇게 하면 네트워크 연결에 대한 걱정 없이 메서드의 동작을 테스트할 수 있습니다.class NetworkStub: Network { func getData() -> Data { // 미리 정의된 데이터 반환 } }
- mock
반면에 모의 객체는 실제 객체의 동작을 시뮬레이션하지만 더 많은 제어 기능을 제공하고 객체 간의 상호 작용을 확인할 수 있는 객체입니다. 모의 객체는 객체 또는 컴포넌트 간의 복잡한 상호 작용을 테스트하고 올바른 매개 변수를 사용하여 올바른 메서드가 호출되는지 확인하는 데 자주 사용됩니다. 예를 들어 이메일을 보내는 개체가 있는데 이 개체가 올바른 제목과 본문으로 올바른 이메일을 보내는지 테스트하고 싶다고 가정해 보겠습니다. 이메일 전송 동작을 시뮬레이션하는 모의 개체를 만든 다음 올바른 매개 변수를 사용하여 올바른 메서드가 호출되는지 확인할 수 있습니다.class EmailMock: EmailSender { var sentEmail: Email? func sendEmail(email: Email) { sentEmail = email } }
요약하자면, 스텁은 메서드의 동작을 테스트하기 위해 미리 정의된 응답으로 의존성을 대체하는 데 사용되며, 모의는 의존성의 동작을 시뮬레이션하고 객체 간의 상호 작용을 확인하는 데 사용됩니다.
Unit Test의 종류
- 기본 단위 테스트 (예상 값을 확인) (Test)
- 비동기 테스트 (SlowTest) - Fast Fail 처리 절차 있었음
- Stub 또는 Mock을 이용한 Fake Object Interactions 테스트 (FakeTest, MockTest) - Stub, Mock 활용 상황
'iOS > Swift' 카테고리의 다른 글
[iOS - Swift] 의존 관계 역전 원칙 (DIP, Dependency Inversion Principle) (0) | 2023.03.15 |
---|---|
[iOS - Swift] TDD를 위한 Unit Test 코드 작성하기 (Feat. XCTest) (0) | 2023.03.08 |
[iOS - Swift] static func vs func (0) | 2023.03.06 |
[Swift] Struct, Initializer (구조체와 이니셜라이저) (0) | 2022.05.10 |
[iOS 앱 프로그래밍] 회원가입 화면 구현 - 2. 화면의 전환 (1) 내비게이션 인터페이스 (0) | 2021.08.09 |