WWDC18 - Testing Tips and Tricks - 2(Working with notifications)
2023. 4. 12. 00:48ㆍIOS/WWDC
반응형
Working with notifications
Notification
을 보내거나 옵저버가 제대로 작동하는지 여부를 테스트 하기 위해서는 테스트 할Notification
은 격리되어 있어야 온전한 테스트라고 할 수 있다.Notification
은 일대다 통신이므로 특정 부분만을 테스트를 시도하다 다른Notification
의post
를 받은 옵저버로 인해 의도하지 않은 부작용이 생길 수 있다. 그러므로, 격리 혹은 고립시켜서 테스트를 할 필요가 있다.
class PointsOfInterestTableViewController {
var observer: AnyObject?
var didHandleNotification = false
init() {
let name = CurrentLocationProvider.authChangedNotification
observer = NotificationCenter.default.addObserver(forName: name, object: nil, queue: .main, using: { [weak self] _ in
self?.handleAuthChanged()
})
}
func handleAuthChanged() {
didHandleNotification = true
}
}
PointsOfInterestTableViewController
는CurrentLocationProvider
의Name
을Notification
을 받아낸다.- 위 뷰 컨트롤러가 제대로 옵저빙 하는지 파악하기 위해서 간단하게 테스트코드를 작성하면 아래와 같이 작성할 수 있을것이다.
class PointsOfInterestTableViewControllerTests: XCTestCase {
func testNotification() {
let observer = PointsOfInterestTableViewController()
XCTAssertFalse(observer.didHandleNotification)
let name = CurrentLocationProvider.authChangedNotification
NotificationCenter.default.post(name: name, object: nil)
XCTAssertTrue(observer.didHandleNotification)
}
}
- 이는 뷰컨트롤러에서와 같은
NotificationCenter.defulat
를 통하여 알림을 보내고 있다. - 이는 테스트코드로는 동작을 하지만 맨 처음에서 말한 고립된 환경은 아니다.
- 만약
NotificationCenter.default
에서 해당name
으로 여러개의 옵저버를 만들었다면 모든 곳에post
될 것이고 이는 테스트 속도를 저하시킨다. - 그러므로 우리는 테스트 코드뿐만 아니라 뷰 컨트롤러 역시 조금 더 세분화시켜야한다.
Testing Notification Observers
- 우선
NotificationCenter
가 여러 인스턴스를 가질 수 있음을 알고있어야 한다.default
라는 기본 인스턴스가 제공되지만 필요할 경우 추가적으로 새로운 인스턴스를 만들 수 있으므로 테스트 환경을 구축하는데 큰 도움을 줄 수 있다. - 새롭게 만든 인스턴스를 통하여
default
대신 사용하여야한다. 이는 흔히 종속성 주입(dependency injection)이라고 불린다.
class PointsOfInterestTableViewController {
var observer: AnyObject?
var didHandleNotification = false
var notificationCenter: NotificationCenter
init(notificationCenter: NotificationCenter = .default) {
self.notificationCenter = notificationCenter
let name = CurrentLocationProvider.authChangedNotification
observer = NotificationCenter.default.addObserver(forName: name, object: nil, queue: .main, using: { [weak self] _ in
self?.handleAuthChanged()
})
}
func handleAuthChanged() {
didHandleNotification = true
}
}
NotificationCenter
의 새로운 인스턴스를 만들고init
에 해당 값을 받도록 구현할 수 있다.- 만약
init(notificationCenter: NotificationCenter)
로 구현을 한다면 앱이 동작하거나 앱을 처음 동작시킬 때 따로NotificationCenter
를 만들어 뷰컨트롤러 인스턴스를 만들어야겠지만=.default
를 주면서 기존 앱에서 큰 수정없이 테스트만을 위한init
을 만들 수 있다.
class PointsOfInterestTableViewControllerTests: XCTestCase {
func testNotification() {
let notificationCenter = NotificationCenter()
let observer = PointsOfInterestTableViewController(notificationCenter: notificationCenter)
XCTAssertFalse(observer.didHandleNotification)
let name = CurrentLocationProvider.authChangedNotification
notificationCenter.post(name: name, object: nil)
XCTAssertTrue(observer.didHandleNotification)
}
}
- 위의 테스트코드를 약간 수정하자면 위와 같이 수정을 할 수 있다.
- 이럴경우 기존의
default
를 통해 받는 옵저버와 상관없이 고립된 환경에서의 옵저버가 만들어지며,post
메소드가 제대로 동작하는지 여부를 쉽게 확인할 수 있다.
Testing Notification Posters
- 위에서는 옵저버가 제대로 동작하는지 여부를 확인했다면, 이번에는
NotificationCenter
가 제대로post
되는지 여부를 확인해봐야한다. - 이는 위에서
NotificationCenter
를 종속성 주입을 통해 사용한것과 마찬가지의 방법을 사용할 것이고 - XCTest가 제공하는
XCTNSNotificationExpectation
를 사용하여 테스트 해 볼것이다.
class CurrentLocationProvider {
static let authChangedNotification = Notification.Name("AuthChanged")
func notifyAuthChanged() {
let name = CurrentLocationProvider.authChangedNotification
NotificationCenter.default.post(name: name, object: self)
}
}
- 이는 기존에 종속성 주입을 하기전의 뷰 컨트롤러와 비슷하다.
class CurrentLocationProviderTests: XCTestCase {
func testNotifyAuthChanged() {
let poster = CurrentLocationProvider()
var observer: AnyObject?
let expectation = self.expectation(description: "auth changed notification")
let name = CurrentLocationProvider.authChangedNotification
observer = Notification.default.addObserver(forName: name, object: poster, queue: .main, using: { _ in
NotificationCenter.default.removeObserver(observer!)
expectation.fulfill()
})
poster.notifyAuthChanged()
wait(for: [expectation], timeout: 0)
}
}
- 위 코드를
XCTest
가 제공하는XCTNSNotificationExpectation
메소드를 통해 우리는 조금 더 간편하게 만들 수 있다.
class CurrentLocationProviderTests: XCTestCase {
func testNotifyAuthChanged() {
let poster = CurrentLocationProvider()
let name = CurrentLocationProvider.authChangedNotification
let expectation = XCTNSNotificationExpectation(name: name, object: object)
//....
}
}
- 하지만, 위에서 말한것과 같이
NotificationCenter.default
를 쓰는것은 다른 옵저버에도 해당post
를 보내는 문제가 있기 때문에NotificationCenter
환경을 고립시킬 필요가 있다.
class CurrentLocationProvider {
static let authChangedNotification = Notification.Name("AuthChanged")
let notificationCenter: NotificationCenter
init(notificationCenter: NotificationCenter = .default) {
self.notificationCenter = notificationCenter
}
func notifyAuthChanged() {
let name = CurrentLocationProvider.authChangedNotification
notificationCenter.post(name: name, object: self)
}
}
- 뷰컨트롤러에서 해준 종속성 주입을
Provider
에도 해주므로 환경을 고립시켜줄 수 있는 여건을 만들었다.
func testNotifyAuthChanged() {
let notificationCenter = NotificationCenter()
let poster = CurrentLocationProvider(notificationCenter: notificationCenter)
let name = CurrentLocationProvider.authChangedNotification
let expectation = XCTNSNotificationExpectation(name: name, object: poster, notificationCenter: notificationCenter)
poster.notifyAuthChanged()
wait(for: [expectation], timeout: 0)
}
최종적으로 테스트코드를 다음과 같이 작성할 수 있다.
예상값으로
XCTNSNotificationExpectation
을 만드므로 해당notificationCenter
가name
,object
,notificationCenter
까지 정확히post
되는지 여부를 확인할 수 있다.또한,
wait(for: [expectation], timeout: 0)
을 보면timeout
이 0초인것을 학인할 수 있는데 이는.notifyAuthChanged
의 메소드가 동작하는 순간 곧바로expectation
이 실행되므로 0초가 가능한 것이다.~19:49
반응형
'IOS > WWDC' 카테고리의 다른 글
WWDC22 - Design protocol interfaces in Swift - 1편 (0) | 2023.07.25 |
---|---|
WWDC21 - Your guide to keyboard layout (0) | 2023.04.29 |
WWDC19 - Advances in Collection View Layout (0) | 2023.04.21 |
WWDC18 - Testing Tips and Tricks - 3 (0) | 2023.04.18 |
WWDC 18 - Testing Tips & Tricks - 1 (0) | 2023.03.24 |