UI Test와 Unit Test
UI Test
화면에 보이는 객체가 잘 눌리는지, 화면 전환은 잘 되는지 확인할 수 있다.
또한, 접근성 관련 설정이 잘 동작하는지 테스트할 수 있다.
Unit Test
비즈니스 모델에 문제가 없는지, 로직에 대한 테스트를 하는 것!
이 안에서 UI적인 이름을 많이 가지고 있을 경우에는 추후 UI 객체를 수정하게 될 때, 테스트 코드까지 수정해야 할 수 있다.
따라서, 로직만 테스트 할 수 있는 형태로 구성하는 것이 좋다!
CLEAN CODE에서 나온 원칙으로, FIRST 원칙이 있는데... ...
Fast: 빨리 테스트를 할 수 있게끔 만들어야 한다.
Independent: 각각의 테스트가 독립적이어야 한다. (서로 영향을 받으면 안 됨)
Repeat: 반복적으로 같은 결과가 나와야 한다.
Self Validation: 스스로 검증이 가능해야 한다.
Timely: 시기 적절할 때에 해야 한다.
또한, 하나의 테스트는 하나의 결과만 가지고 오는 게 효과적이다.
테스트에서는 특정 경우에 성공할 때뿐만 아니라 실패할 때에 대한 경우를 실험할 수 있으며, 이를 테스트하는 경우에는 `XCTAssert~` 함수를 통해 테스트를 진행할 수 있다.
네트워크 테스트 시에는 기존의 테스트하는 방법과 약간 다른데,
네트워크 상황이 테스트에 영향을 미치는 것을 방지하기 위해 Test Double과 같은 방식으로 실제 객체를 대신하는 다른 객체를 생성하여 테스트하는 것이 좋다.
Test Double이란?
영화 촬영 시 위험한 촬영을 대신하는 스턴트를 말하는 말인 스턴트 더블에서 따온 말로, 실제 객체를 사용하기에는 여러 조건에 의해 테스트 결과가 달라질 수 있으므로, 영향을 받지 않는 다른 객체를 만드는 것을 의미한다.
그 종류로는 Dummy, Mock, Stub, Fake, Spy 등이 있으며 통칭해서 Test Double이라고 한다.
Unit Test는 비동기적으로 동작하여 네트워크 테스트를 할 경우에는 expectation(promise) / fullfill / wait 를 추가하는 것이 필요하다.
UI Test와 Unit Test 직접 해 보기

파일을 추가할 때, `UI Test`와 `Unit Test` 중에 원하는 걸 선택해서 할 수 있다.
돌아가는 로직만 다를 뿐, 내부 함수는 동일하다.
유닛 테스트를 하는 함수들은 세 가지로 구성되는데,
- 테스트 이전에 대상의 새로운 인스턴스를 만드는 함수(setUpWithError())
- 테스트를 하는 함수
- 테스트 이후 매번 초기화를 해 주어 test의 대상이 독립적으로 유지하도록 하는 함수(tearDownWithError())
로 구성된다.
매번 테스트를 할 때마다 1 -> 2 -> 3 -> 1 -> 2... ... 이런 식으로 반복되며, 새로운 테스트를 하기 전에 매번 새로운 인스턴스를 만들고 이후 초기화를 해 주어 이전 테스트의 결과가 이후 테스트에게 영향을 미치지 않도록 한다.
UI Test의 경우에는 마름모꼴의 테스트를 누르면 실행이 가능하며, Xcode에서 자동으로 써주거나 ,,,,,, 써주는 걸 바탕으로 어떤 식으로 진행되는지 쉽게 알 수 있으므로 생략! 하고 Unit Test 설명으로 들어가겠당.
Unit Test 해 보기
`@testable import (프로젝트)`의 경우에는 해당 target memgership을 체크하지 않아도 internal인 경우에 해당 프로젝트를 임포트하게 된다면 프로젝트의 요소를 불러와 사용할 수 있다.
import XCTest
@testable import TestSwiftUI
// LoginVC 내 valid 메서드
final class TestSwiftUITests: XCTestCase {
var sut: LoginViewController! // 시스템이 테스트하려는 대상
// 첫 번째 실행 + 네 번째 실행
// 대상의 인스턴스를 새롭게 심어주고
override func setUpWithError() throws {
let sb = UIStoryboard(name: "Login", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
sut = vc
sut.loadViewIfNeeded()
}
// 세 번째 실행 + 여섯 번째 실행
// 매번 초기화를 해주어 test의 대상이 독립적으로 유지되도록 해야 할 거임
override func tearDownWithError() throws {
// 테스트 이후 초기화 (보통 nil)
sut = nil
}
// 하나의 테스트는 하나의 결과만 가지고 오는 게 효과적임
// 두 번째 실행
func testLoginViewController_ValidEmail_ReturnTrue() throws {
// function 이름에 역할에 대해서 바로 사용할 수 있게끔 육하원칙을 맞춰서 쓰듯이 작성!
sut.emailTextField.text = "yeoni@test.com"
XCTAssertTrue(sut.isValidEmail(), "@가 없거나 6글자 미만임")
// 문구의 경우에는 테스트에 실패했을 때 알려주는 거임
}
// 다섯 번째 실행
// 테스트 결과가 성공이지만, 사실 실패 케이스를 한 것
// 테스트 결과가 성공이어야만 테스트를 가능함
// 로직에 대해서 테스트하는 것과 테스트 결과에 대한 건 다른 문제
func testLoginViewController_ValidEmail_ReturnFalse() throws {
sut.emailTextField.text = "yeonitest.com"
XCTAssertFalse(sut.isValidEmail(), "@가 없거나 6글자 미만임")
}
func testLoginViewController_Testing_ReturnNil() throws {
sut.emailTextField = nil
XCTAssertNil(sut.emailTextField, "Nil")
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
measure {
// Put the code you want to measure the time of here.
}
}
}
위와 같은 경우 UI 요소에 많은 영향을 받으므로, 직접적으로 ViewController를 불러오는 것보다
import XCTest
@testable import TestSwiftUI
final class LoginTestCase: XCTestCase {
var sut: Validator!
override func setUpWithError() throws {
sut = Validator()
}
override func tearDownWithError() throws {
sut = nil
}
func testLoginViewController_ValidEmail_ReturnTrue() throws {
let user = User(email: "yeoni@test.co", password: "123123", check: "123123")
let valid = sut.isValidEmail(email: user.email)
XCTAssertTrue(valid, "@없거나 6글자 미만임")
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
로직 자체를 시험하려고 하는 경우 뷰모델을 테스트하는 것이 좋다.
Network 로직 테스트해 보기
import XCTest
@testable import TestSwiftUI
final class NetworkTestCase: XCTestCase {
var sut: NetworkProvider!
override func setUpWithError() throws {
// 테스트 시작 전 인스턴스 생성
sut = NetworkManager.shared
}
override func tearDownWithError() throws {
// 테스트 시작 후 초기화
sut = nil
}
// unit test는 동기 테스트에 최적화되어 있음
// 비동기 테스트: expectation(promise) / fullfill / wait
func testExample() throws {
// 어떤 기능을 앞으로 수행할지 설명을 작성한다.
let expectation = expectation(description: "Lotto Number Completion Handler")
sut.fetchLotto { lotto in
print(lotto.bnusNo, lotto.drwNoDate)
XCTAssertLessThanOrEqual(lotto.bnusNo, 45)
XCTAssertGreaterThanOrEqual(lotto.bnusNo, 1)
// 정의해둔 expectation이 충족되는 시점에서 호출해서 동작을 시행했음을 알려줌!!
expectation.fulfill()
}
// 비동기 작업을 기다린다.
// 이때, 타임아웃 시간이 지나면 실패로 간주한다!
wait(for: [expectation], timeout: 5)
}
func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
'iOS > App' 카테고리의 다른 글
[SwiftUI] Widget Padding 없애기 (0) | 2024.01.07 |
---|---|
[iOS] Share 기능 구현하기 +) 메타데이터? (1) | 2023.12.24 |
[iOS] NavigationBar BackgroundColor 노치까지 채우기 (0) | 2023.12.02 |
[RxSwift] Single (1) | 2023.11.21 |
[iOS] Push Notification 보내기 (1) | 2023.11.13 |