SlideShare a Scribd company logo
1 of 103
Download to read offline
brian gesiak 
@modocache 
- Born in Greenpoint 
- Some of you may know me from the Internet
- Quick
- Consumers include CapitalOne, GitHub, Artsy, open source projects…
brian gesiak 
@modocache 
- Also iOS Core Systems at Facebook
functional programming 
in swift 
- Excited by interest
class Banana { 
// ... 
func peel() { 
peeled = true 
} 
} 
- Still write code like Obj-C, embarrassing
imperative programming 
- Learn from new field (to me) 
- Bring it back to my day job
functional programming 
imperative programming 
- Learn from new field (to me) 
- Bring it back to my day job
functional programming 
imperative programming 
- Learn from new field (to me) 
- Bring it back to my day job
testing 
- In particular 
- Let’s make sure we’re on same page 
- How many people here have written a unit test?
why test? 
- Confirm app does what you think it does
- Imperative tests follow basic pattern 
- Set up state, make state change, confirm state change
arrange 
act 
assert 
- Imperative tests follow basic pattern 
- Set up state, make state change, confirm state change
class BananaTests: XCTestCase { 
var banana: Banana! 
override func setUp() { 
// Arrange: Prepare necessary objects, state 
banana = Banana(delicious: true) 
} 
func testPeelRemovesPeel() { 
// Act: Perform an action with side effects 
banana.peel() 
// Assert: Confirm the effects of that action 
XCTAssert(banana.peeled) 
} 
} 
- Makes sense with stateful code (explain peel) 
- Frameworks like XCTest evolved to help build state
class BananaTests: XCTestCase { 
var banana: Banana! 
override func setUp() { 
// Arrange: Prepare necessary objects, state 
banana = Banana(delicious: true) 
} 
func testPeelRemovesPeel() { 
// Act: Perform an action with side effects 
banana.peel() 
// Assert: Confirm the effects of that action 
XCTAssert(banana.peeled) 
} 
} 
- Makes sense with stateful code (explain peel) 
- Frameworks like XCTest evolved to help build state
class BananaTests: XCTestCase { 
var banana: Banana! 
override func setUp() { 
// Arrange: Prepare necessary objects, state 
banana = Banana(delicious: true) 
} 
func testPeelRemovesPeel() { 
// Act: Perform an action with side effects 
banana.peel() 
// Assert: Confirm the effects of that action 
XCTAssert(banana.peeled) 
} 
} 
- Makes sense with stateful code (explain peel) 
- Frameworks like XCTest evolved to help build state
class BananaTests: XCTestCase { 
var banana: Banana! 
override func setUp() { 
// Arrange: Prepare necessary objects, state 
banana = Banana(delicious: true) 
} 
func testPeelRemovesPeel() { 
// Act: Perform an action with side effects 
banana.peel() 
// Assert: Confirm the effects of that action 
XCTAssert(banana.peeled) 
} 
} 
- Makes sense with stateful code (explain peel) 
- Frameworks like XCTest evolved to help build state
beforeEach { 
// Arrange #1: Prepare banana 
banana = Banana(delicious: true) 
} 
describe("peel") { 
beforeEach { 
// Arrange #2: More preparation of banana state 
banana.peeled = false 
} 
it("peels the banana") { 
// Act: Perform an action with side effects 
banana.peel() 
// Assert: Confirm the effects of that action 
expect(banana.peeled).to(beTrue()) 
} 
} 
- Many frameworks created to build state
beforeEach { 
// Arrange #1: Prepare banana 
banana = Banana(delicious: true) 
} 
describe("peel") { 
beforeEach { 
// Arrange #2: More preparation of banana state 
banana.peeled = false 
} 
it("peels the banana") { 
// Act: Perform an action with side effects 
banana.peel() 
// Assert: Confirm the effects of that action 
expect(banana.peeled).to(beTrue()) 
} 
} 
- Many frameworks created to build state
beforeEach { 
// Arrange #1: Prepare banana 
banana = Banana(delicious: true) 
} 
describe("peel") { 
beforeEach { 
// Arrange #2: More preparation of banana state 
banana.peeled = false 
} 
it("peels the banana") { 
// Act: Perform an action with side effects 
banana.peel() 
// Assert: Confirm the effects of that action 
expect(banana.peeled).to(beTrue()) 
} 
} 
- Many frameworks created to build state
beforeEach { 
// Arrange #1: Prepare banana 
banana = Banana(delicious: true) 
} 
describe("peel") { 
beforeEach { 
// Arrange #2: More preparation of banana state 
banana.peeled = false 
} 
it("peels the banana") { 
// Act: Perform an action with side effects 
banana.peel() 
// Assert: Confirm the effects of that action 
expect(banana.peeled).to(beTrue()) 
} 
} 
- Many frameworks created to build state
beforeEach { 
// Arrange #1: Prepare banana 
banana = Banana(delicious: true) 
} 
describe("peel") { 
beforeEach { 
// Arrange #2: More preparation of banana state 
banana.peeled = false 
} 
it("peels the banana") { 
// Act: Perform an action with side effects 
banana.peel() 
// Assert: Confirm the effects of that action 
expect(banana.peeled).to(beTrue()) 
} 
} 
- Many frameworks created to build state
what’s different in functional 
programming? 
- FP prefers immutable objects and pure functions
func peel(banana: Banana) -> PeeledBanana { 
return PeeledBanana(delicious: banana.delicious) 
} 
- Here’s a pure version of peel
class PeelTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
// Assert: Peeled banana is equally delicious 
let banana = Banana(delicious: false) 
let peeledBanana = peel(banana) 
XCTAssertFalse(peeledBanana.delicious) 
} 
} 
- FP uses simple data structures, don’t need setup 
- Pure functions have no side effects, no act step 
- One line
class PeelTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
// Assert: Peeled banana is equally delicious 
let banana = Banana(delicious: false) 
let peeledBanana = peel(banana) 
XCTAssertFalse(peeledBanana.delicious) 
} 
} 
- FP uses simple data structures, don’t need setup 
- Pure functions have no side effects, no act step 
- One line
class PeelTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
// Assert: Peeled banana is equally delicious 
let banana = Banana(delicious: false) 
let peeledBanana = peel(banana) 
XCTAssertFalse(peeledBanana.delicious) 
} 
} 
- FP uses simple data structures, don’t need setup 
- Pure functions have no side effects, no act step 
- One line
class PeelTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
// Assert: Peeled banana is equally delicious 
let banana = Banana(delicious: false) 
let peeledBanana = peel(banana) 
XCTAssertFalse(peeledBanana.delicious) 
} 
} 
- FP uses simple data structures, don’t need setup 
- Pure functions have no side effects, no act step 
- One line
functional programming: 
spend less time 
worrying about state 
- That’s the big sell of functional programming
xUnit and xSpec 
are out of date 
- Solving the wrong problem—don’t need setup/teardown 
- Haskell has HUnit and hspec, but they’re minor
QuickCheck 
- Instead, FP focuses on a different problem, solved by QuickCheck
class PeelTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
// Assert: Peeled banana is equally delicious 
XCTAssertFalse(peel(Banana(delicious: false)).delicious) 
XCTAssert(peel(Banana(delicious: true)).delicious) 
} 
} 
- To illustrate look at peel() again 
- Tested false, how about true? 
- Two params may be OK, but Int?
class PeelTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
// Assert: Peeled banana is equally delicious 
XCTAssertFalse(peel(Banana(delicious: false)).delicious) 
XCTAssert(peel(Banana(delicious: true)).delicious) 
} 
} 
- To illustrate look at peel() again 
- Tested false, how about true? 
- Two params may be OK, but Int?
func testPeelReturnsPeeledBananaWithSameWeight() { 
// Test base case 
XCTAssertEqual(peel(Banana(weight: 2)).weight, 2) 
// Test some edge cases 
XCTAssertEqual(peel(Banana(weight: 0)).weight, 0) 
XCTAssertEqual(peel(Banana(weight: -1)).weight, -1) 
} 
- Up to us to determine what to test 
- These are example-based tests 
- Helps us think about edge cases (but we think of them)
func testPeelReturnsPeeledBananaWithSameWeight() { 
// Test base case 
XCTAssertEqual(peel(Banana(weight: 2)).weight, 2) 
// Test some edge cases 
XCTAssertEqual(peel(Banana(weight: 0)).weight, 0) 
XCTAssertEqual(peel(Banana(weight: -1)).weight, -1) 
} 
- Up to us to determine what to test 
- These are example-based tests 
- Helps us think about edge cases (but we think of them)
QuickCheck 
- Solves this problem 
- Basic idea…
don’t write tests 
generate them 
- Fox will throw tons of random data at your function, expecting core properties to hold 
- Ported to many languages, and now ObjC and Swift
Fox 
- Written by Jeff Hui from Pivotal Labs
property-based testing 
- QuickCheck and Fox are property-based testing frameworks 
- What is a property?
class BananaTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
let property = forAll(integer()) { weight in 
let banana = Banana(weight: weight as Int) 
let peeled = peel(banana) 
return peeled.weight == banana.weight 
} 
Fox.Assert(property) 
} 
} 
- Given any integer 
- Fox finds the edge cases for us
class BananaTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
let property = forAll(integer()) { weight in 
let banana = Banana(weight: weight as Int) 
let peeled = peel(banana) 
return peeled.weight == banana.weight 
} 
Fox.Assert(property) 
} 
} 
- Given any integer 
- Fox finds the edge cases for us
class BananaTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
let property = forAll(integer()) { weight in 
let banana = Banana(weight: weight as Int) 
let peeled = peel(banana) 
return peeled.weight == banana.weight 
} 
Fox.Assert(property) 
} 
} 
- Given any integer 
- Fox finds the edge cases for us
class BananaTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
let property = forAll(integer()) { weight in 
let banana = Banana(weight: weight as Int) 
let peeled = peel(banana) 
return peeled.weight == banana.weight 
} 
Fox.Assert(property) 
} 
} 
- Given any integer 
- Fox finds the edge cases for us
class BananaTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
let property = forAll(integer()) { weight in 
let banana = Banana(weight: weight as Int) 
let peeled = peel(banana) 
return peeled.weight == banana.weight 
} 
Fox.Assert(property) 
} 
} 
- Given any integer 
- Fox finds the edge cases for us
class BananaTests: XCTestCase { 
func testPeelReturnsPeeledBanana() { 
let property = forAll(integer()) { weight in 
let banana = Banana(weight: weight as Int) 
let peeled = peel(banana) 
return peeled.weight == banana.weight 
} 
Fox.Assert(property) 
} 
} 
- Given any integer 
- Fox finds the edge cases for us
github/Archimedes 
Geometry functions for Cocoa and Cocoa Touch 
- Let’s use GitHub’s Archimedes as an example 
- It’s ObjC but we can use Fox
MEDEdgeInsets insets = 
MEDEdgeInsetsMake(10, 5, 10, 5); 
- Archimedes defines insets—like padding for a view
MEDEdgeInsets insets = 
MEDEdgeInsetsMake(10, 5, 10, 5); 
10 
5 5 
10 
- Archimedes defines insets—like padding for a view
MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
// => @"{10, 5, 10, 5}" 
NSString *string = @"{1, 2, 3, 4}"; 
MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); 
// => MEDEdgeInsets(1, 2, 3, 4) 
- Contains functions to convert between insets and strings
MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
// => @"{10, 5, 10, 5}" 
NSString *string = @"{1, 2, 3, 4}"; 
MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); 
// => MEDEdgeInsets(1, 2, 3, 4) 
- Contains functions to convert between insets and strings
MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
// => @"{10, 5, 10, 5}" 
NSString *string = @"{1, 2, 3, 4}"; 
MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); 
// => MEDEdgeInsets(1, 2, 3, 4) 
- Contains functions to convert between insets and strings
MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
// => @"{10, 5, 10, 5}" 
NSString *string = @"{1, 2, 3, 4}"; 
MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); 
// => MEDEdgeInsets(1, 2, 3, 4) 
- Contains functions to convert between insets and strings
MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
// => @"{10, 5, 10, 5}" 
NSString *string = @"{1, 2, 3, 4}"; 
MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); 
// => MEDEdgeInsets(1, 2, 3, 4) 
- Contains functions to convert between insets and strings
MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
// => @"{10, 5, 10, 5}" 
NSString *string = @"{1, 2, 3, 4}"; 
MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); 
// => MEDEdgeInsets(1, 2, 3, 4) 
- Contains functions to convert between insets and strings
it(@"should create a string from an MEDEdgeInsets", ^{ 
expect(NSStringFromMEDEdgeInsets(insets)).to( 
equal(@"{1, 2, 3, 4}”)); 
expect(NSStringFromMEDEdgeInsets(insets2)).to( 
equal(@"{1.05, 2.05, 3.05, 4.05}”)); 
}); 
- Archimedes has example-based tests 
- Why 1, 2, 3, 4? No real reason. 
- Someone assumed these were good enough
it(@"should create a string from an MEDEdgeInsets", ^{ 
expect(NSStringFromMEDEdgeInsets(insets)).to( 
equal(@"{1, 2, 3, 4}”)); 
expect(NSStringFromMEDEdgeInsets(insets2)).to( 
equal(@"{1.05, 2.05, 3.05, 4.05}”)); 
}); 
- Archimedes has example-based tests 
- Why 1, 2, 3, 4? No real reason. 
- Someone assumed these were good enough
it(@"should convert between MEDEdgeInsets and strings", ^{ 
id<FOXGenerator> floats = FOXTuple( 
@[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); 
FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { 
MEDEdgeInsets insets = MEDEdgeInsetsMake( 
[generated[0] floatValue], 
[generated[1] floatValue], 
[generated[2] floatValue], 
[generated[3] floatValue]); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); 
return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); 
})); 
}); 
- Given 4 random numbers, we can convert to string and back
it(@"should convert between MEDEdgeInsets and strings", ^{ 
id<FOXGenerator> floats = FOXTuple( 
@[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); 
FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { 
MEDEdgeInsets insets = MEDEdgeInsetsMake( 
[generated[0] floatValue], 
[generated[1] floatValue], 
[generated[2] floatValue], 
[generated[3] floatValue]); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); 
return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); 
})); 
}); 
- Given 4 random numbers, we can convert to string and back
it(@"should convert between MEDEdgeInsets and strings", ^{ 
id<FOXGenerator> floats = FOXTuple( 
@[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); 
FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { 
MEDEdgeInsets insets = MEDEdgeInsetsMake( 
[generated[0] floatValue], 
[generated[1] floatValue], 
[generated[2] floatValue], 
[generated[3] floatValue]); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); 
return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); 
})); 
}); 
- Given 4 random numbers, we can convert to string and back
it(@"should convert between MEDEdgeInsets and strings", ^{ 
id<FOXGenerator> floats = FOXTuple( 
@[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); 
FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { 
MEDEdgeInsets insets = MEDEdgeInsetsMake( 
[generated[0] floatValue], 
[generated[1] floatValue], 
[generated[2] floatValue], 
[generated[3] floatValue]); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); 
return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); 
})); 
}); 
- Given 4 random numbers, we can convert to string and back
it(@"should convert between MEDEdgeInsets and strings", ^{ 
id<FOXGenerator> floats = FOXTuple( 
@[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); 
FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { 
MEDEdgeInsets insets = MEDEdgeInsetsMake( 
[generated[0] floatValue], 
[generated[1] floatValue], 
[generated[2] floatValue], 
[generated[3] floatValue]); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); 
return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); 
})); 
}); 
- Given 4 random numbers, we can convert to string and back
it(@"should convert between MEDEdgeInsets and strings", ^{ 
id<FOXGenerator> floats = FOXTuple( 
@[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); 
FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { 
MEDEdgeInsets insets = MEDEdgeInsetsMake( 
[generated[0] floatValue], 
[generated[1] floatValue], 
[generated[2] floatValue], 
[generated[3] floatValue]); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); 
return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); 
})); 
}); 
- Given 4 random numbers, we can convert to string and back
it(@"should convert between MEDEdgeInsets and strings", ^{ 
id<FOXGenerator> floats = FOXTuple( 
@[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); 
FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { 
MEDEdgeInsets insets = MEDEdgeInsetsMake( 
[generated[0] floatValue], 
[generated[1] floatValue], 
[generated[2] floatValue], 
[generated[3] floatValue]); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); 
return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); 
})); 
}); 
- Given 4 random numbers, we can convert to string and back
MEDEdgeInsets MEDEdgeInsetsFromString(NSString *string) { 
int top = 0, left = 0, bottom = 0, right = 0; 
sscanf(string.UTF8String, 
"{%d, %d, %d, %d}”, 
&top, &left, &bottom, &right); 
return MEDEdgeInsetsMake((CGFloat)top, 
(CGFloat)left 
(CGFloat)bottom, 
(CGFloat)right); 
} 
- Let’s say there was a bug in function to convert string to insets
MEDEdgeInsets MEDEdgeInsetsFromString(NSString *string) { 
int top = 0, left = 0, bottom = 0, right = 0; 
sscanf(string.UTF8String, 
"{%d, %d, %d, %d}”, 
&top, &left, &bottom, &right); 
return MEDEdgeInsetsMake((CGFloat)top, 
(CGFloat)left 
(CGFloat)bottom, 
(CGFloat)right); 
} 
- Let’s say there was a bug in function to convert string to insets
{126.0, -299.0, 16.0, 75.0} 
- Fox will find a failure 
- Then it shrinks to find the smallest failing case
{126.0, -299.0, 16.0, 75.0} => {126.0, -299.0, 16.0, 75.0} 
- Fox will find a failure 
- Then it shrinks to find the smallest failing case
{126.0, -299.0, 16.0, 75.0} => 
{9.87, 0.52, -4.21, 26.0} => {9.0, 0.0, -4.0, 26.0} 
{126.0, -299.0, 16.0, 75.0} 
- Fox will find a failure 
- Then it shrinks to find the smallest failing case
{126.0, -299.0, 16.0, 75.0} => 
{9.87, 0.52, -4.21, 26.0} => {9.0, 0.0, -4.0, 26.0} 
{126.0, -299.0, 16.0, 75.0} 
- Fox will find a failure 
- Then it shrinks to find the smallest failing case
{126.0, -299.0, 16.0, 75.0} => 
{9.87, 0.52, -4.21, 26.0} => {9.0, 0.0, -4.0, 26.0} 
{126.0, -299.0, 16.0, 75.0} 
{4.93, 0.26, -2.10, 13.0} => {4.0, 0.0, -2.0, 13.0} 
- Fox will find a failure 
- Then it shrinks to find the smallest failing case
{126.0, -299.0, 16.0, 75.0} => 
{9.87, 0.52, -4.21, 26.0} => {9.0, 0.0, -4.0, 26.0} 
{126.0, -299.0, 16.0, 75.0} 
{4.93, 0.26, -2.10, 13.0} => {4.0, 0.0, -2.0, 13.0} 
{2.46, 0.13, -1.05, 6.5} => {-2.0, 0.0, -1.0, 6.0} 
- Fox will find a failure 
- Then it shrinks to find the smallest failing case
{126.0, -299.0, 16.0, 75.0} => 
{9.87, 0.52, -4.21, 26.0} => {9.0, 0.0, -4.0, 26.0} 
{126.0, -299.0, 16.0, 75.0} 
{4.93, 0.26, -2.10, 13.0} => {4.0, 0.0, -2.0, 13.0} 
{2.46, 0.13, -1.05, 6.5} => {-2.0, 0.0, -1.0, 6.0} 
... 
{0.1, 0.0, 0.0, 0.0} => {0.0, 0.0, 0.0, 0.0} 
- Fox will find a failure 
- Then it shrinks to find the smallest failing case
it(@"should convert between MEDEdgeInsets and strings", ^{ 
id<FOXGenerator> floats = FOXTuple( 
@[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); 
FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { 
MEDEdgeInsets insets = MEDEdgeInsetsMake( 
[generated[0] floatValue], 
[generated[1] floatValue], 
[generated[2] floatValue], 
[generated[3] floatValue]); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); 
return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); 
})); 
}); 
- So when you run the test, it’s easier to interpret failure 
- Property-based tests are powerful 
- Can use in tandem with example-based tests
it(@"should convert between MEDEdgeInsets and strings", ^{ 
id<FOXGenerator> floats = FOXTuple( 
@[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); 
FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { 
MEDEdgeInsets insets = MEDEdgeInsetsMake( 
Property failed with (0.1, 0.0, 0.0, 0.0) 
[generated[0] floatValue], 
[generated[1] floatValue], 
[generated[2] floatValue], 
[generated[3] floatValue]); 
NSString *string = NSStringFromMEDEdgeInsets(insets); 
MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); 
return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); 
})); 
}); 
- So when you run the test, it’s easier to interpret failure 
- Property-based tests are powerful 
- Can use in tandem with example-based tests
test stateful code 
- Not only can be used together with example-based tests 
- Can also test stateful things like view controllers
- We can use Fox
- We can use Fox
state 
- Model changes in state using transitions 
- Transitions have corresponding “model state” updates 
- Specify conditions that must be met after transition
state 
more transition 
less transition 
- Model changes in state using transitions 
- Transitions have corresponding “model state” updates 
- Specify conditions that must be met after transition
state 
count 
more transition 
count + 1 
less transition 
count - 1 
- Model changes in state using transitions 
- Transitions have corresponding “model state” updates 
- Specify conditions that must be met after transition
state 
count 
property 
more transition 
count + 1 
property 
less transition 
count - 1 
- Model changes in state using transitions 
- Transitions have corresponding “model state” updates 
- Specify conditions that must be met after transition
initial 
count = 0 
- Fox will run transitions at random, checking that properties hold
initial 
count = 0 
more 
count = 1 
property 
less 
count = 0 
property 
- Fox will run transitions at random, checking that properties hold
initial 
count = 0 
more 
count = 1 
property 
less 
count = 0 
property 
more 
count = 1 
more 
count = 2 
- Fox will run transitions at random, checking that properties hold
let moreTransition = FOXTransition(action: { state, _ in 
let controller = state as BananaViewController 
controller.moreButton.sendActionsForControlEvents( 
UIControlEvents.TouchUpInside) 
}, nextModelState: { modelState, _ in 
return (modelState as Int) + 1 
}) 
- Fox gives us a hook to move into the next state 
- And we need to update our model state as well
let moreTransition = FOXTransition(action: { state, _ in 
let controller = state as BananaViewController 
controller.moreButton.sendActionsForControlEvents( 
UIControlEvents.TouchUpInside) 
}, nextModelState: { modelState, _ in 
return (modelState as Int) + 1 
}) 
- Fox gives us a hook to move into the next state 
- And we need to update our model state as well
let moreTransition = FOXTransition(action: { state, _ in 
let controller = state as BananaViewController 
controller.moreButton.sendActionsForControlEvents( 
UIControlEvents.TouchUpInside) 
}, nextModelState: { modelState, _ in 
return (modelState as Int) + 1 
}) 
- Fox gives us a hook to move into the next state 
- And we need to update our model state as well
let moreTransition = FOXTransition(action: { state, _ in 
let controller = state as BananaViewController 
controller.moreButton.sendActionsForControlEvents( 
UIControlEvents.TouchUpInside) 
}, nextModelState: { modelState, _ in 
return (modelState as Int) + 1 
}) 
- Fox gives us a hook to move into the next state 
- And we need to update our model state as well
let moreTransition = FOXTransition(action: { state, _ in 
let controller = state as BananaViewController 
controller.moreButton.sendActionsForControlEvents( 
UIControlEvents.TouchUpInside) 
}, nextModelState: { modelState, _ in 
return (modelState as Int) + 1 
}) 
- Fox gives us a hook to move into the next state 
- And we need to update our model state as well
let moreTransition = FOXTransition(action: { state, _ in 
let controller = state as BananaViewController 
controller.moreButton.sendActionsForControlEvents( 
UIControlEvents.TouchUpInside) 
}, nextModelState: { modelState, _ in 
return (modelState as Int) + 1 
}) 
- Fox gives us a hook to move into the next state 
- And we need to update our model state as well
moreTransition.postcondition = { (modelState, _, 
subject, _, _) in 
let controller = subject as BananaViewController 
return modelState as Int == controller.bananaCount 
} 
- Post condition (or property) is must be fulfilled after every transition—that view controller displays correct model state 
- We can only tap more when count < 10
moreTransition.postcondition = { (modelState, _, 
subject, _, _) in 
let controller = subject as BananaViewController 
return modelState as Int == controller.bananaCount 
} 
- Post condition (or property) is must be fulfilled after every transition—that view controller displays correct model state 
- We can only tap more when count < 10
moreTransition.postcondition = { (modelState, _, 
subject, _, _) in 
let controller = subject as BananaViewController 
return modelState as Int == controller.bananaCount 
} 
- Post condition (or property) is must be fulfilled after every transition—that view controller displays correct model state 
- We can only tap more when count < 10
moreTransition.postcondition = { (modelState, _, 
subject, _, _) in 
let controller = subject as BananaViewController 
return modelState as Int == controller.bananaCount 
} 
moreTransition.precondition = { modelState in 
return (modelState as Int) < 10 
} 
- Post condition (or property) is must be fulfilled after every transition—that view controller displays correct model state 
- We can only tap more when count < 10
moreTransition.postcondition = { (modelState, _, 
subject, _, _) in 
let controller = subject as BananaViewController 
return modelState as Int == controller.bananaCount 
} 
moreTransition.precondition = { modelState in 
return (modelState as Int) < 10 
} 
- Post condition (or property) is must be fulfilled after every transition—that view controller displays correct model state 
- We can only tap more when count < 10
let stateMachine = FOXFiniteStateMachine( 
initialModelState: 0) 
stateMachine.addTransition(moreTransition) 
stateMachine.addTransition(lessTransition) 
let executedCommands = executeCommands(stateMachine) { 
let storyboard = UIStoryboard(name: "Main", bundle: nil) 
let controller = 
storyboard.instantiateInitialViewController() 
as BananaViewController 
let _ = controller.view 
return controller 
} 
Fox.Assert(forAll(executedCommands) { commands in 
return executedSuccessfully(commands as NSArray) 
}) 
- Same gist for less transition 
- Define state machine with initial model state 
- Provide initial state with executeCommands 
- Minimum failing case displayed
let stateMachine = FOXFiniteStateMachine( 
initialModelState: 0) 
stateMachine.addTransition(moreTransition) 
stateMachine.addTransition(lessTransition) 
let executedCommands = executeCommands(stateMachine) { 
let storyboard = UIStoryboard(name: "Main", bundle: nil) 
let controller = 
storyboard.instantiateInitialViewController() 
as BananaViewController 
let _ = controller.view 
return controller 
} 
Fox.Assert(forAll(executedCommands) { commands in 
return executedSuccessfully(commands as NSArray) 
}) 
- Same gist for less transition 
- Define state machine with initial model state 
- Provide initial state with executeCommands 
- Minimum failing case displayed
let stateMachine = FOXFiniteStateMachine( 
initialModelState: 0) 
stateMachine.addTransition(moreTransition) 
stateMachine.addTransition(lessTransition) 
let executedCommands = executeCommands(stateMachine) { 
let storyboard = UIStoryboard(name: "Main", bundle: nil) 
let controller = 
storyboard.instantiateInitialViewController() 
as BananaViewController 
let _ = controller.view 
return controller 
} 
Fox.Assert(forAll(executedCommands) { commands in 
return executedSuccessfully(commands as NSArray) 
}) 
- Same gist for less transition 
- Define state machine with initial model state 
- Provide initial state with executeCommands 
- Minimum failing case displayed
let stateMachine = FOXFiniteStateMachine( 
initialModelState: 0) 
stateMachine.addTransition(moreTransition) 
stateMachine.addTransition(lessTransition) 
let executedCommands = executeCommands(stateMachine) { 
let storyboard = UIStoryboard(name: "Main", bundle: nil) 
let controller = 
storyboard.instantiateInitialViewController() 
as BananaViewController 
let _ = controller.view 
return controller 
} 
Fox.Assert(forAll(executedCommands) { commands in 
return executedSuccessfully(commands as NSArray) 
}) 
- Same gist for less transition 
- Define state machine with initial model state 
- Provide initial state with executeCommands 
- Minimum failing case displayed
let stateMachine = FOXFiniteStateMachine( 
initialModelState: 0) 
stateMachine.addTransition(moreTransition) 
stateMachine.addTransition(lessTransition) 
let executedCommands = executeCommands(stateMachine) { 
let storyboard = UIStoryboard(name: "Main", bundle: nil) 
let controller = 
storyboard.instantiateInitialViewController() 
as BananaViewController 
let _ = controller.view 
return controller 
} 
Fox.Assert(forAll(executedCommands) { commands in 
return executedSuccessfully(commands as NSArray) 
}) 
- Same gist for less transition 
- Define state machine with initial model state 
- Provide initial state with executeCommands 
- Minimum failing case displayed
github.com/jeffh/Fox 
fox-testing.readthedocs.org/ 
- That’s Fox in a nutshell; Many more features, see GitHub 
- Property-based testing is deep, many resources available 
- Fox will continue to grow, perhaps provide properties
functional programming 
imperative programming 
- QuickCheck has been around since 1999, only in ObjC/Swift recently 
- Great example of cross-pollination of ideas, excited for more
brian gesiak 
@modocache 
- Thanks for listening. Questions?

More Related Content

Similar to Property-Based Testing in Objective-C & Swift with Fox

Chaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreChaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreNicolas Carlo
 
Learning Functional Programming Without Growing a Neckbeard
Learning Functional Programming Without Growing a NeckbeardLearning Functional Programming Without Growing a Neckbeard
Learning Functional Programming Without Growing a NeckbeardKelsey Gilmore-Innis
 
Cucumber on the JVM with Groovy
Cucumber on the JVM with GroovyCucumber on the JVM with Groovy
Cucumber on the JVM with GroovyRichard Paul
 
Chaining et composition de fonctions avec lodash / underscore
Chaining et composition de fonctions avec lodash / underscoreChaining et composition de fonctions avec lodash / underscore
Chaining et composition de fonctions avec lodash / underscoreNicolas Carlo
 
Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015Lukas Ruebbelke
 
Getting property based testing to work after struggling for 3 years
Getting property based testing to work after struggling for 3 yearsGetting property based testing to work after struggling for 3 years
Getting property based testing to work after struggling for 3 yearsSaurabh Nanda
 
Intro to scala
Intro to scalaIntro to scala
Intro to scalaJoe Zulli
 
The $path to knowledge: What little it take to unit-test Perl.
The $path to knowledge: What little it take to unit-test Perl.The $path to knowledge: What little it take to unit-test Perl.
The $path to knowledge: What little it take to unit-test Perl.Workhorse Computing
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteLeonardo Proietti
 
Using c++Im also using a the ide editor called CodeLiteThe hea.pdf
Using c++Im also using a the ide editor called CodeLiteThe hea.pdfUsing c++Im also using a the ide editor called CodeLiteThe hea.pdf
Using c++Im also using a the ide editor called CodeLiteThe hea.pdffashiongallery1
 
Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit TestingMike Lively
 
Unit Testing using PHPUnit
Unit Testing using  PHPUnitUnit Testing using  PHPUnit
Unit Testing using PHPUnitvaruntaliyan
 

Similar to Property-Based Testing in Objective-C & Swift with Fox (20)

Chaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscoreChaining and function composition with lodash / underscore
Chaining and function composition with lodash / underscore
 
Learning Functional Programming Without Growing a Neckbeard
Learning Functional Programming Without Growing a NeckbeardLearning Functional Programming Without Growing a Neckbeard
Learning Functional Programming Without Growing a Neckbeard
 
Cucumber on the JVM with Groovy
Cucumber on the JVM with GroovyCucumber on the JVM with Groovy
Cucumber on the JVM with Groovy
 
Chaining et composition de fonctions avec lodash / underscore
Chaining et composition de fonctions avec lodash / underscoreChaining et composition de fonctions avec lodash / underscore
Chaining et composition de fonctions avec lodash / underscore
 
Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015
 
Getting property based testing to work after struggling for 3 years
Getting property based testing to work after struggling for 3 yearsGetting property based testing to work after struggling for 3 years
Getting property based testing to work after struggling for 3 years
 
Intro to scala
Intro to scalaIntro to scala
Intro to scala
 
Event Driven Javascript
Event Driven JavascriptEvent Driven Javascript
Event Driven Javascript
 
Functional programming
Functional programmingFunctional programming
Functional programming
 
Scala in Practice
Scala in PracticeScala in Practice
Scala in Practice
 
The $path to knowledge: What little it take to unit-test Perl.
The $path to knowledge: What little it take to unit-test Perl.The $path to knowledge: What little it take to unit-test Perl.
The $path to knowledge: What little it take to unit-test Perl.
 
ES6: The Awesome Parts
ES6: The Awesome PartsES6: The Awesome Parts
ES6: The Awesome Parts
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
 
Solid principles
Solid principlesSolid principles
Solid principles
 
test
testtest
test
 
Using c++Im also using a the ide editor called CodeLiteThe hea.pdf
Using c++Im also using a the ide editor called CodeLiteThe hea.pdfUsing c++Im also using a the ide editor called CodeLiteThe hea.pdf
Using c++Im also using a the ide editor called CodeLiteThe hea.pdf
 
Advanced PHPUnit Testing
Advanced PHPUnit TestingAdvanced PHPUnit Testing
Advanced PHPUnit Testing
 
PHP Unit Testing
PHP Unit TestingPHP Unit Testing
PHP Unit Testing
 
Unit Testing Lots of Perl
Unit Testing Lots of PerlUnit Testing Lots of Perl
Unit Testing Lots of Perl
 
Unit Testing using PHPUnit
Unit Testing using  PHPUnitUnit Testing using  PHPUnit
Unit Testing using PHPUnit
 

More from Brian Gesiak

Everything You (N)ever Wanted to Know about Testing View Controllers
Everything You (N)ever Wanted to Know about Testing View ControllersEverything You (N)ever Wanted to Know about Testing View Controllers
Everything You (N)ever Wanted to Know about Testing View ControllersBrian Gesiak
 
Quick: Better Tests via Incremental Setup
Quick: Better Tests via Incremental SetupQuick: Better Tests via Incremental Setup
Quick: Better Tests via Incremental SetupBrian Gesiak
 
Intel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingIntel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingBrian Gesiak
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API DesignBrian Gesiak
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API DesignBrian Gesiak
 
Apple Templates Considered Harmful
Apple Templates Considered HarmfulApple Templates Considered Harmful
Apple Templates Considered HarmfulBrian Gesiak
 
アップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられるアップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられるBrian Gesiak
 
RSpec 3.0: Under the Covers
RSpec 3.0: Under the CoversRSpec 3.0: Under the Covers
RSpec 3.0: Under the CoversBrian Gesiak
 
iOS Behavior-Driven Development
iOS Behavior-Driven DevelopmentiOS Behavior-Driven Development
iOS Behavior-Driven DevelopmentBrian Gesiak
 
iOSビヘイビア駆動開発
iOSビヘイビア駆動開発iOSビヘイビア駆動開発
iOSビヘイビア駆動開発Brian Gesiak
 

More from Brian Gesiak (11)

iOS API Design
iOS API DesigniOS API Design
iOS API Design
 
Everything You (N)ever Wanted to Know about Testing View Controllers
Everything You (N)ever Wanted to Know about Testing View ControllersEverything You (N)ever Wanted to Know about Testing View Controllers
Everything You (N)ever Wanted to Know about Testing View Controllers
 
Quick: Better Tests via Incremental Setup
Quick: Better Tests via Incremental SetupQuick: Better Tests via Incremental Setup
Quick: Better Tests via Incremental Setup
 
Intel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance ProgrammingIntel® Xeon® Phi Coprocessor High Performance Programming
Intel® Xeon® Phi Coprocessor High Performance Programming
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API Design
 
iOS UI Component API Design
iOS UI Component API DesigniOS UI Component API Design
iOS UI Component API Design
 
Apple Templates Considered Harmful
Apple Templates Considered HarmfulApple Templates Considered Harmful
Apple Templates Considered Harmful
 
アップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられるアップルのテンプレートは有害と考えられる
アップルのテンプレートは有害と考えられる
 
RSpec 3.0: Under the Covers
RSpec 3.0: Under the CoversRSpec 3.0: Under the Covers
RSpec 3.0: Under the Covers
 
iOS Behavior-Driven Development
iOS Behavior-Driven DevelopmentiOS Behavior-Driven Development
iOS Behavior-Driven Development
 
iOSビヘイビア駆動開発
iOSビヘイビア駆動開発iOSビヘイビア駆動開発
iOSビヘイビア駆動開発
 

Recently uploaded

Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessPixlogix Infotech
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slidespraypatel2
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUK Journal
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)wesley chun
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 

Recently uploaded (20)

Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Advantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your BusinessAdvantages of Hiring UIUX Design Service Providers for Your Business
Advantages of Hiring UIUX Design Service Providers for Your Business
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Slack Application Development 101 Slides
Slack Application Development 101 SlidesSlack Application Development 101 Slides
Slack Application Development 101 Slides
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 

Property-Based Testing in Objective-C & Swift with Fox

  • 1. brian gesiak @modocache - Born in Greenpoint - Some of you may know me from the Internet
  • 3. - Consumers include CapitalOne, GitHub, Artsy, open source projects…
  • 4. brian gesiak @modocache - Also iOS Core Systems at Facebook
  • 5. functional programming in swift - Excited by interest
  • 6. class Banana { // ... func peel() { peeled = true } } - Still write code like Obj-C, embarrassing
  • 7. imperative programming - Learn from new field (to me) - Bring it back to my day job
  • 8. functional programming imperative programming - Learn from new field (to me) - Bring it back to my day job
  • 9. functional programming imperative programming - Learn from new field (to me) - Bring it back to my day job
  • 10. testing - In particular - Let’s make sure we’re on same page - How many people here have written a unit test?
  • 11. why test? - Confirm app does what you think it does
  • 12. - Imperative tests follow basic pattern - Set up state, make state change, confirm state change
  • 13. arrange act assert - Imperative tests follow basic pattern - Set up state, make state change, confirm state change
  • 14. class BananaTests: XCTestCase { var banana: Banana! override func setUp() { // Arrange: Prepare necessary objects, state banana = Banana(delicious: true) } func testPeelRemovesPeel() { // Act: Perform an action with side effects banana.peel() // Assert: Confirm the effects of that action XCTAssert(banana.peeled) } } - Makes sense with stateful code (explain peel) - Frameworks like XCTest evolved to help build state
  • 15. class BananaTests: XCTestCase { var banana: Banana! override func setUp() { // Arrange: Prepare necessary objects, state banana = Banana(delicious: true) } func testPeelRemovesPeel() { // Act: Perform an action with side effects banana.peel() // Assert: Confirm the effects of that action XCTAssert(banana.peeled) } } - Makes sense with stateful code (explain peel) - Frameworks like XCTest evolved to help build state
  • 16. class BananaTests: XCTestCase { var banana: Banana! override func setUp() { // Arrange: Prepare necessary objects, state banana = Banana(delicious: true) } func testPeelRemovesPeel() { // Act: Perform an action with side effects banana.peel() // Assert: Confirm the effects of that action XCTAssert(banana.peeled) } } - Makes sense with stateful code (explain peel) - Frameworks like XCTest evolved to help build state
  • 17. class BananaTests: XCTestCase { var banana: Banana! override func setUp() { // Arrange: Prepare necessary objects, state banana = Banana(delicious: true) } func testPeelRemovesPeel() { // Act: Perform an action with side effects banana.peel() // Assert: Confirm the effects of that action XCTAssert(banana.peeled) } } - Makes sense with stateful code (explain peel) - Frameworks like XCTest evolved to help build state
  • 18. beforeEach { // Arrange #1: Prepare banana banana = Banana(delicious: true) } describe("peel") { beforeEach { // Arrange #2: More preparation of banana state banana.peeled = false } it("peels the banana") { // Act: Perform an action with side effects banana.peel() // Assert: Confirm the effects of that action expect(banana.peeled).to(beTrue()) } } - Many frameworks created to build state
  • 19. beforeEach { // Arrange #1: Prepare banana banana = Banana(delicious: true) } describe("peel") { beforeEach { // Arrange #2: More preparation of banana state banana.peeled = false } it("peels the banana") { // Act: Perform an action with side effects banana.peel() // Assert: Confirm the effects of that action expect(banana.peeled).to(beTrue()) } } - Many frameworks created to build state
  • 20. beforeEach { // Arrange #1: Prepare banana banana = Banana(delicious: true) } describe("peel") { beforeEach { // Arrange #2: More preparation of banana state banana.peeled = false } it("peels the banana") { // Act: Perform an action with side effects banana.peel() // Assert: Confirm the effects of that action expect(banana.peeled).to(beTrue()) } } - Many frameworks created to build state
  • 21. beforeEach { // Arrange #1: Prepare banana banana = Banana(delicious: true) } describe("peel") { beforeEach { // Arrange #2: More preparation of banana state banana.peeled = false } it("peels the banana") { // Act: Perform an action with side effects banana.peel() // Assert: Confirm the effects of that action expect(banana.peeled).to(beTrue()) } } - Many frameworks created to build state
  • 22. beforeEach { // Arrange #1: Prepare banana banana = Banana(delicious: true) } describe("peel") { beforeEach { // Arrange #2: More preparation of banana state banana.peeled = false } it("peels the banana") { // Act: Perform an action with side effects banana.peel() // Assert: Confirm the effects of that action expect(banana.peeled).to(beTrue()) } } - Many frameworks created to build state
  • 23. what’s different in functional programming? - FP prefers immutable objects and pure functions
  • 24. func peel(banana: Banana) -> PeeledBanana { return PeeledBanana(delicious: banana.delicious) } - Here’s a pure version of peel
  • 25. class PeelTests: XCTestCase { func testPeelReturnsPeeledBanana() { // Assert: Peeled banana is equally delicious let banana = Banana(delicious: false) let peeledBanana = peel(banana) XCTAssertFalse(peeledBanana.delicious) } } - FP uses simple data structures, don’t need setup - Pure functions have no side effects, no act step - One line
  • 26. class PeelTests: XCTestCase { func testPeelReturnsPeeledBanana() { // Assert: Peeled banana is equally delicious let banana = Banana(delicious: false) let peeledBanana = peel(banana) XCTAssertFalse(peeledBanana.delicious) } } - FP uses simple data structures, don’t need setup - Pure functions have no side effects, no act step - One line
  • 27. class PeelTests: XCTestCase { func testPeelReturnsPeeledBanana() { // Assert: Peeled banana is equally delicious let banana = Banana(delicious: false) let peeledBanana = peel(banana) XCTAssertFalse(peeledBanana.delicious) } } - FP uses simple data structures, don’t need setup - Pure functions have no side effects, no act step - One line
  • 28. class PeelTests: XCTestCase { func testPeelReturnsPeeledBanana() { // Assert: Peeled banana is equally delicious let banana = Banana(delicious: false) let peeledBanana = peel(banana) XCTAssertFalse(peeledBanana.delicious) } } - FP uses simple data structures, don’t need setup - Pure functions have no side effects, no act step - One line
  • 29. functional programming: spend less time worrying about state - That’s the big sell of functional programming
  • 30. xUnit and xSpec are out of date - Solving the wrong problem—don’t need setup/teardown - Haskell has HUnit and hspec, but they’re minor
  • 31. QuickCheck - Instead, FP focuses on a different problem, solved by QuickCheck
  • 32. class PeelTests: XCTestCase { func testPeelReturnsPeeledBanana() { // Assert: Peeled banana is equally delicious XCTAssertFalse(peel(Banana(delicious: false)).delicious) XCTAssert(peel(Banana(delicious: true)).delicious) } } - To illustrate look at peel() again - Tested false, how about true? - Two params may be OK, but Int?
  • 33. class PeelTests: XCTestCase { func testPeelReturnsPeeledBanana() { // Assert: Peeled banana is equally delicious XCTAssertFalse(peel(Banana(delicious: false)).delicious) XCTAssert(peel(Banana(delicious: true)).delicious) } } - To illustrate look at peel() again - Tested false, how about true? - Two params may be OK, but Int?
  • 34. func testPeelReturnsPeeledBananaWithSameWeight() { // Test base case XCTAssertEqual(peel(Banana(weight: 2)).weight, 2) // Test some edge cases XCTAssertEqual(peel(Banana(weight: 0)).weight, 0) XCTAssertEqual(peel(Banana(weight: -1)).weight, -1) } - Up to us to determine what to test - These are example-based tests - Helps us think about edge cases (but we think of them)
  • 35. func testPeelReturnsPeeledBananaWithSameWeight() { // Test base case XCTAssertEqual(peel(Banana(weight: 2)).weight, 2) // Test some edge cases XCTAssertEqual(peel(Banana(weight: 0)).weight, 0) XCTAssertEqual(peel(Banana(weight: -1)).weight, -1) } - Up to us to determine what to test - These are example-based tests - Helps us think about edge cases (but we think of them)
  • 36. QuickCheck - Solves this problem - Basic idea…
  • 37. don’t write tests generate them - Fox will throw tons of random data at your function, expecting core properties to hold - Ported to many languages, and now ObjC and Swift
  • 38. Fox - Written by Jeff Hui from Pivotal Labs
  • 39. property-based testing - QuickCheck and Fox are property-based testing frameworks - What is a property?
  • 40. class BananaTests: XCTestCase { func testPeelReturnsPeeledBanana() { let property = forAll(integer()) { weight in let banana = Banana(weight: weight as Int) let peeled = peel(banana) return peeled.weight == banana.weight } Fox.Assert(property) } } - Given any integer - Fox finds the edge cases for us
  • 41. class BananaTests: XCTestCase { func testPeelReturnsPeeledBanana() { let property = forAll(integer()) { weight in let banana = Banana(weight: weight as Int) let peeled = peel(banana) return peeled.weight == banana.weight } Fox.Assert(property) } } - Given any integer - Fox finds the edge cases for us
  • 42. class BananaTests: XCTestCase { func testPeelReturnsPeeledBanana() { let property = forAll(integer()) { weight in let banana = Banana(weight: weight as Int) let peeled = peel(banana) return peeled.weight == banana.weight } Fox.Assert(property) } } - Given any integer - Fox finds the edge cases for us
  • 43. class BananaTests: XCTestCase { func testPeelReturnsPeeledBanana() { let property = forAll(integer()) { weight in let banana = Banana(weight: weight as Int) let peeled = peel(banana) return peeled.weight == banana.weight } Fox.Assert(property) } } - Given any integer - Fox finds the edge cases for us
  • 44. class BananaTests: XCTestCase { func testPeelReturnsPeeledBanana() { let property = forAll(integer()) { weight in let banana = Banana(weight: weight as Int) let peeled = peel(banana) return peeled.weight == banana.weight } Fox.Assert(property) } } - Given any integer - Fox finds the edge cases for us
  • 45. class BananaTests: XCTestCase { func testPeelReturnsPeeledBanana() { let property = forAll(integer()) { weight in let banana = Banana(weight: weight as Int) let peeled = peel(banana) return peeled.weight == banana.weight } Fox.Assert(property) } } - Given any integer - Fox finds the edge cases for us
  • 46. github/Archimedes Geometry functions for Cocoa and Cocoa Touch - Let’s use GitHub’s Archimedes as an example - It’s ObjC but we can use Fox
  • 47. MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); - Archimedes defines insets—like padding for a view
  • 48. MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); 10 5 5 10 - Archimedes defines insets—like padding for a view
  • 49. MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); NSString *string = NSStringFromMEDEdgeInsets(insets); // => @"{10, 5, 10, 5}" NSString *string = @"{1, 2, 3, 4}"; MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); // => MEDEdgeInsets(1, 2, 3, 4) - Contains functions to convert between insets and strings
  • 50. MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); NSString *string = NSStringFromMEDEdgeInsets(insets); // => @"{10, 5, 10, 5}" NSString *string = @"{1, 2, 3, 4}"; MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); // => MEDEdgeInsets(1, 2, 3, 4) - Contains functions to convert between insets and strings
  • 51. MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); NSString *string = NSStringFromMEDEdgeInsets(insets); // => @"{10, 5, 10, 5}" NSString *string = @"{1, 2, 3, 4}"; MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); // => MEDEdgeInsets(1, 2, 3, 4) - Contains functions to convert between insets and strings
  • 52. MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); NSString *string = NSStringFromMEDEdgeInsets(insets); // => @"{10, 5, 10, 5}" NSString *string = @"{1, 2, 3, 4}"; MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); // => MEDEdgeInsets(1, 2, 3, 4) - Contains functions to convert between insets and strings
  • 53. MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); NSString *string = NSStringFromMEDEdgeInsets(insets); // => @"{10, 5, 10, 5}" NSString *string = @"{1, 2, 3, 4}"; MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); // => MEDEdgeInsets(1, 2, 3, 4) - Contains functions to convert between insets and strings
  • 54. MEDEdgeInsets insets = MEDEdgeInsetsMake(10, 5, 10, 5); NSString *string = NSStringFromMEDEdgeInsets(insets); // => @"{10, 5, 10, 5}" NSString *string = @"{1, 2, 3, 4}"; MEDEdgeInsets insets = MEDEdgeInsetsFromString(string); // => MEDEdgeInsets(1, 2, 3, 4) - Contains functions to convert between insets and strings
  • 55. it(@"should create a string from an MEDEdgeInsets", ^{ expect(NSStringFromMEDEdgeInsets(insets)).to( equal(@"{1, 2, 3, 4}”)); expect(NSStringFromMEDEdgeInsets(insets2)).to( equal(@"{1.05, 2.05, 3.05, 4.05}”)); }); - Archimedes has example-based tests - Why 1, 2, 3, 4? No real reason. - Someone assumed these were good enough
  • 56. it(@"should create a string from an MEDEdgeInsets", ^{ expect(NSStringFromMEDEdgeInsets(insets)).to( equal(@"{1, 2, 3, 4}”)); expect(NSStringFromMEDEdgeInsets(insets2)).to( equal(@"{1.05, 2.05, 3.05, 4.05}”)); }); - Archimedes has example-based tests - Why 1, 2, 3, 4? No real reason. - Someone assumed these were good enough
  • 57. it(@"should convert between MEDEdgeInsets and strings", ^{ id<FOXGenerator> floats = FOXTuple( @[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { MEDEdgeInsets insets = MEDEdgeInsetsMake( [generated[0] floatValue], [generated[1] floatValue], [generated[2] floatValue], [generated[3] floatValue]); NSString *string = NSStringFromMEDEdgeInsets(insets); MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); })); }); - Given 4 random numbers, we can convert to string and back
  • 58. it(@"should convert between MEDEdgeInsets and strings", ^{ id<FOXGenerator> floats = FOXTuple( @[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { MEDEdgeInsets insets = MEDEdgeInsetsMake( [generated[0] floatValue], [generated[1] floatValue], [generated[2] floatValue], [generated[3] floatValue]); NSString *string = NSStringFromMEDEdgeInsets(insets); MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); })); }); - Given 4 random numbers, we can convert to string and back
  • 59. it(@"should convert between MEDEdgeInsets and strings", ^{ id<FOXGenerator> floats = FOXTuple( @[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { MEDEdgeInsets insets = MEDEdgeInsetsMake( [generated[0] floatValue], [generated[1] floatValue], [generated[2] floatValue], [generated[3] floatValue]); NSString *string = NSStringFromMEDEdgeInsets(insets); MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); })); }); - Given 4 random numbers, we can convert to string and back
  • 60. it(@"should convert between MEDEdgeInsets and strings", ^{ id<FOXGenerator> floats = FOXTuple( @[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { MEDEdgeInsets insets = MEDEdgeInsetsMake( [generated[0] floatValue], [generated[1] floatValue], [generated[2] floatValue], [generated[3] floatValue]); NSString *string = NSStringFromMEDEdgeInsets(insets); MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); })); }); - Given 4 random numbers, we can convert to string and back
  • 61. it(@"should convert between MEDEdgeInsets and strings", ^{ id<FOXGenerator> floats = FOXTuple( @[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { MEDEdgeInsets insets = MEDEdgeInsetsMake( [generated[0] floatValue], [generated[1] floatValue], [generated[2] floatValue], [generated[3] floatValue]); NSString *string = NSStringFromMEDEdgeInsets(insets); MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); })); }); - Given 4 random numbers, we can convert to string and back
  • 62. it(@"should convert between MEDEdgeInsets and strings", ^{ id<FOXGenerator> floats = FOXTuple( @[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { MEDEdgeInsets insets = MEDEdgeInsetsMake( [generated[0] floatValue], [generated[1] floatValue], [generated[2] floatValue], [generated[3] floatValue]); NSString *string = NSStringFromMEDEdgeInsets(insets); MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); })); }); - Given 4 random numbers, we can convert to string and back
  • 63. it(@"should convert between MEDEdgeInsets and strings", ^{ id<FOXGenerator> floats = FOXTuple( @[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { MEDEdgeInsets insets = MEDEdgeInsetsMake( [generated[0] floatValue], [generated[1] floatValue], [generated[2] floatValue], [generated[3] floatValue]); NSString *string = NSStringFromMEDEdgeInsets(insets); MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); })); }); - Given 4 random numbers, we can convert to string and back
  • 64. MEDEdgeInsets MEDEdgeInsetsFromString(NSString *string) { int top = 0, left = 0, bottom = 0, right = 0; sscanf(string.UTF8String, "{%d, %d, %d, %d}”, &top, &left, &bottom, &right); return MEDEdgeInsetsMake((CGFloat)top, (CGFloat)left (CGFloat)bottom, (CGFloat)right); } - Let’s say there was a bug in function to convert string to insets
  • 65. MEDEdgeInsets MEDEdgeInsetsFromString(NSString *string) { int top = 0, left = 0, bottom = 0, right = 0; sscanf(string.UTF8String, "{%d, %d, %d, %d}”, &top, &left, &bottom, &right); return MEDEdgeInsetsMake((CGFloat)top, (CGFloat)left (CGFloat)bottom, (CGFloat)right); } - Let’s say there was a bug in function to convert string to insets
  • 66. {126.0, -299.0, 16.0, 75.0} - Fox will find a failure - Then it shrinks to find the smallest failing case
  • 67. {126.0, -299.0, 16.0, 75.0} => {126.0, -299.0, 16.0, 75.0} - Fox will find a failure - Then it shrinks to find the smallest failing case
  • 68. {126.0, -299.0, 16.0, 75.0} => {9.87, 0.52, -4.21, 26.0} => {9.0, 0.0, -4.0, 26.0} {126.0, -299.0, 16.0, 75.0} - Fox will find a failure - Then it shrinks to find the smallest failing case
  • 69. {126.0, -299.0, 16.0, 75.0} => {9.87, 0.52, -4.21, 26.0} => {9.0, 0.0, -4.0, 26.0} {126.0, -299.0, 16.0, 75.0} - Fox will find a failure - Then it shrinks to find the smallest failing case
  • 70. {126.0, -299.0, 16.0, 75.0} => {9.87, 0.52, -4.21, 26.0} => {9.0, 0.0, -4.0, 26.0} {126.0, -299.0, 16.0, 75.0} {4.93, 0.26, -2.10, 13.0} => {4.0, 0.0, -2.0, 13.0} - Fox will find a failure - Then it shrinks to find the smallest failing case
  • 71. {126.0, -299.0, 16.0, 75.0} => {9.87, 0.52, -4.21, 26.0} => {9.0, 0.0, -4.0, 26.0} {126.0, -299.0, 16.0, 75.0} {4.93, 0.26, -2.10, 13.0} => {4.0, 0.0, -2.0, 13.0} {2.46, 0.13, -1.05, 6.5} => {-2.0, 0.0, -1.0, 6.0} - Fox will find a failure - Then it shrinks to find the smallest failing case
  • 72. {126.0, -299.0, 16.0, 75.0} => {9.87, 0.52, -4.21, 26.0} => {9.0, 0.0, -4.0, 26.0} {126.0, -299.0, 16.0, 75.0} {4.93, 0.26, -2.10, 13.0} => {4.0, 0.0, -2.0, 13.0} {2.46, 0.13, -1.05, 6.5} => {-2.0, 0.0, -1.0, 6.0} ... {0.1, 0.0, 0.0, 0.0} => {0.0, 0.0, 0.0, 0.0} - Fox will find a failure - Then it shrinks to find the smallest failing case
  • 73. it(@"should convert between MEDEdgeInsets and strings", ^{ id<FOXGenerator> floats = FOXTuple( @[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { MEDEdgeInsets insets = MEDEdgeInsetsMake( [generated[0] floatValue], [generated[1] floatValue], [generated[2] floatValue], [generated[3] floatValue]); NSString *string = NSStringFromMEDEdgeInsets(insets); MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); })); }); - So when you run the test, it’s easier to interpret failure - Property-based tests are powerful - Can use in tandem with example-based tests
  • 74. it(@"should convert between MEDEdgeInsets and strings", ^{ id<FOXGenerator> floats = FOXTuple( @[FOXFloat(), FOXFloat(), FOXFloat(), FOXFloat()]); FOXAssert(forAll(floats, ^BOOL(NSArray *generated) { MEDEdgeInsets insets = MEDEdgeInsetsMake( Property failed with (0.1, 0.0, 0.0, 0.0) [generated[0] floatValue], [generated[1] floatValue], [generated[2] floatValue], [generated[3] floatValue]); NSString *string = NSStringFromMEDEdgeInsets(insets); MEDEdgeInsets backToInsets = MEDEdgeInsetsFromString(string); return MEDEdgeInsetsEqualToEdgeInsets(insets, backToInsets); })); }); - So when you run the test, it’s easier to interpret failure - Property-based tests are powerful - Can use in tandem with example-based tests
  • 75. test stateful code - Not only can be used together with example-based tests - Can also test stateful things like view controllers
  • 76. - We can use Fox
  • 77. - We can use Fox
  • 78. state - Model changes in state using transitions - Transitions have corresponding “model state” updates - Specify conditions that must be met after transition
  • 79. state more transition less transition - Model changes in state using transitions - Transitions have corresponding “model state” updates - Specify conditions that must be met after transition
  • 80. state count more transition count + 1 less transition count - 1 - Model changes in state using transitions - Transitions have corresponding “model state” updates - Specify conditions that must be met after transition
  • 81. state count property more transition count + 1 property less transition count - 1 - Model changes in state using transitions - Transitions have corresponding “model state” updates - Specify conditions that must be met after transition
  • 82. initial count = 0 - Fox will run transitions at random, checking that properties hold
  • 83. initial count = 0 more count = 1 property less count = 0 property - Fox will run transitions at random, checking that properties hold
  • 84. initial count = 0 more count = 1 property less count = 0 property more count = 1 more count = 2 - Fox will run transitions at random, checking that properties hold
  • 85. let moreTransition = FOXTransition(action: { state, _ in let controller = state as BananaViewController controller.moreButton.sendActionsForControlEvents( UIControlEvents.TouchUpInside) }, nextModelState: { modelState, _ in return (modelState as Int) + 1 }) - Fox gives us a hook to move into the next state - And we need to update our model state as well
  • 86. let moreTransition = FOXTransition(action: { state, _ in let controller = state as BananaViewController controller.moreButton.sendActionsForControlEvents( UIControlEvents.TouchUpInside) }, nextModelState: { modelState, _ in return (modelState as Int) + 1 }) - Fox gives us a hook to move into the next state - And we need to update our model state as well
  • 87. let moreTransition = FOXTransition(action: { state, _ in let controller = state as BananaViewController controller.moreButton.sendActionsForControlEvents( UIControlEvents.TouchUpInside) }, nextModelState: { modelState, _ in return (modelState as Int) + 1 }) - Fox gives us a hook to move into the next state - And we need to update our model state as well
  • 88. let moreTransition = FOXTransition(action: { state, _ in let controller = state as BananaViewController controller.moreButton.sendActionsForControlEvents( UIControlEvents.TouchUpInside) }, nextModelState: { modelState, _ in return (modelState as Int) + 1 }) - Fox gives us a hook to move into the next state - And we need to update our model state as well
  • 89. let moreTransition = FOXTransition(action: { state, _ in let controller = state as BananaViewController controller.moreButton.sendActionsForControlEvents( UIControlEvents.TouchUpInside) }, nextModelState: { modelState, _ in return (modelState as Int) + 1 }) - Fox gives us a hook to move into the next state - And we need to update our model state as well
  • 90. let moreTransition = FOXTransition(action: { state, _ in let controller = state as BananaViewController controller.moreButton.sendActionsForControlEvents( UIControlEvents.TouchUpInside) }, nextModelState: { modelState, _ in return (modelState as Int) + 1 }) - Fox gives us a hook to move into the next state - And we need to update our model state as well
  • 91. moreTransition.postcondition = { (modelState, _, subject, _, _) in let controller = subject as BananaViewController return modelState as Int == controller.bananaCount } - Post condition (or property) is must be fulfilled after every transition—that view controller displays correct model state - We can only tap more when count < 10
  • 92. moreTransition.postcondition = { (modelState, _, subject, _, _) in let controller = subject as BananaViewController return modelState as Int == controller.bananaCount } - Post condition (or property) is must be fulfilled after every transition—that view controller displays correct model state - We can only tap more when count < 10
  • 93. moreTransition.postcondition = { (modelState, _, subject, _, _) in let controller = subject as BananaViewController return modelState as Int == controller.bananaCount } - Post condition (or property) is must be fulfilled after every transition—that view controller displays correct model state - We can only tap more when count < 10
  • 94. moreTransition.postcondition = { (modelState, _, subject, _, _) in let controller = subject as BananaViewController return modelState as Int == controller.bananaCount } moreTransition.precondition = { modelState in return (modelState as Int) < 10 } - Post condition (or property) is must be fulfilled after every transition—that view controller displays correct model state - We can only tap more when count < 10
  • 95. moreTransition.postcondition = { (modelState, _, subject, _, _) in let controller = subject as BananaViewController return modelState as Int == controller.bananaCount } moreTransition.precondition = { modelState in return (modelState as Int) < 10 } - Post condition (or property) is must be fulfilled after every transition—that view controller displays correct model state - We can only tap more when count < 10
  • 96. let stateMachine = FOXFiniteStateMachine( initialModelState: 0) stateMachine.addTransition(moreTransition) stateMachine.addTransition(lessTransition) let executedCommands = executeCommands(stateMachine) { let storyboard = UIStoryboard(name: "Main", bundle: nil) let controller = storyboard.instantiateInitialViewController() as BananaViewController let _ = controller.view return controller } Fox.Assert(forAll(executedCommands) { commands in return executedSuccessfully(commands as NSArray) }) - Same gist for less transition - Define state machine with initial model state - Provide initial state with executeCommands - Minimum failing case displayed
  • 97. let stateMachine = FOXFiniteStateMachine( initialModelState: 0) stateMachine.addTransition(moreTransition) stateMachine.addTransition(lessTransition) let executedCommands = executeCommands(stateMachine) { let storyboard = UIStoryboard(name: "Main", bundle: nil) let controller = storyboard.instantiateInitialViewController() as BananaViewController let _ = controller.view return controller } Fox.Assert(forAll(executedCommands) { commands in return executedSuccessfully(commands as NSArray) }) - Same gist for less transition - Define state machine with initial model state - Provide initial state with executeCommands - Minimum failing case displayed
  • 98. let stateMachine = FOXFiniteStateMachine( initialModelState: 0) stateMachine.addTransition(moreTransition) stateMachine.addTransition(lessTransition) let executedCommands = executeCommands(stateMachine) { let storyboard = UIStoryboard(name: "Main", bundle: nil) let controller = storyboard.instantiateInitialViewController() as BananaViewController let _ = controller.view return controller } Fox.Assert(forAll(executedCommands) { commands in return executedSuccessfully(commands as NSArray) }) - Same gist for less transition - Define state machine with initial model state - Provide initial state with executeCommands - Minimum failing case displayed
  • 99. let stateMachine = FOXFiniteStateMachine( initialModelState: 0) stateMachine.addTransition(moreTransition) stateMachine.addTransition(lessTransition) let executedCommands = executeCommands(stateMachine) { let storyboard = UIStoryboard(name: "Main", bundle: nil) let controller = storyboard.instantiateInitialViewController() as BananaViewController let _ = controller.view return controller } Fox.Assert(forAll(executedCommands) { commands in return executedSuccessfully(commands as NSArray) }) - Same gist for less transition - Define state machine with initial model state - Provide initial state with executeCommands - Minimum failing case displayed
  • 100. let stateMachine = FOXFiniteStateMachine( initialModelState: 0) stateMachine.addTransition(moreTransition) stateMachine.addTransition(lessTransition) let executedCommands = executeCommands(stateMachine) { let storyboard = UIStoryboard(name: "Main", bundle: nil) let controller = storyboard.instantiateInitialViewController() as BananaViewController let _ = controller.view return controller } Fox.Assert(forAll(executedCommands) { commands in return executedSuccessfully(commands as NSArray) }) - Same gist for less transition - Define state machine with initial model state - Provide initial state with executeCommands - Minimum failing case displayed
  • 101. github.com/jeffh/Fox fox-testing.readthedocs.org/ - That’s Fox in a nutshell; Many more features, see GitHub - Property-based testing is deep, many resources available - Fox will continue to grow, perhaps provide properties
  • 102. functional programming imperative programming - QuickCheck has been around since 1999, only in ObjC/Swift recently - Great example of cross-pollination of ideas, excited for more
  • 103. brian gesiak @modocache - Thanks for listening. Questions?