Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing in Swift

Jeff
March 19, 2015

Testing in Swift

Slides for "Testing in Swift" Talk.

Abstract:

Software tests are great for verifying behavior of your software and improving code quality. Jeff will talk about the Swift testing landscape. Learn about tooling and techniques to writing tested code in Swift. We’ll use the Quick testing library to as a basis for discussing good testing practices and common challenges of testing on iOS.

We’ll also explore generative testing, a prevalent functional programming approach to testing and it’s non-functional application.

About Jeff Hui:

Jeff Hui is an iOS software engineer at Pivotal Labs by day. At night, he hacks on open source projects. He’s a core team member of the Cedar and Quick. Besides that, he is the creator of Nimble, Cedar Xcode Plugin, and Fox.

You can find him at jeffhui.net or @jeffhui.

Jeff

March 19, 2015
Tweet

More Decks by Jeff

Other Decks in Programming

Transcript

  1. 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]“) } }
  2. 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])) } } } }
  3. 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”) {} } } }
  4. ?

  5. @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 } }
  6. 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) } }
  7. 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)) } }
  8. 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")) } // ... } }
  9. // 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) }
  10. 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]))
  11. Assert(forAll(array(positiveInteger())) { numbers in var nums = customSort(numbers as! [Int])

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

    var n = 0 for i in 0..<nums.count { if n > nums[i] { return false } n = nums[i] } return true }) func customSort<T: Comparable>(array: [T]) -> [T] { var items = sorted(array) if items.count == 3 { let t = items[0] items[0] = items[2] items[2] = t } return items }
  13. [22, 10, 5] [0, 10, 5] [0, 0, 5] [0,

    0, 0] [ ] [22, 10] [0, 0, 3] [0, 0, 1]
  14. Assert(forAll(array(positiveInteger())) { numbers in var nums = customSort(numbers as! [Int])

    var n = 0 for i in 0..<nums.count { if n > nums[i] { return false } n = nums[i] } return true }) failed - Property failed with: (0, 0, 1)
  15. te Transitions uests mbers Roll Resolve Request precondition acting on

    the subject model state change postcondition (assertions)
  16. te Transitions uests mbers Roll Resolve Request No precondition Tapping

    button Add active request Is spinner visible?
  17. te Transitions uests mbers Roll Resolve Request At least one

    active request Resolve HTTP Response Remove active request Is spinner visible?