Date post: | 25-Jul-2015 |
Category: |
Technology |
Upload: | jorge-ortiz |
View: | 393 times |
Download: | 1 times |
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
Thank you!
@jdortiz #CoreDataMT