The document discusses testing approaches for imperative and functional programming. For imperative programming, tests typically follow the arrange-act-assert pattern and set up object state. For functional programming, tests can be simpler since functions are pure without side effects. Property-based testing with frameworks like QuickCheck and Fox is introduced, which generates random test values rather than writing specific tests. This allows the framework to thoroughly test functions by finding edge cases automatically. An example using Fox to test functions from the Archimedes geometry library in Objective-C is provided.
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
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)
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
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
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
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