본문 바로가기

도서/SwiftUI by Tutorials

[SwiftUI by Tutorials] Chapter 04: Testing & Debugging

반응형

Different types of tests

 

테스트는 세 가지 유형이 있다.

복잡한 순서대로 단위 테스트, 통합 테스트, UI 테스트이다.

 

단위 테스트

  • 모든 테스트의 기본이자 다른 모든 테스트의 기초 단위
  • 함수가 주어진 입력을 처리할 때 예상되는 출력을 얻는 것
  • 여러 단위 테스트는 동일한 코드 조각을 테스트할 수 있지만 각 단위 테스트 자체는 단일 코드 단위에만 집중
  • 자주 실행되므로, 빠르게 실행되길 원함

 

통합 테스트

  • 코드의 여러 부분이 서로 얼마나 잘 작동하는지 확인
  • 앱이 외부 API와 같이 앱 외부 세계와 얼마나 잘 작동하는지 확인
  • 단위 테스트보다 복잡함
  • 일반적으로 실행하는 시간이 오래 걸리기 때문에 실행 빈도가 적음

 

UI 테스트

  • 가장 복잡한 테스트
  • 사용자 동작을 확인
  • 앱과 사용자간의 상호 작용을 시뮬레이션하고 상호 작용에 응답한 후 사용자 인터페이스가 예상대로 작동하는지 확인

 

테스트 계층을 위로 이동함에 따라 각 테스트 수준은 더 넓은 범위의 작업을 확인한다.

예를 들어 단위 테스트는 앱의 computeTotal() 메서드가 주문에 대해 정확한 금액을 반환하는지 확인한다.

통합 테스트는 주문 항목의 재고가 있는지 올바르게 판단하는지 확인한다.

UI 테스트는 주문에 항목을 추가한 후, 사용자에게 표시되는 금액이 올바른 값을 표시하는지 확인한다.

 

 

Debugging SwiftUI apps

 

Live Preview, Debug Preview에 대한 설명이 있는데, Xcode13부터 Debug Preview가 제거된 것으로 보인다.

breakingpoints를 찍고 확인하고 싶다면 실 디바이스 혹은 시뮬레이터를 통해 확인하자.

 

https://developer.apple.com/forums/thread/683773

 

Debug Previews | Apple Developer Forums

I used to be able to "right click" on the play button for a preview, but in Beta 1 and 2 of Xcode 13 that isn't working (neither is control clicking). Any ideas how to debug a preview short of running app in the simulator?

developer.apple.com

 

Adding UI tests

 

Xcode 템플릿에서 제공되는 네 가지 기본 메서드를 볼 수 있다.

 

테스트 프로세스는 각 테스트 메서드를 실행하기 전에

setUpWithError()를 호출한 다음 각 테스트 메서드가 완료된 후

tearDownWithError()를 호출한다.

 

setUpWithError() 안의 코드를 주목할 필요가 있다.

continueAfterFailure = false

위 코드는 오류가 발생하면 테스트를 중지하도록 설정하는 것이다.

이 값을 false로 설정하면 첫 번째 실패 후 테스트 프로세스가 중지된다.

UI Test의 특성을 감안하면 테스트가 실패했을 경우 거의 항상 알 수 없는 상태가 된다.

의미 없는 긴 테스트를 지속하지 않고, 문제를 바로 수정해야 한다.

 

테스트 이름은 test로 시작해야 한다.

그렇지 않을 경우 테스트 프레임워크는 메서드를 무시하고 테스트할 때 실행하지 않는다.

(myCoolTest(): 테스트 실행 X, testMyCoolCode(): 테스트 실행 O)

 

 

Creating a UI Test

 

테스트 이름은 테스트가 검증하는 내용에 대해 정확하고 명확해야 한다.

명확한 이름을 사용하면 실패한 항목을 쉽게 이해할 수 있다.

테스트 이름은 테스트 대상, 테스트 환경 및 결과가 어떠해야 하는지를 명시해야 한다.

 

testExample testPressMemoryPlusAtAppStartShowZeroInDisplay

 

길게 느낄 수 있다.

하지만 테스트 이름은 간결함을 위한 것이 아니다.

이름은 한눈에 세 가지 요소를 모두 명확하게 제공해야 한다.

 

 

Accessing UI elements

 

func testPressMemoryPlusAtAppStartShowZeroInDisplay() {
	// UI tests must launch the application that they test.
	let app = XCUIApplication()
	app.launch()
    
	let memoryButton = app.buttons["M+"]
	memoryButton.tap()    
}

 

XCUIApplication에는 각 유형의 사용자 인터페이스 객체들이 있다.

app.buttons 쿼리는 먼저 앱의 .buttons에 대해서만 필터링한다.

그런 다음 Label이 M+인 것으로 필터링을 한다.

 

SwiftUI 앱은 새로운 컴포넌트들이 아니라 플랫폼의 기본 요소들로 렌더링 된다.

SwiftUI는 인터페이스를 정의하는 새로운 방법을 제공하지만 여전히 플랫폼의 기존 요소들을 사용한다.

 

SwiftUI 버튼은 iOS에서는 UIButton, MacOs에서는 NSButton이다.

버튼 객체의 tap()을 호출하여 버튼을 탭 하는 시뮬레이션을 할 수 있다.

 

테스트를 실행하면 시뮬레이터에서 앱이 시작되고 실행되는 것을 볼 수 있다.

위 상황에서는 보기에는 실패해야 하는 것으로 보여도, 녹색 체크로 통과된 테스트로 나온다.

위 경우는 아무것도 확인하지 않은 테스트로 실패하지 않는 테스트를 통과한 테스트로 취급한다.

M+ 버튼을 눌러 상호작용을 수행했으므로 이제 결과를 확인해야 한다.

 

 

Reading the user interface

 

.accessibility(identifier: "display")

버튼 텍스트에 따라 찾는 것은, 앱 내에서 변경할 경우 작동하지 않는다.

이 메서드는 UI 요소의 accessibilityIdentifer를 설정한다.

테스트를 간단하게 하기 위해 UI 요소에 label을 제공하는 방법이다.

 

// 1
let display = app.staticTexts["display"]
// 2
let displayText = display.label
// 3
XCTAssert(displayText == "0")

 

1. 추가한 accessibility(identifier:)를 사용하여 앱에서 디스플레이된 요소를 찾는다.

2. label 텍스트가 포함된 요소를 확인을 위한 것이다.

3. assertion을 사용하여 label이 예상 결과와 일치하는지 확인한다.

 

실패한 테스트를 작성하고 버그를 수정한 다음 테스트가 통과했는지 확인하는 패턴은 유용한 패턴이다.

테스트 없이 기존 앱을 가져오고, 버그를 수정할 때마다 테스트를 추가하면 미래를 위한 유용한 테스트 세트를 빠르게 구축할 수 있다.

 

Adding more complex tests

func testAddingTwoDigits() {
	let app = XCUIApplication()
	app.launch()
	let threeButton = app.buttons["3"]
	threeButton.tap()
	let addButton = app.buttons["+"]
	addButton.tap()
	let fiveButton = app.buttons["5"]
	fiveButton.tap()
	let equalButton = app.buttons["="]
	equalButton.tap()
	let display = app.staticTexts["display"]
	let displayText = display.label
	XCTAssert(displayText == "8")
}

3+5=8 당연히 테스트는 패스할 것을 보인다. 하지만 이 예제에서는 실패한다.

displayText를 console에서 검색하면 8.0으로 나오기 때문이다.

이와 같이 UI 테스트는 사용자 인터페이스에 중점을 둔다. 반대로 단위 테스트의 경우 3+5=8을 올바르게 계산했는지 확인한다.

UI 테스트는 이 계산을 수행할 때 사용자가 보는 것을 확인한다.

XCTAssertEqual(displayText, "8.0")

XCTAssert()는 조건이 참이 아니면 실패한다.

보다 구체적인 오류 메시지를 확인하려면 위의 XCTAssertEqual을 사용하는 방법도 있다.

 

Simulating user interaction

func testSwipeToClearMemory() {
	let app = XCUIApplication()
	app.launch()
	let threeButton = app.buttons["3"]
	threeButton.tap()
	let fiveButton = app.buttons["5"]
	fiveButton.tap()
	let memoryButton = app.buttons["M+"]
	memoryButton.tap()
	let memoryDisplay = app.staticTexts["memoryDisplay"]
	// 1
	XCTAssert(memoryDisplay.exists)
	// 2
	memoryDisplay.swipeLeft()
	// 3
	XCTAssertFalse(memoryDisplay.exists)
}

1. XCUIElement의 exist 속성은 요소가 존재할 때 true이다.

2. 왼쪽 swipe 동작을 생성한다. (swipeRight(), swipeUp(), swipeDown() 메서드도 있다.)

3. XCTAssertFalse는 XCTAssert와 반대 역할로 false일 경우 성공한다.

 

이외에도 많은 테스트 요소가 있다.

  • .isHittable
    요소가 존재하고, 사용자가 현재 위치에서 클릭, 탭 혹은 누를 수 있을 경우 hittable 하다.
    스크린을 벗어난 요소는 존재할 수 있으나 hittable하지 않다.
  • .typeText()
    사용자가 입력하는 것처럼 작동한다.
  • .press(forDuration:)
    지정된 시간 동안 한 손가락 터치를 수행할 수 있다.
  • .press(forDuration:thenDragTo:)
    스와이프 메서드는 제스처의 속도를 보장하지 않는다. 이 방법을 사용하여 더 정확한 드래그 동작을 수행할 수 있다.
  • .waitForExistence()
    요소가 화면에 즉시 나타나지 않아 일시 중지가 필요한 경우 유용하다.

https://developer.apple.com/documentation/xctest/xcuielement

 

Apple Developer Documentation

 

developer.apple.com

 

Testing multiple platforms

 

SwiftUI의 특징 중 하나는 Apple Multiple Platform이 있다. iOS 전용으로 만든 앱은 아주 적은 작업으로 macOS 앱이 될 수 있다.

그러나 앱과 테스트가 모든 플랫폼에서 제대로 워킹하도록 하기 위해서는 주의해야 할 사항이 있다.

 

target device를 My Mac으로 변경하고 테스트를 빌드하고 실행할 경우 컴파일 오류가 발생한다.

"XCUIElement 유형에 swipeLeft 멤버가 없다"

모든 작업이 모든 운영 체제에서 1:1로 상응하는 것은 아니다.

 

솔루션으로 Xcode 조건부 컴파일 블록을 사용하는 방법이 있다.

#if !targetEnvironment(macCatalyst)
	// Test to exclude
#endif

 #if !os(watchOS)
	// Your XCTest code
#endif

 

 

 

 

 

 

 

반응형