testing view controllers with quick and nimble

26
Testing view controllers with Quick and Nimble Marcio Klepacz iOS Engineering @ GetYourGuide - Swift Berlin 2015

Upload: marcio-klepacz

Post on 12-Apr-2017

656 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Testing view controllers with Quick and Nimble

Testing view controllers with Quick and Nimble

Marcio Klepacz iOS Engineering @ GetYourGuide - Swift Berlin 2015

Page 2: Testing view controllers with Quick and Nimble

Overview

• The Problem

• Tools

• Example

• Conclusion

Page 3: Testing view controllers with Quick and Nimble

The Problem

Page 4: Testing view controllers with Quick and Nimble

View Controllers

• The place where the user interface connects with the app logic and model

Controller

Model View

Page 5: Testing view controllers with Quick and Nimble

• One of the pillars of the architecture.

• Sensible code.

• Involuntary change can damage.

Page 6: Testing view controllers with Quick and Nimble

Testing View Controllers

• Not easy

• lifecycle managed by the framework.

• lot’s of states.

😰

Page 7: Testing view controllers with Quick and Nimble

Tools

Page 8: Testing view controllers with Quick and Nimble

Behaviour Driven Development (BDD)

BDD BDD+View Controllers

Tests that verify what an application does are

behavioural tests.

What a view controllers does rather than how.

Check the behaviour not pieces of code.

Robust testing.

More readable. More readable tests.

Page 9: Testing view controllers with Quick and Nimble

Quick and Nimble

• Quick is a Behaviour-driven development framework for Swift and Objective-C.

• Inspired by RSpec,Specta, and Ginkgo.

• Nimble is a Matcher Framework also for both languages.

• Provide more clear expectations.

Page 10: Testing view controllers with Quick and Nimble

Example

Page 11: Testing view controllers with Quick and Nimble

iOS App: Pony

• PonyTabController: UITabBarController.

• Responsible for presenting the app intro

Page 12: Testing view controllers with Quick and Nimble

PonyTabController

public class PonyTabController: UITabBarController { override public func viewDidAppear(animated: Bool) { //… if !appIntroHasBeenPresented {

presentViewController(appIntroViewController,…) { appIntroViewController.dismissButtonTapHandler = {

appIntroHasBeenPresented = true self.dismissViewControllerAnimated(true,…) }

• Check if app intro has been presented ⚠ • Present app intro. ⚠ • Dismiss if handler is called. ⚠

Page 13: Testing view controllers with Quick and Nimble

Testing: Present app intro ⚠import Quick import Nimble

class PonyTabBarControllerSpec: QuickSpec { override func spec() { describe(“.viewDidAppear"){ context("when app intro had never been dismissed"){ it("should be presented”){

expect(tabBarController.presentedViewController) .toEventually(beAnInstanceOf(AppIntroViewController))

} } } //…

• Pre-conditions are still missing. ⚠

• The object under test is not being invoked. ⚠

Page 14: Testing view controllers with Quick and Nimble

Testing: Present app intro ⚠import Pony //… var tabBarController: PonyTabController! describe(".viewDidAppear"){

context("when app intro had never been dismissed"){

beforeEach { // 1 Arrange: tabBarController = storyboard.instantiateInitialViewController() as! PonyTabController

// 2 Act: let _ = tabBarController.view

} it("should be presented”){

// 3 Assert: expect(tabBarController.presentedViewController).toEventually(beAnInstanceOf(AppIntro…))

} }

} } 1. Pre-conditions. ✅

2. The object under test is being invoked. ✅

3. Asserting. ⚠

Page 15: Testing view controllers with Quick and Nimble

"Arrange-Act-Assert"

• Pattern for arranging and formatting code in Tests methods.

• Benefit:

• Clearly separates what is being tested from the setup and verification steps.

Page 16: Testing view controllers with Quick and Nimble

Testing: Present app intro ⚠import Pony //… var tabBarController: PonyTabController! describe(".viewDidAppear"){

context("when app intro had never been dismissed"){

beforeEach { // 1 Arrange: tabBarController = storyboard.instantiateInitialViewController() as! PonyTabController

// 2 Act: let _ = tabBarController.view

} it("should be presented”){

// 3 Assert: expect(tabBarController.presentedViewController).toEventually(beAnInstanceOf(AppIntro…))

} }

} } 1. Pre-conditions. ✅

2. The object under test is being invoked. ✅

3. Asserting. ⚠

Page 17: Testing view controllers with Quick and Nimble

Testing: Present app intro ✅

PonyTabController: UITabBarController { override public func viewDidAppear(animated: Bool) { //… presentViewController(appIntroViewController,…) {

} //…

Warning: Attempt to present <AppIntroViewController: 0x1e56e0a0> on <PonyTabController: 0x1ec3e000>

whose view is not in the window hierarchy!

Page 18: Testing view controllers with Quick and Nimble

Testing: Present app intro ✅import Pony //… var tabBarController: PonyTabController! describe(".viewDidAppear"){

context("when app intro had never been dismissed"){

beforeEach { // 1 Arrange: tabBarController = storyboard.instantiateInitialViewController() as! PonyTabController

// 2 Act: UIApplication.sharedApplication().keyWindow?.rootViewController = tabBarController

it("should be presented”){

// 3 Assert: expect(tabBarController.presentedViewController).toEventually(beAnInstanceOf(AppIntro…))

} }

} } 1. Pre-conditions. ✅

2. The object under test is being invoked. ✅

3. Asserting. ✅

Page 19: Testing view controllers with Quick and Nimble

Testing: Dismiss app intro ⚠//… context("when app intro had never been dismissed”){ //… context("and dismiss button was tapped") { beforeEach { // Arrange: appIntroHasBeenPresented = false

// Act: tabBarController.beginAppearanceTransition(true, animated: false) tabBarController.endAppearanceTransition()

var appIntroViewController = tabBarController.presentedViewController as! AppIntroViewController appIntroViewController.dismissButton! .sendActionsForControlEvents(UIControlEvents.TouchUpInside)

} it("should dismiss app intro"){ // Assert: expect(tabBarController.presentedViewController).toEventually(beNil())

} //…

}• Another context.

• beginAppearanceTransition: will trigger viewWillAppear.

• endAppearanceTransition: will trigger viewDidAppear.

• sendActionsForControlEvents: simulate tap on the dismiss button

Page 20: Testing view controllers with Quick and Nimble

Testing: Dismiss app intro ✅//… context("when app intro had never been dismissed”){

//… context("and dismiss button was tapped") {

beforeEach { // Arrange: appIntroHasBeenPresented = false

// Act: tabBarController.beginAppearanceTransition(true, animated: false) tabBarController.endAppearanceTransition()

var appIntroViewController = tabBarController.presentedViewController as! AppIntroViewController appIntroViewController.dismissButton!

.sendActionsForControlEvents(UIControlEvents.TouchUpInside) }

it("should set appIntroHasBeenPresented to true""){

// Assert: expect(appIntroHasBeenPresented).to(beTrue())

}

it("should dismiss app intro"){ // Assert: expect(tabBarController.presentedViewController).toEventually(beNil())

} //…

}

• Set app intro presented to be true. ✅

• Will dismiss if the button tap handler is called. ✅

Page 21: Testing view controllers with Quick and Nimble

Extra//…

waitUntil { done in tabBarController.dismissViewControllerAnimated(false) { done()

} }

}

//…

waitUntil { done in NSThread.sleepForTimeInterval(0.5) done()

} //…

• waitUntil is a function provided by Nimble where you can execute something inside it closure and call done() when is ready.

• Useful when waiting for a callback.

Page 22: Testing view controllers with Quick and Nimble

Tested ✅

• Verifying the app intro will be presented if it had never been dismissed. ✅

• Set app intro presented to be true. ✅

• Will dismiss if the button tap handler is called. ✅

//… it("should be presented”){ expect(tabBarController.presentedViewController).toEventually(beAnInstanceOf(AppIntro…))

} //… it("should dismiss app intro"){ expect(tabBarController.presentedViewController).toEventually(beNil())

}

it("should set appIntroHasBeenPresented to true"){ expect(appIntroHasBeenPresented).to(beTrue()) } //…

Page 23: Testing view controllers with Quick and Nimble

Conclusion

Page 24: Testing view controllers with Quick and Nimble

Conclusion• BDD + Quick and Nimble can help you get more

meaningful tests for view controllers.

• There is a lifecycle that must be followed, i.e: you can’t present a V.C. if there another already being presented or the view is not part of the hierarchy.

• UIKit provide public methods that can help.

• Don’t create massive view controllers.

Page 25: Testing view controllers with Quick and Nimble

Questions ?

Page 26: Testing view controllers with Quick and Nimble

References

• https://github.com/Quick/Quick • http://realm.io/news/testing-in-swift/ • http://www.slideshare.net/bgesiak/everything-you-never-

wanted • http://c2.com/cgi/wiki?ArrangeActAssert