Testing in Swift

571ef181daf9d289527e50367ab664cb?s=47 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.

571ef181daf9d289527e50367ab664cb?s=128

Jeff

March 19, 2015
Tweet

Transcript

  1. Testing in Swift Jeff Hui

  2. Software Quality

  3. None
  4. Automated programs that describe intent and verify behavior repeatably.

  5. Automated programs that describe intent and verify behavior repeatably.

  6. Automated programs that describe intent and verify behavior repeatably.

  7. Automated programs that describe intent and verify behavior repeatably.

  8. Automated programs that describe intent and verify behavior repeatably.

  9. XCTest

  10. 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]“) } }
  11. Quick BDD Testing Framework

  12. 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])) } } } }
  13. 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”) {} } } }
  14. Testing an App

  15. None
  16. Fancy Architecture ListViewController RandomClient DetailViewController

  17. None
  18. ?

  19. App Test Bundle

  20. App Test Bundle Tests Production

  21. App Test Bundle Tests Production Foo

  22. App Test Bundle Tests Production Foo

  23. None
  24. XCTest UIApplicationDidFinishLaunchingNotification

  25. @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 } }
  26. Testing Networking

  27. Testing Networking Nocilla OHHTTPStubs Method Swizzling

  28. Adapters

  29. public protocol HTTPClient { func sendRequest( request: NSURLRequest, complete: (NSURLResponse?,

    NSData?, NSError?) -> Void) }
  30. 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) } }
  31. 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)) } }
  32. 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")) } // ... } }
  33. Adapters

  34. Adapters Networking Logging Analytics Persistence

  35. UIKit

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

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

    barButtonItem) }
  38. // 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) }
  39. func tap(button: UIButton) { button.sendActionsForControlEvents(.TouchUpInside) }

  40. func cellAt(tableView: UITableView, indexPath: NSIndexPath) -> UITableViewCell { tableView.layoutIfNeeded() tableView.scrollToRowAtIndexPath(indexPath,

    atScrollPosition: .Middle, animated: false) return tableView.cellForRowAtIndexPath(indexPath)! }
  41. tableView.selectRowAtIndexPath( indexPath, animated: false, scrollPosition: .Middle) tableView.delegate?.tableView?( tableView, didSelectRowAtIndexPath: newIndexPath)

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

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

  44. 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]))
  45. values = [2, 5, 3] sort(&values) expect(values).to(equal([2, 3, 5]))

  46. QuickCheck Property Based Testing

  47. Fox Property Based Testing

  48. 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 })
  49. 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 }
  50. None
  51. [0, 1, 2, 3, 4] [4] [9, 92, 44, 22]

    [22, 10, 5]
  52. [22, 10, 5] [0, 10, 5] [0, 0, 5] [0,

    0, 0] [ ] [22, 10] [0, 0, 3] [0, 0, 1]
  53. 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)
  54. State

  55. State Can you describe state as values?

  56. State Machine

  57. None
  58. Model State Transitions

  59. Model State Tran User-state In Memory

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

  61. te Transitions uests mbers precondition acting on the subject model

    state change postcondition (assertions)
  62. te Transitions uests mbers Roll Resolve Request precondition acting on

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

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

    active request Resolve HTTP Response Remove active request Is spinner visible?
  65. None
  66. Roll Resolve Roll Roll Resolve

  67. Roll Resolve Roll

  68. QuickCheck Parallel Testing Asynchronous Testing

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