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

Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationSafe Software
 
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Wonjun Hwang
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Mattias Andersson
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...Fwdays
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Patryk Bandurski
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024The Digital Insurer
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 

Recently uploaded (20)

Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry InnovationBeyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
 
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
Bun (KitWorks Team Study 노별마루 발표 2024.4.22)
 
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?Are Multi-Cloud and Serverless Good or Bad?
Are Multi-Cloud and Serverless Good or Bad?
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks..."LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
"LLMs for Python Engineers: Advanced Data Analysis and Semantic Kernel",Oleks...
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
Integration and Automation in Practice: CI/CD in Mule Integration and Automat...
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024My INSURER PTE LTD - Insurtech Innovation Award 2024
My INSURER PTE LTD - Insurtech Innovation Award 2024
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 

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?