Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

TDD by Controlling Dependencies

456 views

Published on

How hard can it be testing your iOS apps? Is TDD a utopian scenario for you yet? That might be because of the architecture that you are using. During this talk, I explain how using the SOLID principles and a good architecture can help you to do real Test Driven Development. But don't worry, this talk is not only about theory, I point out some real examples of common problems when testing your apps and explain how the Clean Architecture and Swift come to the rescue in those scenarios.

You can watch the video here: https://www.youtube.com/watch?v=qYpURmZcCKs

Published in: Technology
  • Be the first to comment

  • Be the first to like this

TDD by Controlling Dependencies

  1. 1. TDD by Controlling Dependencies Jorge D. Ortiz-Fuentes
  2. 2. You Go First
  3. 3. TDD by Controlling Dependencies Jorge D. Ortiz-Fuentes @jdortiz #Swift2TDD
  4. 4. A Canonical Examples production #Swift2TDD
  5. 5. #Swift2TDD Agenda ★ TDD ★ Real Life ★ Swift
  6. 6. TDD
  7. 7. TDD
  8. 8. #Swift2TDD Progress as a developer ★ It does things! ★ It does the things I want! ★ I can prove that it does what I say! ★ I am going to make it do exactly what i say! Nerdvana
  9. 9. #Swift2TDD 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
  10. 10. The TDD Dance
  11. 11. Red Green Refactor
  12. 12. #Swift2TDD Pros ★ 100% coverage ★ Detect edge cases and unforeseen scenarios ★ Measurable progress ★ Easier to introduce changes ★ Less debug ★ Decoupled code ★ Explicit dependencies ★ Single responsibility ★ Autopilot programming ★ Simpler design Easier
  13. 13. TDD Cons?
  14. 14. #Swift2TDD Cons ★ Put the horse before the cart ★ Takes longer ★ Initial investment. Slower and more work in QA ★ Design without the big picture ★ Tests must also be rewritten when requirements change ★ Other tests are needed too ★ Tests are as bad as code
  15. 15. – Winston Churchill “Success is stumbling from failure to failure with no loss of enthusiasm.”
  16. 16. Real Life
  17. 17. UI Testing
  18. 18. Persistence
  19. 19. Traits
  20. 20. TDD Swift + 2
  21. 21. Test the UI
  22. 22. #Swift2TDD UI Testing ★ Xcode UI Testing ≠ Unit Testing ★ Nor is Kif, Calabash, or Cucumber (Integration) ★ Unit test is about isolation
  23. 23. View View Presenter Interactor EntityGateway
  24. 24. Make the view dumb class SpeakerTableViewCell: UITableViewCell { @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var dateLabel: UILabel! } extension SpeakerTableViewCell: SpeakerCellProtocol { func displayName(name: String) { nameLabel.text = name } func displayTitle(title: String) { titleLabel.text = title } func displayDateSubmitted(date: String){ dateLabel.text = date } }
  25. 25. Testing a label func testDisplayDateChangesLabelTextToAltValue() { let labelMock = LabelMock() sut.dateLabel = labelMock sut.displayDateSubmitted(speakerAltDateSubmitted) XCTAssertTrue(labelMock.setTextWasInvoked, "displayDate must set the text of the label.") XCTAssertEqual(labelMock.text, speakerAltDateSubmitted, "displayDate must set the text of the label to the provided text.") } // MARK: - Stubs & Mocks class LabelMock: UILabel { // MARK: - Properties var setTextWasInvoked = false override var text: String? { didSet { setTextWasInvoked = true } } }
  26. 26. Test despite the persistence
  27. 27. #Swift2TDD Testing Business Logic ★ Persistence seems to be a requirement for testing the logic ★ But it can be abstracted ★ And the data from the entity gateway
  28. 28. Clean Architecture View (VC) Presenter Connector Interactor Repository Persistence WSC
  29. 29. Isolate the user story class ShowAllSpeakersInteractor { // MARK: - Properties let entityGateway: EntityGatewayProtocol weak var presenter: SpeakersListPresenterProtocol? // MARK: - Initializers init(entityGateway: EntityGatewayProtocol) { self.entityGateway = entityGateway } } extension ShowAllSpeakersInteractor: ShowAllSpeakersInteractorProtocol { func showAllSpeakers() { let entities = entityGateway.fetchAllSpeakers() let displayData = entities.map({entity in return SpeakerDisplayData(speaker: entity)}) presenter?.presentAllSpeakers(displayData) } }
  30. 30. Testing a use case func testEntityIstransformedIntoDisplayData() { entityGateway.entities = [Speaker(name: speakerMainName, title: speakerMainTitle, synopsis: speakerMainSynopsis, dateSubmitted: speakerMainDateSubmitted)] sut.showAllSpeakers() guard presenter.displayData.count == entityGateway.entities.count else { XCTFail(“There must be one and only one display data per each entity of the repo.”) return } let speakerData = presenter.displayData[0] XCTAssertEqual(speakerData.name, speakerMainName, "Speaker name must be copied onto the display data.") XCTAssertEqual(speakerData.title, speakerMainTitle, "Speaker title must be copied onto the display data.") XCTAssertEqual(speakerData.dateSubmitted, speakerMainDateSubmitted, "Speaker submitedDate must be copied onto the display data.") }
  31. 31. Traits
  32. 32. Protocol extensions protocol SegueHandlerTypeProtocol { typealias SegueIdentifier: RawRepresentable } extension SegueHandlerTypeProtocol where Self: UIViewController, SegueIdentifier.RawValue == String { func performSegueWithIdentifier(segueIdentifier: SegueIdentifier, sender: AnyObject?) { performSegueWithIdentifier(segueIdentifier.rawValue, sender: sender) } func segueIdentifierForSegue(segue: UIStoryboardSegue) -> SegueIdentifier { guard let identifier = segue.identifier, segueIdentifier = SegueIdentifier(rawValue: identifier) else { fatalError("Invalid segue identifier (segue.identifier).") } return segueIdentifier } } As explained in WWDC 2015 Swift in Practice
  33. 33. Testing a Trait var sut: SegueHandlerTypeViewControlerMock! class SegueHandlerTypeViewControlerMock: UIViewController, SegueHandlerTypeProtocol { // MARK: - Properties var identifierToPerformSegue: String? var senderPerformingSegue: AnyObject? enum SegueIdentifier: String { case FirstSegue = "FirstSegueString" case SecondSegue = "SecondSegueString" } // MARK: - Mocks & Stubs override func performSegueWithIdentifier(identifier: String, sender: AnyObject?) { identifierToPerformSegue = identifier senderPerformingSegue = sender } }
  34. 34. Testing a trait (2) func testPerformSegueWithIdentifierInvokesClassicVersion() { sut.performSegueWithIdentifier(SegueHandlerTypeViewControlerMock.SegueIdentifier.FirstSeg ue, sender: nil) XCTAssertNotNil(sut.identifierToPerformSegue, "Classic version of performSegueWithIdentifier must be invoked by the one in the protocol extension.") } func testPerformSegueWithIdentifierPassesRawValueOfFirstIdentifier() { sut.performSegueWithIdentifier(SegueHandlerTypeViewControlerMock.SegueIdentifier.FirstSeg ue, sender: nil) XCTAssertEqual(sut.identifierToPerformSegue, firstSegueString, "Identifier must correspond to the raw value of the enum case.") } func testPerformSegueWithIdentifierPassesRawValueOfSecondIdentifier() { sut.performSegueWithIdentifier(SegueHandlerTypeViewControlerMock.SegueIdentifier.SecondSe gue, sender: nil) XCTAssertEqual(sut.identifierToPerformSegue, secondSegueString, "Identifier must correspond to the raw value of the enum case.") }
  35. 35. – Thomas A. Edison “I have not failed. I’ve just found 10,000 ways that won’t work.”
  36. 36. Thank you!
  37. 37. @jdortiz #Swift2TDD

×