Slide 1

Slide 1 text

Testing in Swift Jeff Hui

Slide 2

Slide 2 text

Software Quality

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Automated programs that describe intent and verify behavior repeatably.

Slide 5

Slide 5 text

Automated programs that describe intent and verify behavior repeatably.

Slide 6

Slide 6 text

Automated programs that describe intent and verify behavior repeatably.

Slide 7

Slide 7 text

Automated programs that describe intent and verify behavior repeatably.

Slide 8

Slide 8 text

Automated programs that describe intent and verify behavior repeatably.

Slide 9

Slide 9 text

XCTest

Slide 10

Slide 10 text

import XCTest class TestSort: XCTestCase { var values: [Int] = [] override func setUp() { values = [2, 5, 3] } func testReorderingOfSmallerIntegersFirst() { sort(&values) XCTAssertEqual(values, [2, 3, 5], "Expected \(values) to equal [2, 3, 5]“) } }

Slide 11

Slide 11 text

Quick BDD Testing Framework

Slide 12

Slide 12 text

import Quick import Nimble class SortSpec: QuickSpec { override func spec() { describe("sorting integers") { var values: [Int] = [] beforeEach { values = [2, 5, 3] } it("reorders smaller integers first in the array") { sort(&values) expect(values).to(equal([2, 3, 5])) } } } }

Slide 13

Slide 13 text

describe("viewing the list of numbers") { it("makes a request for random numbers”) {} it("shows a spinner") {} context("when the service responds with the numbers") { it("hides the spinner") {} it("should display the first number to the user”) {} describe("tapping on the ‘roll’ button”) { it("should add the next random number to the top of the list”) {} } describe("tapping on a randomly generated number") { it("should push the a detail controller on the nav stack”) {} } } }

Slide 14

Slide 14 text

Testing an App

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Fancy Architecture ListViewController RandomClient DetailViewController

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

?

Slide 19

Slide 19 text

App Test Bundle

Slide 20

Slide 20 text

App Test Bundle Tests Production

Slide 21

Slide 21 text

App Test Bundle Tests Production Foo

Slide 22

Slide 22 text

App Test Bundle Tests Production Foo

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

XCTest UIApplicationDidFinishLaunchingNotification

Slide 25

Slide 25 text

@UIApplicationMain class DispatchDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow?, appDelegate: AppDelegate! func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { window = UIWindow(frame: UIScreen.mainScreen().bounds) var result = true if isTesting() { window?.rootViewController = UIViewController() } else { appDelegate = AppDelegate(window: window!) result = appDelegate.application(application, didFinishLaunchingWithOptions: launchOptions) } window?.makeKeyAndVisible() return result } private func isTesting() -> Bool { return NSClassFromString("XCTest") != nil } }

Slide 26

Slide 26 text

Testing Networking

Slide 27

Slide 27 text

Testing Networking Nocilla OHHTTPStubs Method Swizzling

Slide 28

Slide 28 text

Adapters

Slide 29

Slide 29 text

public protocol HTTPClient { func sendRequest( request: NSURLRequest, complete: (NSURLResponse?, NSData?, NSError?) -> Void) }

Slide 30

Slide 30 text

public class URLConnectionHTTPClient: HTTPClient { let queue: NSOperationQueue public init() { queue = NSOperationQueue.mainQueue() } public func sendRequest(request: NSURLRequest, complete: (NSURLResponse?, NSData?, NSError?) -> Void) { NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler: complete) } }

Slide 31

Slide 31 text

struct RecordedRequest { let request: NSURLRequest let callback: (NSURLResponse?, NSData?, NSError?) -> Void } class FakeHTTPClient: HTTPClient { var recordedRequests: [RecordedRequest] init() { recordedRequests = [] } func sendRequest(request: NSURLRequest, complete: (NSURLResponse?, NSData?, NSError?) -> Void) { recordedRequests.append(RecordedRequest( request: request, callback: complete)) } }

Slide 32

Slide 32 text

describe("RandomClient") { var client: RandomClient!, fakeHTTPClient: FakeHTTPClient! beforeEach { fakeHTTPClient = FakeHTTPClient() client = RandomClient(httpClient: fakeHTTPClient, apiKey: "my-api-key") } describe("requesting a random number between 0 and 100") { var number: Int?, error: NSError? beforeEach { client.randomInteger { n, err in number = n error = err } } it("should make an HTTP request") { var lastRequest = fakeHTTPClient.recordedRequests.last! expect(lastRequest.request.URL?.absoluteString).to( equal("https://api.random.org/json-rpc/1/invoke")) } // ... } }

Slide 33

Slide 33 text

Adapters

Slide 34

Slide 34 text

Adapters Networking Logging Analytics Persistence

Slide 35

Slide 35 text

UIKit

Slide 36

Slide 36 text

viewController.beginAppearanceTransition( true, animated: false) viewController.endAppearanceTransition()

Slide 37

Slide 37 text

// Swift func tap(barButtonItem: UIBarButtonItem) { SelectorProxy(target: barButtonItem.target). performAction(barButtonItem.action, withObject: barButtonItem) }

Slide 38

Slide 38 text

// Objective-C void tap(UIBarButtonItem *barButtonItem) { id target = barButtonItem.target; SEL action = barButtonItem.action; [target performSelector:action withObject:barButtonItem]; } // Swift func tap(barButtonItem: UIBarButtonItem) { SelectorProxy(target: barButtonItem.target). performAction(barButtonItem.action, withObject: barButtonItem) }

Slide 39

Slide 39 text

func tap(button: UIButton) { button.sendActionsForControlEvents(.TouchUpInside) }

Slide 40

Slide 40 text

func cellAt(tableView: UITableView, indexPath: NSIndexPath) -> UITableViewCell { tableView.layoutIfNeeded() tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Middle, animated: false) return tableView.cellForRowAtIndexPath(indexPath)! }

Slide 41

Slide 41 text

tableView.selectRowAtIndexPath( indexPath, animated: false, scrollPosition: .Middle) tableView.delegate?.tableView?( tableView, didSelectRowAtIndexPath: newIndexPath)

Slide 42

Slide 42 text

github.com/jeffh/TestingInSwift Full Source in Swift 1.2

Slide 43

Slide 43 text

values = [2, 5, 3] sort(&values) expect(values).to(equal([2, 3, 5]))

Slide 44

Slide 44 text

values = [2, 5, 3] sort(&values) expect(values).to(equal([2, 3, 5])) values = [5, 5, 5] sort(&values) expect(values).to(equal([5, 5, 5]))

Slide 45

Slide 45 text

values = [2, 5, 3] sort(&values) expect(values).to(equal([2, 3, 5]))

Slide 46

Slide 46 text

QuickCheck Property Based Testing

Slide 47

Slide 47 text

Fox Property Based Testing

Slide 48

Slide 48 text

Assert(forAll(array(positiveInteger())) { numbers in var nums = customSort(numbers as! [Int]) var n = 0 for i in 0.. nums[i] { return false } n = nums[i] } return true })

Slide 49

Slide 49 text

Assert(forAll(array(positiveInteger())) { numbers in var nums = customSort(numbers as! [Int]) var n = 0 for i in 0.. nums[i] { return false } n = nums[i] } return true }) func customSort(array: [T]) -> [T] { var items = sorted(array) if items.count == 3 { let t = items[0] items[0] = items[2] items[2] = t } return items }

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

[0, 1, 2, 3, 4] [4] [9, 92, 44, 22] [22, 10, 5]

Slide 52

Slide 52 text

[22, 10, 5] [0, 10, 5] [0, 0, 5] [0, 0, 0] [ ] [22, 10] [0, 0, 3] [0, 0, 1]

Slide 53

Slide 53 text

Assert(forAll(array(positiveInteger())) { numbers in var nums = customSort(numbers as! [Int]) var n = 0 for i in 0.. nums[i] { return false } n = nums[i] } return true }) failed - Property failed with: (0, 0, 1)

Slide 54

Slide 54 text

State

Slide 55

Slide 55 text

State Can you describe state as values?

Slide 56

Slide 56 text

State Machine

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

Model State Transitions

Slide 59

Slide 59 text

Model State Tran User-state In Memory

Slide 60

Slide 60 text

Model State Tran Active Requests Received Numbers User-state In Memory

Slide 61

Slide 61 text

te Transitions uests mbers precondition acting on the subject model state change postcondition (assertions)

Slide 62

Slide 62 text

te Transitions uests mbers Roll Resolve Request precondition acting on the subject model state change postcondition (assertions)

Slide 63

Slide 63 text

te Transitions uests mbers Roll Resolve Request No precondition Tapping button Add active request Is spinner visible?

Slide 64

Slide 64 text

te Transitions uests mbers Roll Resolve Request At least one active request Resolve HTTP Response Remove active request Is spinner visible?

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

Roll Resolve Roll Roll Resolve

Slide 67

Slide 67 text

Roll Resolve Roll

Slide 68

Slide 68 text

QuickCheck Parallel Testing Asynchronous Testing

Slide 69

Slide 69 text

Questions? @jeffhui jeffhui.net github.com/Quick/Quick github.com/jeffh/TestingInSwift github.com/jeffh/Fox github.com/luisobo/Nocilla github.com/AliSoftware/OHHTTPStubs