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

The Two Sides of Testing

The Two Sides of Testing

Avatar for Brandon Williams

Brandon Williams

March 01, 2017
Tweet

More Decks by Brandon Williams

Other Decks in Programming

Transcript

  1. /** * Reads a number from a file on disk,

    performs a computation, prints the result to the console, and returns the result. */ func compute(file: String) -> Int { }
  2. /** * Reads a number from a file on disk,

    performs a computation, prints the result to the console, and returns the result. */ func compute(file: String) -> Int { let value = Bundle.main.path(forResource: file, ofType: nil) .flatMap { try? String(contentsOfFile: $0) } .flatMap { Int($0) } ?? 0 let result = value * value print("Computed: \(result)") return result }
  3. /** * Reads a number from a file on disk,

    performs a computation, prints the result to the console, and returns the result. */ func compute(file: String) -> Int { let value = Bundle.main.path(forResource: file, ofType: nil) // "/var/.../number.txt" .flatMap { try? String(contentsOfFile: $0) } // "123" .flatMap { Int($0) } // 123 ?? 0 // 123 let result = value * value // 15129 print("Computed: \(result)") // "Computed: 15129\n" return result // 15129 } compute(file: "number.txt") // 15129
  4. Side effects An expression is said to have a “side

    effect” if its execution makes an observable change to the outside world.
  5. A be!er way to handle side effects Try to describe

    effects as much as possible without actually performing the effects.
  6. A be!er way to handle side effects func compute(file: String)

    -> (Int, String) { let value = Bundle.main.path(forResource: file, ofType: nil) .flatMap { try? String(contentsOfFile: $0) } .flatMap { Int($0) } ?? 0 let result = value * value return (result, "Computed: \(result)") }
  7. Co-effects If an effect is a change to the outside

    world after executing an expression... ...then... ...a co-effect is the state of the world that the expression needs in order to execute.
  8. Co-effects An expression is said to have a “co-effect” if

    it requires a particular state of the world in order to execute.
  9. struct Environment { let apiService: ServiceProtocol let cookieStorage: HTTPCookieStorageProtocol let

    currentUser: User? let dateType: DateProtocol.Type let language: Language }
  10. struct Environment { let apiService: ServiceProtocol let cookieStorage: HTTPCookieStorageProtocol let

    currentUser: User? let dateType: DateProtocol.Type let language: Language let mainBundle: BundleProtocol }
  11. struct Environment { let apiService: ServiceProtocol let cookieStorage: HTTPCookieStorageProtocol let

    currentUser: User? let dateType: DateProtocol.Type let language: Language let mainBundle: BundleProtocol let reachability: SignalProducer<Reachability, NoError> }
  12. struct Environment { let apiService: ServiceProtocol let cookieStorage: HTTPCookieStorageProtocol let

    currentUser: User? let dateType: DateProtocol.Type let language: Language let mainBundle: BundleProtocol let reachability: SignalProducer<Reachability, NoError> let scheduler: DateSchedulerProtocol }
  13. struct Environment { let apiService: ServiceProtocol let cookieStorage: HTTPCookieStorageProtocol let

    currentUser: User? let dateType: DateProtocol.Type let language: Language let mainBundle: BundleProtocol let reachability: SignalProducer<Reachability, NoError> let scheduler: DateSchedulerProtocol let userDefaults: UserDefaultsProtocol }
  14. Refactor protocol BundleProtocol { func path(forResource name: String?, ofType ext:

    String?) -> String? } extension Bundle: BundleProtocol {}
  15. Refactor struct SuccessfulPathForResourceBundle: BundleProtocol { func path(forResource name: String?, ofType

    ext: String?) -> String? { return "a/path/to/a/file.txt" } } struct FailedPathForResourceBundle: BundleProtocol { func path(forResource name: String?, ofType ext: String?) -> String? { return nil } }
  16. Refactor protocol ContentsOfFileProtocol { static func from(contentsOfFile file: String) throws

    -> String } extension String: ContentsOfFileProtocol { static func from(contentsOfFile file: String) throws -> String { return try String(contentsOfFile: file) } }
  17. Refactor struct IntContentsOfFile: ContentsOfFileProtocol { static func from(contentsOfFile file: String)

    throws -> String { return "123" } } struct NonIntContentsOfFile: ContentsOfFileProtocol { static func from(contentsOfFile file: String) throws -> String { return "asdf" } } struct ThrowingContentsOfFile: ContentsOfFileProtocol { static func from(contentsOfFile file: String) throws -> String { throw SomeError() } }
  18. Refactor func compute(file: String, bundle: BundleProtocol = Bundle.main, contentsOfFileProtocol: ContentsOfFileProtocol.Type

    = String.self) -> (Int, String) { let value = bundle.path(forResource: file, ofType: nil) .flatMap { try? contentsOfFileProtocol.from(contentsOfFile: $0) } .flatMap { Int($0) } ?? 0 let result = value * value return (result, "Computed: \(result)") }
  19. Conclusion To tame effects, think of them as data in

    their own right, and you simply describe the effect rather than actually perform it. A naive interpreter can perform the effects somewhere else.
  20. Conclusion To tame co-effects, put them all in one big

    ole global struct, and don't ever access a global unless it is through that struct.