Swift testing ftw

Preview:

Citation preview

Swift Testing FTW!

Jorge D. Ortiz-Fuentes @jdortiz

#SwiftTesting

A Canonical Examples

production

#SwiftTesting

#SwiftTesting

Agenda

★Basics about unit testing

★4 challenges of Swift Testing

★Proposed enhancements

Basics about Unit Testing

But my code is always awesome!

#SwiftTesting

Unit Tests★Prove correctness of different aspects of the

public interface.

• Prove instead of intuition

• Define contract and assumptions

• Document the code

• Easier refactoring or change

★Reusable code = code + tests

#SwiftTesting

Use Unit Testing Incrementally

★You don’t have to write every unit test

★Start with the classes that take care of the logic

• If mixed apply SOLID

★The easier entry point is fixing bugs

Time writing tests < Time debugging

Ask for your wishes

#SwiftTesting

Types of Unit Tests

★Test return value

★Test state

★Test behavior

#SwiftTesting

The Rules of Testing

★We only test our code

★Only a level of abstraction

★Only public methods

★Only one assertion per test

★Tests are independent of sequence or state

4 Challenges of Swift Testing

Lost

#SwiftTesting

New to Swift

★Still learning the language

★Functional Paradigm

★Swift has bugs

#SwiftTesting

Implicitly unwrapped SUT

★SUT cannot be created in init

★Thus, it needs to be optional

★But once set in setUp, it never becomes nil

★Syntax is clearer with an implicitly unwrapped optional.

#SwiftTesting

XCTAssertEquals

★Works with non custom objects

★But requires objects to be equatable

★Use reference comparison instead

#SwiftTesting

func createSut() { interactor = ShowAllSpeakersInteractorMock() sut = SpeakerListPresenter(interactor: interactor) view = SpeakersListViewMock() sut.view = view } func testViewIsPersisted() { if let persitedView = shut.view as? SpeakersListViewMock { XCTAssertTrue(persistedView === view, “Wrong view persisted”) } else { XCTFail(“View must be persisted”) } }

Example: Test persistence

public class SpeakersListPresenter { let interactor: ShowAllSpeakersInteractorProtocol public weak var view SpeakersListViewProtocol?

public init(interactor: ShowAllSpeakersInteractorProtocol) { self.interactor = interaction } }

No Courage

#SwiftTesting

Room for improvement★Brian Gesiak: XCTest: The Good Parts:

• Replace/customize Testing frameworks

• XCTAssertThrows

• assert/precondition

• 1,000+ tests

★ I add:

• Run tests without the simulator

• Jon Reid provides a method to speed up AppDelegate launch, but not for Swift

No Brains

#SwiftTesting

Access control NTFTC★ It would be nice to have access to internal

properties, but you should only test the public interface

★ Implicit constructors for structs are internal

★However, mocks defined in the same test case can be accessed (internal)

★ If not tested, view controllers may not be public. But it makes things more complicated. More on that later.

#SwiftTesting

Create your own templates

import XCTest import ___PACKAGENAMEASIDENTIFIER___

class ___FILEBASENAMEASIDENTIFIER___: ___VARIABLE_testSubclass___ { // MARK: - Parameters & Constants // MARK: - Test vatiables. var sut: ___VARIABLE_classUnderTest___! // MARK: - Set up and tear down override func setUp() { super.setUp() createSut() } func createSut() { sut = ___VARIABLE_classUnderTest___() } override func tearDown() { releaseSut() super.tearDown() } func releaseSut() { sut = nil }

No Heart

#SwiftTesting

Dependency Injection

★Code of an object depends on other objects.

★Those are considered dependencies.

★Dependencies must be controlled in order to reproduce behavior properly.

#SwiftTesting

Dependency Injection

★Extract and override: move to a method and override in testing class (more fragile)

★Method injection: change the signature of the method to provide the dependency

★Property injection: lazy instantiation

★Constructor injection: not always possible

#SwiftTesting

Stubs & Mocks

★Both are fake objects

★Stubs provide desired responses to the SUT

★Mocks also expect certain behaviors

OCMock / OCMockito

Not Available!

#SwiftTesting

Testing with dependency

class ViewController: UIViewController {

@IBOutlet weak var messageLabel: UILabel!

override func viewDidLoad() { super.viewDidLoad() let userDefaults = NSUserDefaults.standardUserDefaults() let score = userDefaults.integerForKey("PreservedScore") messageLabel.text = String(score) } }

#SwiftTesting

func testMessageLabelDisplaysStoredScore() { var labelMock = LabelMock() sut.messageLabel = labelMock sut.userDefaults = UserDefaultsMock() var view = sut.view if let text = sut.messageLabel.text { XCTAssertEqual(text, "1337", "Label must display the preserved score.") } else { XCTFail("Label text must not be nil.") } } class UserDefaultsMock: NSUserDefaults { override func integerForKey(defaultName: String) -> Int { return 1337 } } class LabelMock: UILabel { var presentedText: String? override internal var text: String? { get { return presentedText } set { presentedText = newValue } } } }

Dependency injectionimport UIKit

public class ViewController: UIViewController {

@IBOutlet public weak var messageLabel: UILabel!

lazy public var userDefaults = NSUserDefaults.standardUserDefaults()

override public func viewDidLoad() { super.viewDidLoad() let score = userDefaults.integerForKey("Score") messageLabel.text = String(score) } }

Let the Architecture Help You

#SwiftTesting

Clean Architecture

View (VC) Presenter

Wireframe

Interactor Repository

Persistence

WSC

Follow the Clean Brick Road

canonicalexamples.com coupon:

COCOAHEADSNL

Thank you!

@jdortiz #CoreDataMT