이 글은 raywenderlich.com의 iOS Unit Testing and UI Testing Tutorial을 읽고 공부를 위해 번역했습니다.
iOS Unit 테스트는 매력적이지 않지만, 테스트로 인해 흥미로운 앱이 버그가 많은 잡동사니가 되는 것을 막을 수 있기 때문에 필요합니다. 이 튜토리얼을 읽고 있다면 코드와 UI에 대한 테스트를 해야한다는 것을 이미 알고 있지만 방법을 모를 수도 있습니다.
작업 중 앱이 있을 수 있지만, 앱 확장을 위해 변경 사항을 테스트하려고 합니다. 아마 여러분은 이미 시험을 썼을 수도 있지만, 시험이 옳은지 확실하지 않을 수도 있습니다. 또는 새 앱에서 작업을 시작했는데 진행하면서 테스트하려고 합니다.
이 튜토리얼에서는 다음 방법을 보여 줍니다.
도중에, 여러분은 닌자를 테스트하는 데 사용되는 어휘 중 일부를 배울 것입니다.
이 튜토리얼의 상단 또는 하단에 있는 Download Materials 버튼을 사용하여 프로젝트 자료를 다운로드하십시오. 이 프로그램에는 UIKit Apprentice의 샘플 앱을 기반으로 하는 프로젝트 BullsEye가 포함되어 있습니다. 이것은 우연과 행운의 단순한 게임입니다. 게임 로직은 이 튜토리얼에서 테스트할 'Bulls EyeGame' 클래스에 있습니다.
어떤 테스트를 쓰기 전에, 기본을 아는 것이 중요합니다. 무엇을 테스트해야 합니까?
기존 앱을 확장하는 것이 목표인 경우 먼저 변경할 구성 요소에 대한 테스트를 작성해야 합니다.
일반적으로 테스트는 다음을 포함해야 합니다.
- 핵심 기능: 클래스 및 메서드 및 컨트롤러와의 상호 작용을 모델링합니다.
- 가장 일반적인 UI 워크플로우입니다.
- 경계 조건
- 버그 수정
FIRST라는 약자는 효과적인 단위 테스트를 위한 일련의 기준을 설명합니다. 그 기준은 다음과 같습니다.
FIRST 원칙을 따르면 앱의 장애물이 되는 대신 테스트가 명확하고 유용하게 유지됩니다.
Test navigator를 사용하면 Test 작업을 가장 쉽게 수행할 수 있습니다. 이 프로그램을 사용하여 테스트 대상을 만들고 앱에 대한 테스트를 실행할 수 있습니다.
BullsEye 프로젝트를 열고 Command-6를 눌러 테스트 탐색기를 엽니다.
왼쪽 아래 모서리에서 +를 클릭한 다음 메뉴에서 새 장치 테스트 대상...을 선택합니다.
기본 이름인 BullsEyeTests를 사용하고 Organization Identifier로 com.raywenderlich를 입력합니다. 테스트 탐색기에 테스트 번들이 나타나면 확장 버튼(disclosure triangle)을 클릭하여 확장하고 BullsEyeTests를 클릭하여 편집기에서 엽니다.
기본 템플릿은 테스트 프레임워크인 XCTest를 가져오고 SetUpWithError()``tearDownWithError()
및 예제 테스트 방법을 사용하여 XCTestCase
의 BullsEyeTests
하위 클래스를 정의합니다.
다음 세 가지 방법으로 테스트를 실행할 수 있습니다.
테스트 네비게이터 또는 거더에서 다이아몬드를 눌러 개별 테스트 방법을 실행할 수도 있습니다.
얼마나 오래 걸리고 어떻게 생겼는지 느끼기 위해 테스트를 실행하는 여러 가지 방법을 시도해 보세요. 샘플 테스트는 아직 아무 것도 하지 않아서, 그들은 매우 빨리 달립니다.
모든 테스트가 성공하면, 다이아몬드는 녹색으로 변하고 체크 표시를 보일 것입니다. 'test Performance 예제()' 끝의 회색 다이아몬드를 클릭하여 성능 결과를 엽니다.
이 튜토리얼에는 test Performance Example()
또는 testExample()
이 필요하지 않으므로 삭제하십시오.
먼저 'XCTAssert' 기능을 사용하여 BullsEye 모델의 핵심 기능을 테스트합니다. 불스 아이게임이 라운드 스코어를 정확하게 계산합니까?
BullsEyeTests.swift에서 "import XCTest" 아래에 이 줄을 추가합니다.
@testable import BullsEye
그러면 장치 테스트에서 BullsEye의 내부 유형 및 기능에 액세스할 수 있습니다.
'BullsEyeTests'의 맨 위에 다음 속성을 추가하십시오.
var sut: BullsEyeGame!
이렇게 하면 SUT(System Under Test) 또는 이 테스트 사례 클래스가 테스트와 관련된 개체인 'BullsEyeGame'의 자리 표시자가 생성됩니다.
그런 다음 'SetUpWithError()'의 내용을 다음과 같이 바꿉니다.
try super.setUpWithError()
sut = BullsEyeGame()
이렇게 하면 클래스 레벨에서 "BullsEyeGame"이 생성되므로 이 테스트 클래스의 모든 테스트가 SUT 개체의 속성과 메서드에 액세스할 수 있습니다.
잊기 전에 tearDownWithError()
에서 SUT 개체를 release하십시오. 내용을 다음으로 바꿉니다.
sut = nil
try super.tearDownWithError()
note: SetUpWithError()
에서 SUT를 만들어 TearDownWithError()
에 풀어 모든 테스트가 깨끗한 슬레이트로 시작되도록 하는 것이 좋습니다. 자세한 내용은 해당 주제에 대한 Jon Reid's Post)를 참조하십시오.
이제 첫 번째 테스트를 작성할 준비가 되었습니다!
BullsEyeTests
의 끝에 다음 코드를 추가하여 추측에 대한 예상 점수를 계산하는지 테스트합니다.
func testScoreIsComputedWhenGuessIsHigherThanTarget() {
// given
let guess = sut.targetValue + 5
// when
sut.check(guess: guess)
// then
XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")
}
테스트 방법의 이름은 항상 테스트로 시작하고 테스트 내용에 대한 설명이 이어집니다.
테스트를 given, when 및 then 섹션으로 포맷하는 것이 좋습니다.
check(guess:)
를 호출합니다.gutter 또는 테스트 탐색기에서 다이아몬드 아이콘을 클릭하여 테스트를 실행합니다. 이렇게 하면 앱이 만들어지고 실행되며, 다이아몬드 아이콘이 녹색 체크 표시로 바뀝니다! X 코드 위에 다음과 같이 보이는 성공 여부를 나타내는 순간적인 팝업도 표시됩니다.
참고: XCTestAssurations의 전체 목록을 보려면 Application's Acceptions by Category)로 이동하십시오.
일부러 불스 아이게임에 버그가 내장되어 있는데, 지금 바로 찾기 연습을 해보세요. 버그가 작동하는 것을 보기 위해 given 섹션의 "targetValue"에서 *5를 빼서 다른 모든 것을 동일하게 하는 테스트를 만듭니다.
다음 테스트를 추가합니다.
func testScoreIsComputedWhenGuessIsLowerThanTarget() {
// given
let guess = sut.targetValue - 5
// when
sut.check(guess: guess)
// then
XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")
}
추측과 목표값의 차이는 여전히 5이므로 점수는 95점이어야 합니다.
breakpoint navigator에서 Test Failure Breakpoint를 추가합니다. 이렇게 하면 테스트 방법이 failure assertion을 게시할 때 테스트 실행이 중지됩니다.
테스트를 실행하면 테스트 실패와 함께 'XCTAssert Equal' 라인에서 중지됩니다.
디버그 콘솔에서 sut
및 guess
를 검사합니다.
guess
는 목표값 - 5인데 점수 라운드는 95가 아니라 105입니다!
자세히 조사하려면 nomar debugging process를 사용하십시오. when 명령문에 중단점을 설정하고 BullsEyeGame.swift에서 difference
가 발생하는 "check(guess: )"에 중단점을 설정합니다. 그런 다음 테스트를 다시 실행하고 difference
코드로 이동하여 앱의 difference
값을 검사합니다.
문제는 difference
가 음수여서 점수가 100 - (-5)라는 점입니다. 이 문제를 해결하려면 difference
의 절대값을 사용해야 합니다. check(guess:)에서 올바른 줄을 제거하고 잘못된 줄을 삭제합니다.
두 중단점을 제거하고 테스트를 다시 실행하여 이제 성공했는지 확인합니다.
이제 모델을 테스트하고 테스트 실패를 디버그하는 방법을 배웠으니 이제 비동기 코드를 테스트하는 단계로 넘어갈 때입니다.
BullsEyeGame은 URL세션(URLSession)을 활용해 다음 게임 대상으로 임의번호를 부여합니다. URLSession
방법은 비동기적 입니다. 그들은 바로 돌아오지만 나중에야 달리기를 끝냅니다. 비동기 메서드를 테스트하려면 'XCTestExpectation'을 사용하여 비동기 작업이 완료될 때까지 테스트를 기다리도록 합니다.
비동기 테스트는 일반적으로 느리므로 더 빠른 단위 테스트와 별도로 유지해야 합니다.
BullsEyeSlowTests라는 이름의 새 장치 테스트 대상을 만듭니다. 새로운 테스트 클래스 'BullsEyeSlowTests'를 열고 기존 '가져오기' 명령문 바로 아래에 있는 BullsEye 앱 모듈을 가져옵니다.
@testable import BullsEye
이 클래스의 모든 테스트는 기본 URLSession
을 사용하여 요청을 전송하므로 sut
를 선언하고 SetupWithError()
에서 생성한 후 TearDownWithError()
로 해제합니다. 이렇게 하려면 BullsEyeSlowTests
의 내용을 다음으로 바꾸십시오.
var sut: URLSession!
override func setUpWithError() throws {
try super.setUpWithError()
sut = URLSession(configuration: .default)
}
override func tearDownWithError() throws {
sut = nil
try super.tearDownWithError()
}
다음에 비동기 테스트를 추가합니다:
// success가 빠릅니다. failure가 느립니다.
func testValidApiCallGetsHTTPStatusCode200() throws {
// given
let urlString =
"http://www.randomnumberapi.com/api/v1.0/random?min=0&max=100&count=1"
let url = URL(string: urlString)!
// 1
let promise = expectation(description: "Status code: 200")
// when
let dataTask = sut.dataTask(with: url) { _, response, error in
// then
if let error = error {
XCTFail("Error: \(error.localizedDescription)")
return
} else if let statusCode = (response as? HTTPURLResponse)?.statusCode {
if statusCode == 200 {
// 2
promise.fulfill()
} else {
XCTFail("Status code: \(statusCode)")
}
}
}
dataTask.resume()
// 3
wait(for: [promise], timeout: 5)
}
이 테스트는 유효한 요청을 전송하면 200 상태 코드가 반환되는지 확인합니다. 대부분의 코드는 앱에서 작성한 것과 동일하며 다음과 같은 추가 줄이 있습니다.
expectation(description:)
: 'promise'에 저장된 'XCTestExpectation'을(를) "description"은 예상한 결과를 나타냅니다.promise.fulfill()
: 비동기 메서드의 완료 핸들러의 성공 조건 종료 시 이를 호출하여 예상이 충족되었음을 플래그로 지정합니다. wait(for:timeout:)
: 모든 예상이 충족될 때까지 또는 timeout
간격이 종료될 때까지 먼저 테스트를 실행합니다테스트를 실행합니다. 인터넷에 연결되어 있는 경우, 앱이 시뮬레이터에 로드된 후 테스트가 성공하는 데 약 1초 정도 걸립니다.
실패는 아프지만, 오래 걸릴 필요는 없어요.
실패를 경험하려면testValidApiCallGetsHTTPStatusCode200()
을(를) 잘못된 값으로 지정하기만 하면 됩니다.
let url = URL(string: "http://www.randomnumberapi.com/test")!
테스트를 실행합니다. 실패하지만 전체 시간이 초과되서 걸립니다! 왜냐하면 당신은 요청이 항상 성공할 것이라고 생각했기 때문이고, 거기서 당신은 '약속'을 불렀습니다.액면 그대로 이행합니다. 요청이 실패했으므로 시간 초과가 만료되었을 때만 완료되었습니다.
가정을 변경하여 이를 개선하고 검정을 더 빨리 실패할 수 있습니다. 요청이 성공할 때까지 기다리지 말고 비동기 메서드의 완료 처리기가 호출될 때까지 기다리십시오. 이 문제는 앱이 서버로부터 OK 또는 error 응답을 수신하는 즉시 발생하며, 이는 기대치를 충족시킵니다. 그런 다음 테스트에서 요청이 성공했는지 여부를 확인할 수 있습니다.
이 방법을 확인하려면 새 테스트를 만드십시오.
그러나 먼저 url의 변경 사항을 실행 취소하여 이전 테스트를 수정합니다.
그런 다음 다음 클래스에 다음 테스트를 추가합니다.
func testApiCallCompletes() throws {
// given
let urlString = "http://www.randomnumberapi.com/test"
let url = URL(string: urlString)!
let promise = expectation(description: "Completion handler invoked")
var statusCode: Int?
var responseError: Error?
// when
let dataTask = sut.dataTask(with: url) { _, response, error in
statusCode = (response as? HTTPURLResponse)?.statusCode
responseError = error
promise.fulfill()
}
dataTask.resume()
wait(for: [promise], timeout: 5)
// then
XCTAssertNil(responseError)
XCTAssertEqual(statusCode, 200)
}
주요 차이점은 완료 핸들러를 입력하는 것만으로도 예상을 충족하며,이 작업이 발생하는 데 1 초 밖에 걸리지 않는다는 것입니다. 요청이 실패하면 'then' 선언문은 실패합니다.
테스트를 실행하십시오. 이제 실패하는 데 약 1초가 걸립니다. 테스트 실행이 timeout
를 초과했기 때문에 요청이 실패했기 때문에 실패합니다.
url
을 수정 한 다음 테스트를 다시 실행하여 이제 성공하는지 확인합니다.
어떤 상황에서는 테스트를 실행하는 것이 말이 안 되는 경우도 있습니다. 예를 들어, testValidApiCallGetsHTTPStatusCode200()
를 실행 할 경우 어떻게 해야 합니까? 네트워크 연결 없이 실행됩니까? 물론, 200 상태 코드를 받을 수 없기 때문에 통과해서는 안 됩니다. 하지만 그것은 또한 실패해서는 안 됩니다. 왜냐하면 그것은 아무 것도 테스트하지 않았으니까요.
다행히 애플은 XCTS킵을 도입해 전제조건이 실패할 경우 테스트를 생략했습니다. 'sut' 선언 아래에 다음 줄을 추가합니다.
let networkMonitor = NetworkMonitor.shared
NetworkMonitor
는 NWpathMonitor
를 감싸고 있어 네트워크 연결을 편리하게 확인할 수 있습니다.
testValidApiCallGetsHTTPStatusCode200()
에서 테스트 시작 부분에XCTSkipUnless
를 추가합니다.
try XCTSkipUnless(
networkMonitor.isReachable,
"Network connectivity needed for this test.")
네트워크에 연결할 수 없는 경우 XCTSkipInvertion(_:_:)
이(가) 테스트를 건너뜁니다. 네트워크 연결을 비활성화하고 테스트를 실행하여 이 옵션을 선택합니다. 테스트 옆의 거터에 새 아이콘이 표시되어 테스트에 합격하거나 불합격하지 않았음을 나타냅니다.
네트워크 연결을 다시 활성화하고 테스트를 다시 실행하여 정상 상태에서도 성공하는지 확인합니다. 동일한 코드를 testApiCallCompletes()
의 시작 부분에 추가합니다.
비동기 테스트는 코드가 비동기 API에 대한 올바른 입력을 생성한다는 확신을 줍니다. 또한 코드가 URLSession
에서 입력을 수신할 때 코드가 올바르게 작동하는지 테스트하거나 UserDefaults
데이터베이스 또는 iCloud 컨테이너를 올바르게 업데이트하는지 테스트할 수 있습니다.
대부분의 앱은 사용자가 제어할 수 없는 시스템 또는 라이브러리 개체와 상호 작용합니다. 이러한 개체와 상호 작용하는 테스트는 속도가 느리고 반복할 수 없어 FIRST 원칙 중 두 가지를 위반할 수 있습니다. 대신 stubs에서 입력을 받거나 mock 개체를 업데이트하여 상호 작용을 *가짜로 만들 수 있습니다.
코드가 시스템 또는 라이브러리 개체에 대한 의존성이 있는 경우 Fakery를 사용합니다. 이 작업은 가짜 object를 만들어 해당 부분을 재생하고 이 가짜를 코드에 주입하는 방식으로 수행합니다. [Dependency Injection](Jon Reid의 https://www.objc.io/issues/15-testing/dependency-injection/)에서는 이러한 작업을 수행하는 몇 가지 방법을 설명합니다.
이제 세션에서 다운로드한 데이터를 앱의 get RandomNumber(complete:)
가 올바르게 구문 분석하는지 확인합니다. 여러분은 BullsEyeGame
의 세션을 뭉툭한 데이터로 위조하게 될 것입니다.
Test navigator로 이동하여 +를 클릭하고 New Unit TEst Class...를 선택합니다. BullsEyeFakeTests라는 이름을 지정하고 BullsEyeTests 디렉토리에 저장한 후 대상을 BullsEyeTests로 설정합니다.
import
문 바로 아래에 있는 BullsEye
app module을 가져옵니다.
@testable import BullsEye
이제 BullsEyeFakeTests
의 내용을 다음과 같이 바꾸십시오.
var sut: BullsEyeGame!
override func setUpWithError() throws {
try super.setUpWithError()
sut = BullsEyeGame()
}
override func tearDownWithError() throws {
sut = nil
try super.tearDownWithError()
}
BullsEyeGame
인 SUT를 SetUpWithError()
에서 생성하고 TearDownWithError()
로 해제합니다.
BullsEye
프로젝트에는 URLSessionStub.swift 지원 파일이 포함되어 있습니다. 이것은 URL로 데이터 작업을 생성하는 방법을 사용하여 URLSession Protocol
또한 이 프로토콜을 준수하는 URLSessionStub
를 정의합니다. 초기화를 통해 데이터 작업에서 반환해야 하는 데이터, 응답 및 오류를 정의할 수 있습니다.
가짜를 설정하려면 BullsEyeFakeTests.swift로 이동하여 새 테스트를 추가합니다.
func testStartNewRoundUsesRandomValueFromApiRequest() {
// given
// 1
let stubbedData = "[1]".data(using: .utf8)
let urlString =
"http://www.randomnumberapi.com/api/v1.0/random?min=0&max=100&count=1"
let url = URL(string: urlString)!
let stubbedResponse = HTTPURLResponse(
url: url,
statusCode: 200,
httpVersion: nil,
headerFields: nil)
let urlSessionStub = URLSessionStub(
data: stubbedData,
response: stubbedResponse,
error: nil)
sut.urlSession = urlSessionStub
let promise = expectation(description: "Value Received")
// when
sut.startNewRound {
// then
// 2
XCTAssertEqual(self.sut.targetValue, 1)
promise.fulfill()
}
wait(for: [promise], timeout: 5)
}
이 테스트는 두 가지 작업을 수행합니다.
startNewRound(complete:)
를 호출하면 targetValue와 stubbed fake number를 비교하여 가짜 데이터를 구문 분석하는지 확인합니다.테스트를 실행합니다. 실제 네트워크 연결이 없기 때문에 상당히 빠르게 성공할 수 있습니다!
이전 테스트에서는 가짜 object로부터 입력을 제공하기 위해 stub를 사용했습니다. 그런 다음 mock 객체를 사용하여 코드가 UserDefaults
를 올바르게 업데이트하는지 테스트합니다.
이 앱은 두 가지 게임 스타일이 있습니다. 사용자는 다음 중 하나를 수행할 수 있습니다.
오른쪽 아래 모서리에 있는 세그먼트 컨트롤은 게임 스타일을 전환하여 UserDefaults
에 저장합니다.
다음 테스트는 앱이 gameStyle
속성을 올바르게 저장하는지 확인합니다.
새 테스트 클래스를 대상 BullsEyeTests에 추가하고 이름을 BullsEyeMockTests로 지정합니다. import
문 아래에 다음을 추가합니다.
@testable import BullsEye
class MockUserDefaults: UserDefaults {
var gameStyleChanged = 0
override func set(_ value: Int, forKey defaultName: String) {
if defaultName == "gameStyle" {
gameStyleChanged += 1
}
}
}
MockUserDefaults
는 set(_:forKey:)
을 gameStyleChanged
로 재정의합니다. 유사한 테스트에서 Boole
변수를 설정하는 경우가 많지만, Int
를 늘리면 유연성이 향상됩니다. 예를 들어, 테스트에서는 앱이 메소드를 한 번만 호출하는지 확인할 수 있습니다.
다음으로 BullsEyeMockTests에서 SUT와 모의 개체를 선언합니다.
var sut: ViewController!
var mockUserDefaults: MockUserDefaults!
SetUpWithError()
및 TearDownWithError()
를 다음으로 바꿉니다.
override func setUpWithError() throws {
try super.setUpWithError()
sut = UIStoryboard(name: "Main", bundle: nil)
.instantiateInitialViewController() as? ViewController
mockUserDefaults = MockUserDefaults(suiteName: "testing")
sut.defaults = mockUserDefaults
}
override func tearDownWithError() throws {
sut = nil
mockUserDefaults = nil
try super.tearDownWithError()
}
그러면 SUT와 모의 개체가 생성되고 SUT의 속성으로 모의 개체가 주입됩니다.
이제 템플릿의 두 가지 기본 테스트 방법을 다음으로 바꾸십시오.
func testGameStyleCanBeChanged() {
// given
let segmentedControl = UISegmentedControl()
// when
XCTAssertEqual(
mockUserDefaults.gameStyleChanged,
0,
"gameStyleChanged should be 0 before sendActions")
segmentedControl.addTarget(
sut,
action: #selector(ViewController.chooseGameStyle(_:)),
for: .valueChanged)
segmentedControl.sendActions(for: .valueChanged)
// then
XCTAssertEqual(
mockUserDefaults.gameStyleChanged,
1,
"gameStyle user default wasn't changed")
}
when assertion은 test methon가 세그먼트 컨트롤을 변경하기 전에 gameStyleChanged
플래그가 0이라는 것입니다. 따라서 then assertion도 참이면 set(_:forKey:)
이 정확히 한 번 호출되었다는 의미입니다.
테스트를 실행합니다. 성공해야 합니다.
UI 테스트를 통해 사용자 인터페이스와의 상호 작용을 테스트할 수 있습니다. UI 테스트는 쿼리로 앱의 UI object를 찾고 이벤트를 합성한 다음 해당 object로 이벤트를 보내는 방식으로 작동합니다. API를 사용하면 UI object의 property과 state를 검사하여 예상 상태와 비교할 수 있습니다.
Test navigator에서 새 UI Test Target을 추가합니다. Test Target이 BullsEye인지 확인한 다음 기본 이름 BullsEye를 사용합니다
BullsEye를 엽니다.UITests.swift를 테스트하고 BullsEye
상단에 이 속성을 추가합니다.UITests
클래스는 다음과 같습니다.
var app: XCUIApplication!
TearDownWithError()
를 제거하고 SetUpWithError()
의 내용을 다음과 같이 바꿉니다.
try super.setUpWithError()
continueAfterFailure = false
app = XCUIApplication()
app.launch()
기존 테스트 두 개를 제거하고 testGameStyleSwitch()
라는 새 테스트를 추가합니다.
func testGameStyleSwitch() {
}
testGameStyleSwitch()
에서 새 줄을 열고 편집기 창 하단에 있는 빨간색 Record 버튼을 클릭합니다.
이렇게 하면 상호 작용을 테스트 명령으로 기록하는 모드에서 앱이 시뮬레이터에서 열립니다. 앱이 로드되면 게임 스타일 스위치의 Slide 세그먼트와 top label을 누릅니다. Xcode Record 버튼을 다시 클릭하여 기록을 중지합니다.
이제 testGameStyleSwitch()
에 다음 세 줄이 있습니다.
let app = XCUIApplication()
app.buttons["Slide"].tap()
app.staticTexts["Get as close as you can to: "].tap()
Recoder에서 사용자가 앱에서 테스트한 것과 동일한 작업을 테스트하기 위한 코드를 만들었습니다. 게임 스타일 세그먼트 컨트롤 및 상단 레이블에 탭을 보냅니다. 이를 기반으로 고유한 UI 테스트를 만듭니다. 다른 문장이 보이면 삭제하세요.
첫 번째 행은 SetUpWithError()
에서 생성한 속성을 복제하므로 해당 행을 삭제합니다. 아직 아무 것도 누르지 않아도 되므로 2행과 3행의 끝에 있는 .tap()
도 삭제하십시오. 이제 ["Slide"]
옆에 있는 작은 메뉴를 열고 segmentedControls.buttons["Slide"]
를 선택합니다.
다음 사항을 고려해야 합니다.
app.segmentedControls.buttons["Slide"]
app.staticTexts["Get as close as you can to: "]
Recoder가 테스트에서 액세스할 수 있는 코드를 찾는 데 도움이 되도록 다른 object를 누릅니다. 이제 이러한 라인을 이 코드로 교체하여 주어진 섹션을 생성합니다.
// given
let slideButton = app.segmentedControls.buttons["Slide"]
let typeButton = app.segmentedControls.buttons["Type"]
let slideLabel = app.staticTexts["Get as close as you can to: "]
let typeLabel = app.staticTexts["Guess where the slider is: "]
이제 segmented control에 있는 두 버튼과 가능한 두 상단 레이블에 대한 이름이 지정되었으므로 아래에 다음 코드를 추가하십시오.
// then
if slideButton.isSelected {
XCTAssertTrue(slideLabel.exists)
XCTAssertFalse(typeLabel.exists)
typeButton.tap()
XCTAssertTrue(typeLabel.exists)
XCTAssertFalse(slideLabel.exists)
} else if typeButton.isSelected {
XCTAssertTrue(typeLabel.exists)
XCTAssertFalse(slideLabel.exists)
slideButton.tap()
XCTAssertTrue(slideLabel.exists)
XCTAssertFalse(typeLabel.exists)
}
segmented control의 각 버튼을 tap()
할 때 올바른 라벨이 존재하는지 확인합니다. 테스트를 실행합니다. 모든 주장이 성공해야 합니다.
[Apple's documentation:](https://developer.apple.com/library/prerelease/content/documentation/Developer를 참조하십시오.Tools/Conceptional/testing_with_xcode/chapters/04-writing_tests.html#//apple_ref/doc/uid/TP40014132-CH4-SW8):
성능검사는 평가하고자 하는 코드 블록을 취하여 10회 실행하며, 평균 실행 시간과 실행 표준 편차를 수집합니다. 이러한 개별 측정의 평균은 테스트 실행에 대한 값을 형성하며, 이 값을 기준선과 비교하여 성공 또는 실패를 평가할 수 있습니다.
성능 테스트 작성은 간단합니다. 측정하려는 코드를 measure()
의 closure에 넣기만 하면 됩니다. 또한 측정할 metrics을 여러 개 지정할 수 있습니다.
다음 테스트를 BullsEyeTests
에 추가합니다.
func testScoreIsComputedPerformance() {
measure(
metrics: [
XCTClockMetric(),
XCTCPUMetric(),
XCTStorageMetric(),
XCTMemoryMetric()
]
) {
sut.check(guess: 100)
}
}
이 테스트는 여러 메트릭을 측정합니다.
XCTClockMetric
은 경과 시간을 측정합니다.XCTCPUMetric
은 CPU 시간, 주기, 명령 수 등 CPU의 활동을 추적합니다.XCTStorageMetric
은 테스트된 코드가 스토리지에 얼마나 많은 데이터를 쓰는지 알려줍니다.XCTMemoryMetric
은 사용된 물리적 메모리의 양을 추적합니다.테스트를 실행한 다음 measure()
trailing closure 시작 옆에 나타나는 아이콘을 클릭하여 통계를 확인합니다. Metric 옆에서 선택한 Metric을 변경할 수 있습니다.
Set Baseline을 클릭하여 참조 시간을 설정합니다. 성능 테스트를 다시 실행하여 결과를 확인하십시오. 기준보다 더 좋거나 더 나쁠 수 있습니다. Edit 버튼을 사용하여 기준을 이 새 결과로 재설정할 수 있습니다.
Baselines은 디바이스 구성별로 저장되므로 여러 디바이스에서 동일한 테스트를 실행할 수 있습니다. 각 구성에서는 특정 구성의 프로세서 속도, 메모리 등에 따라 서로 다른 baseline을 유지할 수 있습니다.
테스트 중인 method의 성능에 영향을 미칠 수 있는 앱을 변경할 때마다 성능 테스트를 다시 실행하여 해당 방법이 baseline과 어떻게 비교되는지 확인합니다.
code coverage tool는 테스트가 실제로 실행 중인 앱 코드를 알려주므로, 아직 테스트되지 않은 앱 부분이 무엇인지 알 수 있습니다.
code coverage를 활성화하려면 스키마의 테스트 작업을 편집하고 옵션 탭 아래의 적용 범위 수집 확인란을 선택합니다.
Command-U를 사용하여 모든 테스트를 실행한 후 Command-9를 사용하여 report navigator를 엽니다. 목록의 맨 위 항목 아래에서 커버리지를 선택합니다.
삼각형을 클릭하여 BullsEyeGame.swift의 기능 및 폐쇄 목록을 확인합니다.
getRandomNumber(completion:)
로 스크롤하여 적용 범위가 95.0%인지 확인합니다.
이 함수의 화살표 단추를 클릭하여 함수의 소스 파일을 엽니다. 오른쪽 사이드바의 커버리지 주석을 마우스로 가리키면 코드의 섹션이 녹색 또는 빨간색으로 강조 표시됩니다.
적용 범위 주석은 테스트가 각 코드 섹션에 도달하는 횟수를 나타냅니다. 호출되지 않은 섹션은 빨간색으로 강조 표시됩니다.
100% 코드 적용을 위해 얼마나 노력해야 합니까? Google "100% unit test coverage"만 보면, "100% coverage"의 정의에 대한 토론과 함께 이에 대한 찬반 주장을 찾을 수 있습니다. 이에 반대하는 주장들은 마지막 10%–15%가 노력할 가치가 없다고 말합니다. 이에 대한 주장은 마지막 10%~15%가 가장 중요하다고 말합니다. 왜냐하면 테스트하기가 너무 어렵기 때문입니다. Google은 검증 불가능한 코드는 더 깊은 설계 문제의 표시라는 설득력 있는 주장을 찾기 위해 "잘못된 디자인을 결합하기 어렵다"고 말합니다.
본 튜토리얼의 상단 또는 하단에 있는 Download Materials 버튼을 사용하여 프로젝트의 완료된 버전을 다운로드할 수 있습니다. 자신의 테스트를 추가하여 스킬을 계속 개발하십시오.
이제 프로젝트에 대한 쓰기 테스트에서 사용할 수 있는 몇 가지 유용한 도구가 있습니다. 이 iOS 유닛 테스트 및 UI 테스트 튜토리얼을 통해 모든 것을 테스트할 수 있는 자신감을 얻으셨기를 바랍니다!
다음은 추가 연구를 위한 몇 가지 리소스입니다.