Why we are (not)
writing tests in iOS
apps
by Paul Taykalo, MacPaw
To write or not to write, by Paul Taykalo, #MacPaw 1
Plan
To write or not to write, by Paul Taykalo, #MacPaw 2
Plan
» Tests vs No "Tests" Fractions
» Tests are not needed
» Need are needed
» I'm trying, but failing
» Aren't we testing already?
» Pivot point
» How not to write tests with success
To write or not to write, by Paul Taykalo, #MacPaw 3
Tests
No-Tests
Some-Tests
Groups
To write or not to write, by Paul Taykalo, #MacPaw 4
Tests arent't needed
» My grandpa hasn't run a test
» Waste of time
» Just write correct code
» No time
» ???
To write or not to write, by Paul Taykalo, #MacPaw 5
To write or not to write, by Paul Taykalo, #MacPaw 6
Test all the things
» It's only the right way to do it
» No tests - no feature
To write or not to write, by Paul Taykalo, #MacPaw 7
Some-Tests
To write or not to write, by Paul Taykalo, #MacPaw 8
Aren't we testing
already?
To write or not to write, by Paul Taykalo, #MacPaw 9
Aren't we testing already?
» Debugging
» Logging
» QAs
» End users
To write or not to write, by Paul Taykalo, #MacPaw 10
Testing is hard
To write or not to write, by Paul Taykalo, #MacPaw 11
Testing is hard
» Big/Small
» Very hard integration
» We don't actually know how
» Boring
» Next time
» Ther's no time atm
To write or not to write, by Paul Taykalo, #MacPaw 12
We aren't writing tests. We were
writing tests, but they were too fragile,
and they were breaking for no reason,
so we decided not to
— Unknown programmer
To write or not to write, by Paul Taykalo, #MacPaw 13
Every time new OS released, all my
snapshot tests are broken. It's not
worth it
— Unknown programmer
To write or not to write, by Paul Taykalo, #MacPaw 14
Testing is easy
To write or not to write, by Paul Taykalo, #MacPaw 15
Testing is easy
» Tests already setup
» Intro/PR/Review
» No commits without Tests ¯_( )_/¯
» It is not fixed unless there's a test
To write or not to write, by Paul Taykalo, #MacPaw 16
Why is there so big
difference?
To write or not to write, by Paul Taykalo, #MacPaw 17
Starting Test is hard
To write or not to write, by Paul Taykalo, #MacPaw 18
To write or not to write, by Paul Taykalo, #MacPaw 19
When do we start
thinking about tests?
To write or not to write, by Paul Taykalo, #MacPaw 20
Slowing down
To write or not to write, by Paul Taykalo, #MacPaw 21
Slowing down
» Not sure everything is fine
» R-word (Regression)
» Fixing bugs time >> Feature time
To write or not to write, by Paul Taykalo, #MacPaw 22
Slowing down
» No knowledge
» Huge base
» Different integrations M*N
» Code base/complexity
» Team change
» Same time expectation
To write or not to write, by Paul Taykalo, #MacPaw 23
We've slowed down
To write or not to write, by Paul Taykalo, #MacPaw 24
What did we
wrong?
To write or not to write, by Paul Taykalo, #MacPaw 25
What did we
wrong?
To write or not to write, by Paul Taykalo, #MacPaw 26
What did we
wrong?
To write or not to write, by Paul Taykalo, #MacPaw 27
What did we
wrong?
To write or not to write, by Paul Taykalo, #MacPaw 28
Time to add tests?
To write or not to write, by Paul Taykalo, #MacPaw 29
Tests
Expectations vs reality
To write or not to write, by Paul Taykalo, #MacPaw 30
Tests
Expectations vs reality
To write or not to write, by Paul Taykalo, #MacPaw 31
I added tests they didn't help
— Unknown programmer
To write or not to write, by Paul Taykalo, #MacPaw 32
Tests are not a cure
Tests are the
insurance
To write or not to write, by Paul Taykalo, #MacPaw 33
I spent weeks trying to test UI and
failed all deadlines
— Unknown programmer
To write or not to write, by Paul Taykalo, #MacPaw 34
Some parts are really
hard to test
Deal with it
To write or not to write, by Paul Taykalo, #MacPaw 35
There are a lot of tests, but the code
doesn't seem to be better
— Unknown programmer
To write or not to write, by Paul Taykalo, #MacPaw 36
Tests won't make your code
significantly better*
To write or not to write, by Paul Taykalo, #MacPaw 37
You still can write ugly code
To write or not to write, by Paul Taykalo, #MacPaw 38
Your tests can be ugly as well
To write or not to write, by Paul Taykalo, #MacPaw 39
The Good, The Bad
and The Ugly
To write or not to write, by Paul Taykalo, #MacPaw 40
The Bad
To write or not to write, by Paul Taykalo, #MacPaw 41
The Bad
func testImtheS() {
let url = "http://agileinaflash.com/feeds/posts/default"
let content = Data(withContensOfURL:URL(string:url))!
XCTAssertNotNil(content)
let parsedContent = parse(content)!
let rss = RSS(from: parsedContent)!
XCTAssertEqual(rss.title, title)
}
To write or not to write, by Paul Taykalo, #MacPaw 42
The Bad
func testImtheS() {
let url = "http://agileinaflash.com/feeds/posts/default"
let content = Data(withContensOfURL:URL(string:url))!
XCTAssertNotNil(content)
let parsedContent = parse(content)!
let rss = RSS(from: parsedContent)!
XCTAssertEqual(rss.title, title)
}
To write or not to write, by Paul Taykalo, #MacPaw 43
The Bad
func testImtheS() {
let url = "http://agileinaflash.com/feeds/posts/default"
let content = Data(withContensOfURL:URL(string:url))!
XCTAssertNotNil(content)
let parsedContent = parse(content)!
let rss = RSS(from: parsedContent)!
XCTAssertEqual(rss.title, title)
}
To write or not to write, by Paul Taykalo, #MacPaw 44
The Bad
func testImtheS() {
let url = "http://agileinaflash.com/feeds/posts/default"
let content = Data(withContensOfURL:URL(string:url))!
XCTAssertNotNil(content)
let parsedContent = parse(content)!
let rss = RSS(from: parsedContent)!
XCTAssertEqual(rss.title, title)
}
To write or not to write, by Paul Taykalo, #MacPaw 45
The Ugly
To write or not to write, by Paul Taykalo, #MacPaw 46
The Ugly
func testEncoding() {
let path = "Foo/Bar/Baz"
let info = ["Key": "Hello World"]
let request1 = XPCRequest(path: path, info: info)
let data1 = request1.encode()
let decoded1 = XPCRequest(with: data1)!
XCTAssertEqual(request1.path, decoded1.path)
XCTAssertTrue(NSDictionary(dictionary: request1.info!).isEqual(to: decoded1.info!))
let request2 = XPCRequest(path: path, info: nil)
let data2 = request2.encode()
let decoded2 = XPCRequest(with: data2)!
XCTAssertEqual(request2.path, decoded2.path)
XCTAssertNil(request2.info)
}
To write or not to write, by Paul Taykalo, #MacPaw 47
The Ugly
func testMenuItemCreated() throws {
let simulatedClickedRow = 7
let simulatedItems = ["Hello", " World!"]
let mapperExpectation = expectation(description: "mapper should be called")
let mapper: ([Any]) -> [String]? = { objects in
mapperExpectation.fulfill()
return objects as? [String]
}
let formatExpectation = expectation(description: "format should be called")
let format: ([String], Int, Localization.Type) -> String? = { strings, clickedRow, localization in
formatExpectation.fulfill()
XCTAssertEqual(strings, simulatedItems)
XCTAssertEqual(clickedRow, simulatedClickedRow)
return strings.reduce("") { $0 + $1 }
}
let action: ([String], Int) -> Void = { _, _ in }
let keyEquivalent: String = "Key"
let builder = MenuItemBuilder<String>(keyEquivalent: keyEquivalent, mapper: mapper, format: format, action: action)
let menuItem = try require(builder.item(from: simulatedItems, with: simulatedClickedRow))
XCTAssertEqual(menuItem.keyEquivalent, keyEquivalent)
XCTAssertEqual(menuItem.title, "Hello World!")
waitForExpectations(timeout: 0.0) { error in
XCTAssertFalse(error != nil)
}
}
To write or not to write, by Paul Taykalo, #MacPaw 48
The Good
To write or not to write, by Paul Taykalo, #MacPaw 49
F. I. R. S. T.
To write or not to write, by Paul Taykalo, #MacPaw 50
F. I. R. S. T.
» Fast — tests should be able to be executed often.
» Isolated — tests on their own cannot depend on external
factors or on the result of another test.
» Repeatable — tests should have the same result every time we
run them.(*)
» Self-verifying — tests should include assertions; no human
intervention needed.
» Timely — tests should be written along with the production
To write or not to write, by Paul Taykalo, #MacPaw 51
The Good
- (void)testAddMalwareInformationCanBeReadFromDataBase {
// Given
TestMalwareInfo info = [self.knowledgeBase _addMalwareInfo];
// When
NSDictionary *malwareInfo = [self.knowledgeBase malwareInfoForItem:nil hash:info.hash];
// Then
XCTAssertEqualObjects(malwareInfo[CMMalwareInfoKeyName], info.name);
XCTAssertEqualObjects(malwareInfo[CMMalwareInfoKeyType], info.type);
}
To write or not to write, by Paul Taykalo, #MacPaw 52
The Good
- (void)testMalwareDetectionTasksReturnsMalwareModels {
// Given
MPFileSizerMockWithStubbedAnyPath(^(MPFileSize size) {
CMMalwaresDetectionTask * sut = [self _sutWithItemsCount:3 malwaresCount:2];
// When
CMScanResult *result = [sut scan];
CMEntity *entity = [result.items firstObject];
// Then
XCTAssertTrue([entity isKindOfClass:[CMMalwareModel class]]);
});}
To write or not to write, by Paul Taykalo, #MacPaw 53
The Good
- (void)testMalwareDetectionTasksReturnsMalwareModels {
// Given
MPFileSizerMockWithStubbedAnyPath(^(MPFileSize size) {
CMMalwaresDetectionTask * sut = [self _sutWithItemsCount:3 malwaresCount:2];
// When
CMScanResult *result = [sut scan];
CMEntity *entity = [result.items firstObject];
// Then
XCTAssertTrue([entity isKindOfClass:[CMMalwareModel class]]);
});}
To write or not to write, by Paul Taykalo, #MacPaw 54
The Good
- (void)testMalwareDetectionTasksReturnsMalwareModels {
// Given
MPFileSizerMockWithStubbedAnyPath(^(MPFileSize size) {
CMMalwaresDetectionTask * sut = [self _sutWithItemsCount:3 malwaresCount:2];
// When
CMScanResult *result = [sut scan];
CMEntity *entity = [result.items firstObject];
// Then
XCTAssertTrue([entity isKindOfClass:[CMMalwareModel class]]);
});}
To write or not to write, by Paul Taykalo, #MacPaw 55
The Good
- (void)testMalwareDetectionTasksReturnsMalwareModels {
// Given
MPFileSizerMockWithStubbedAnyPath(^(MPFileSize size) {
CMMalwaresDetectionTask * sut = [self _sutWithItemsCount:3 malwaresCount:2];
// When
CMScanResult *result = [sut scan];
CMEntity *entity = [result.items firstObject];
// Then
XCTAssertTrue([entity isKindOfClass:[CMMalwareModel class]]);
});}
To write or not to write, by Paul Taykalo, #MacPaw 56
Why aren't we writing
tests?
To write or not to write, by Paul Taykalo, #MacPaw 57
Why aren't we writing tests?
» Too small (project)
» Simple project
» Clear code
» No complaints from product owner/users
To write or not to write, by Paul Taykalo, #MacPaw 58
Some tips for
those who
doesn't write
tests
To write or not to write, by Paul Taykalo, #MacPaw 59
Some tips for
those who
doesn't write
tests
To write or not to write, by Paul Taykalo, #MacPaw 60
Don't show/require all data
YAGNI
struct User {
let id: String
let name: String
let address: String
let friends: [User]
let dogName: String?
let hairStyle: HairStyle
...
}
To write or not to write, by Paul Taykalo, #MacPaw 61
Own Data layer
Layer things out
class AppApi {
func getUser(by id: String)
-> SignalProducer<User, AppApiError>
}
To write or not to write, by Paul Taykalo, #MacPaw 62
Layer things out
enum AppApiError: Error {
case userNotLoggedIn
case serverFeelsBad
case subscriptionEnded
case unknown(NetworkError) //
}
To write or not to write, by Paul Taykalo, #MacPaw 63
Layer things out
// Struct for showing error imeediately to the user
struct UserError {
let title: String
let message: String
// just in some really rare cases
let underlyingError: Error
}
To write or not to write, by Paul Taykalo, #MacPaw 64
Don't allow invalid data
struct UserForm {
let id: String?
let name: String?
let lastName: String?
}
struct User {
let id: String
let name: String
let lastName: String
}
To write or not to write, by Paul Taykalo, #MacPaw 65
Isolate Order Dependent Code
dataSource.addItems(["1","2","3")
tableView.insertItems(at indexPaths:[IndexPath])
tableView.deleteItems(at indexPaths:[])
tableView.reloadData()
To write or not to write, by Paul Taykalo, #MacPaw 66
Semantic meaning
if itemsCount > 4
if user.rating > 200 && user.userName.isNotEmpty
if view.tag == 13
To write or not to write, by Paul Taykalo, #MacPaw 67
Unidirectional data
flow
To write or not to write, by Paul Taykalo, #MacPaw 68
Linear code
loginUser(onSuccess: { user in
getAllImages(for: user, onSuccess: { images is
verify(images: images, onSuccess: { verified, unverified is
verifyDeletion(images: unverified, onSuccess: { shouldDelete in
},
onFailure: {
// TODO Handle it
print(error)
})
},
onFailure: {
// TODO Handle it
print(error)
})
},
onFailure: {
// TODO Handle it
print(error)
})
},
onFailure: {
// TODO Handle it
print(error)
})
To write or not to write, by Paul Taykalo, #MacPaw 69
Linear code
loggeduserRequest
.flatMap(.latest, { user in getAllimages(for: user)})
.flatMap(.latest, { images in verify(images: images)})
.flatMap { verified, unverified in askForDeletion(images: unverified)}
.filter { shouldDelete in deleteImages(shouldDelete) }
To write or not to write, by Paul Taykalo, #MacPaw 70
Linear code
loggeduserRequest
.flatMap(.latest, getAllimages)
.flatMap(.latest, verify)
.flatMap { verified, unverified in askForDeletion(images: unverified)}
.filter(deleteImages)
To write or not to write, by Paul Taykalo, #MacPaw 71
Non failable code
file.open()
guard something else { file.close(); return}
file.read()
file.close()
To write or not to write, by Paul Taykalo, #MacPaw 72
Non failable code
extension File {
func opened(process: (File -> Void)) {
open()
process(self)
close();
}
}
file.opened { f in
guard something else { return }
f.read()
}
To write or not to write, by Paul Taykalo, #MacPaw 73
Decrease amount of possible
states
» Optionals
» BFO (Object)
» Exponential number of states
» Mutability
To write or not to write, by Paul Taykalo, #MacPaw 74
Some tips for those who doesn't
write tests
» Don't show/require all data
» Layer things out
» Don't allow invalid data
» Semantic meaning
» Linear flow
» Decrease amount of possible states
To write or not to write, by Paul Taykalo, #MacPaw 75
More tips for those
who doesn't write
tests
To write or not to write, by Paul Taykalo, #MacPaw 76
More tips for those who doesn't
write tests
» Visual snapshots
» Revealing hidden info (i.e. Analytics)
» Logs
To write or not to write, by Paul Taykalo, #MacPaw 77
How (not) to fail
adding tests
To write or not to write, by Paul Taykalo, #MacPaw 78
How (not) to fail adding tests
» Read a lot about testing and types of testing
» Try to test-first, try to test-last
» Determine your core
» Isolate features and test them
» Start with Unit tests
» For Custom UI you can run snapshot testing
» Hire someone and let them help you
To write or not to write, by Paul Taykalo, #MacPaw 79
Te$t$
To write or not to write, by Paul Taykalo, #MacPaw 80
- I won't pay additionally for tests
- But there'll be bugs
- They'll be there even if you write tests!
To write or not to write, by Paul Taykalo, #MacPaw 81
Tests readiness
To write or not to write, by Paul Taykalo, #MacPaw 82
There are many ways to write an
application. We're not building
spaceships, you know?
— Unknown programmer
To write or not to write, by Paul Taykalo, #MacPaw 83
Thank you
To write or not to write, by Paul Taykalo, #MacPaw 84
Q & A
To write or not to write, by Paul Taykalo, #MacPaw 85
Why we are (not)
writing tests in iOS
apps
by Paul Taykalo, MacPaw
@TT_Kilew
To write or not to write, by Paul Taykalo, #MacPaw 86
The end
To write or not to write, by Paul Taykalo, #MacPaw 87
Bonus
Building a Swift Quiz App with TDD
and Modular Design
https://goo.gl/cUzTkr
To write or not to write, by Paul Taykalo, #MacPaw 88
Links
http://merowing.info/2017/01/testing-ios-apps/
https://www.swiftbysundell.com/posts/testing-swift-code-
that-uses-system-singletons-in-3-easy-steps?
utmcontent=buffer1765f&utmmedium=social&utmsource=twitter.co
m&utmcampaign=buffer
https://www.joelonsoftware.com/2001/07/31/hard-assed-bug-
fixin/
https://www.objc.io/issues/15-testing/bad-testing-practices/
To write or not to write, by Paul Taykalo, #MacPaw 89
More links
https://medium.com/essential-developer-ios
To write or not to write, by Paul Taykalo, #MacPaw 90

Is it time to write unit tests?

  • 1.
    Why we are(not) writing tests in iOS apps by Paul Taykalo, MacPaw To write or not to write, by Paul Taykalo, #MacPaw 1
  • 2.
    Plan To write ornot to write, by Paul Taykalo, #MacPaw 2
  • 3.
    Plan » Tests vsNo "Tests" Fractions » Tests are not needed » Need are needed » I'm trying, but failing » Aren't we testing already? » Pivot point » How not to write tests with success To write or not to write, by Paul Taykalo, #MacPaw 3
  • 4.
    Tests No-Tests Some-Tests Groups To write ornot to write, by Paul Taykalo, #MacPaw 4
  • 5.
    Tests arent't needed »My grandpa hasn't run a test » Waste of time » Just write correct code » No time » ??? To write or not to write, by Paul Taykalo, #MacPaw 5
  • 6.
    To write ornot to write, by Paul Taykalo, #MacPaw 6
  • 7.
    Test all thethings » It's only the right way to do it » No tests - no feature To write or not to write, by Paul Taykalo, #MacPaw 7
  • 8.
    Some-Tests To write ornot to write, by Paul Taykalo, #MacPaw 8
  • 9.
    Aren't we testing already? Towrite or not to write, by Paul Taykalo, #MacPaw 9
  • 10.
    Aren't we testingalready? » Debugging » Logging » QAs » End users To write or not to write, by Paul Taykalo, #MacPaw 10
  • 11.
    Testing is hard Towrite or not to write, by Paul Taykalo, #MacPaw 11
  • 12.
    Testing is hard »Big/Small » Very hard integration » We don't actually know how » Boring » Next time » Ther's no time atm To write or not to write, by Paul Taykalo, #MacPaw 12
  • 13.
    We aren't writingtests. We were writing tests, but they were too fragile, and they were breaking for no reason, so we decided not to — Unknown programmer To write or not to write, by Paul Taykalo, #MacPaw 13
  • 14.
    Every time newOS released, all my snapshot tests are broken. It's not worth it — Unknown programmer To write or not to write, by Paul Taykalo, #MacPaw 14
  • 15.
    Testing is easy Towrite or not to write, by Paul Taykalo, #MacPaw 15
  • 16.
    Testing is easy »Tests already setup » Intro/PR/Review » No commits without Tests ¯_( )_/¯ » It is not fixed unless there's a test To write or not to write, by Paul Taykalo, #MacPaw 16
  • 17.
    Why is thereso big difference? To write or not to write, by Paul Taykalo, #MacPaw 17
  • 18.
    Starting Test ishard To write or not to write, by Paul Taykalo, #MacPaw 18
  • 19.
    To write ornot to write, by Paul Taykalo, #MacPaw 19
  • 20.
    When do westart thinking about tests? To write or not to write, by Paul Taykalo, #MacPaw 20
  • 21.
    Slowing down To writeor not to write, by Paul Taykalo, #MacPaw 21
  • 22.
    Slowing down » Notsure everything is fine » R-word (Regression) » Fixing bugs time >> Feature time To write or not to write, by Paul Taykalo, #MacPaw 22
  • 23.
    Slowing down » Noknowledge » Huge base » Different integrations M*N » Code base/complexity » Team change » Same time expectation To write or not to write, by Paul Taykalo, #MacPaw 23
  • 24.
    We've slowed down Towrite or not to write, by Paul Taykalo, #MacPaw 24
  • 25.
    What did we wrong? Towrite or not to write, by Paul Taykalo, #MacPaw 25
  • 26.
    What did we wrong? Towrite or not to write, by Paul Taykalo, #MacPaw 26
  • 27.
    What did we wrong? Towrite or not to write, by Paul Taykalo, #MacPaw 27
  • 28.
    What did we wrong? Towrite or not to write, by Paul Taykalo, #MacPaw 28
  • 29.
    Time to addtests? To write or not to write, by Paul Taykalo, #MacPaw 29
  • 30.
    Tests Expectations vs reality Towrite or not to write, by Paul Taykalo, #MacPaw 30
  • 31.
    Tests Expectations vs reality Towrite or not to write, by Paul Taykalo, #MacPaw 31
  • 32.
    I added teststhey didn't help — Unknown programmer To write or not to write, by Paul Taykalo, #MacPaw 32
  • 33.
    Tests are nota cure Tests are the insurance To write or not to write, by Paul Taykalo, #MacPaw 33
  • 34.
    I spent weekstrying to test UI and failed all deadlines — Unknown programmer To write or not to write, by Paul Taykalo, #MacPaw 34
  • 35.
    Some parts arereally hard to test Deal with it To write or not to write, by Paul Taykalo, #MacPaw 35
  • 36.
    There are alot of tests, but the code doesn't seem to be better — Unknown programmer To write or not to write, by Paul Taykalo, #MacPaw 36
  • 37.
    Tests won't makeyour code significantly better* To write or not to write, by Paul Taykalo, #MacPaw 37
  • 38.
    You still canwrite ugly code To write or not to write, by Paul Taykalo, #MacPaw 38
  • 39.
    Your tests canbe ugly as well To write or not to write, by Paul Taykalo, #MacPaw 39
  • 40.
    The Good, TheBad and The Ugly To write or not to write, by Paul Taykalo, #MacPaw 40
  • 41.
    The Bad To writeor not to write, by Paul Taykalo, #MacPaw 41
  • 42.
    The Bad func testImtheS(){ let url = "http://agileinaflash.com/feeds/posts/default" let content = Data(withContensOfURL:URL(string:url))! XCTAssertNotNil(content) let parsedContent = parse(content)! let rss = RSS(from: parsedContent)! XCTAssertEqual(rss.title, title) } To write or not to write, by Paul Taykalo, #MacPaw 42
  • 43.
    The Bad func testImtheS(){ let url = "http://agileinaflash.com/feeds/posts/default" let content = Data(withContensOfURL:URL(string:url))! XCTAssertNotNil(content) let parsedContent = parse(content)! let rss = RSS(from: parsedContent)! XCTAssertEqual(rss.title, title) } To write or not to write, by Paul Taykalo, #MacPaw 43
  • 44.
    The Bad func testImtheS(){ let url = "http://agileinaflash.com/feeds/posts/default" let content = Data(withContensOfURL:URL(string:url))! XCTAssertNotNil(content) let parsedContent = parse(content)! let rss = RSS(from: parsedContent)! XCTAssertEqual(rss.title, title) } To write or not to write, by Paul Taykalo, #MacPaw 44
  • 45.
    The Bad func testImtheS(){ let url = "http://agileinaflash.com/feeds/posts/default" let content = Data(withContensOfURL:URL(string:url))! XCTAssertNotNil(content) let parsedContent = parse(content)! let rss = RSS(from: parsedContent)! XCTAssertEqual(rss.title, title) } To write or not to write, by Paul Taykalo, #MacPaw 45
  • 46.
    The Ugly To writeor not to write, by Paul Taykalo, #MacPaw 46
  • 47.
    The Ugly func testEncoding(){ let path = "Foo/Bar/Baz" let info = ["Key": "Hello World"] let request1 = XPCRequest(path: path, info: info) let data1 = request1.encode() let decoded1 = XPCRequest(with: data1)! XCTAssertEqual(request1.path, decoded1.path) XCTAssertTrue(NSDictionary(dictionary: request1.info!).isEqual(to: decoded1.info!)) let request2 = XPCRequest(path: path, info: nil) let data2 = request2.encode() let decoded2 = XPCRequest(with: data2)! XCTAssertEqual(request2.path, decoded2.path) XCTAssertNil(request2.info) } To write or not to write, by Paul Taykalo, #MacPaw 47
  • 48.
    The Ugly func testMenuItemCreated()throws { let simulatedClickedRow = 7 let simulatedItems = ["Hello", " World!"] let mapperExpectation = expectation(description: "mapper should be called") let mapper: ([Any]) -> [String]? = { objects in mapperExpectation.fulfill() return objects as? [String] } let formatExpectation = expectation(description: "format should be called") let format: ([String], Int, Localization.Type) -> String? = { strings, clickedRow, localization in formatExpectation.fulfill() XCTAssertEqual(strings, simulatedItems) XCTAssertEqual(clickedRow, simulatedClickedRow) return strings.reduce("") { $0 + $1 } } let action: ([String], Int) -> Void = { _, _ in } let keyEquivalent: String = "Key" let builder = MenuItemBuilder<String>(keyEquivalent: keyEquivalent, mapper: mapper, format: format, action: action) let menuItem = try require(builder.item(from: simulatedItems, with: simulatedClickedRow)) XCTAssertEqual(menuItem.keyEquivalent, keyEquivalent) XCTAssertEqual(menuItem.title, "Hello World!") waitForExpectations(timeout: 0.0) { error in XCTAssertFalse(error != nil) } } To write or not to write, by Paul Taykalo, #MacPaw 48
  • 49.
    The Good To writeor not to write, by Paul Taykalo, #MacPaw 49
  • 50.
    F. I. R.S. T. To write or not to write, by Paul Taykalo, #MacPaw 50
  • 51.
    F. I. R.S. T. » Fast — tests should be able to be executed often. » Isolated — tests on their own cannot depend on external factors or on the result of another test. » Repeatable — tests should have the same result every time we run them.(*) » Self-verifying — tests should include assertions; no human intervention needed. » Timely — tests should be written along with the production To write or not to write, by Paul Taykalo, #MacPaw 51
  • 52.
    The Good - (void)testAddMalwareInformationCanBeReadFromDataBase{ // Given TestMalwareInfo info = [self.knowledgeBase _addMalwareInfo]; // When NSDictionary *malwareInfo = [self.knowledgeBase malwareInfoForItem:nil hash:info.hash]; // Then XCTAssertEqualObjects(malwareInfo[CMMalwareInfoKeyName], info.name); XCTAssertEqualObjects(malwareInfo[CMMalwareInfoKeyType], info.type); } To write or not to write, by Paul Taykalo, #MacPaw 52
  • 53.
    The Good - (void)testMalwareDetectionTasksReturnsMalwareModels{ // Given MPFileSizerMockWithStubbedAnyPath(^(MPFileSize size) { CMMalwaresDetectionTask * sut = [self _sutWithItemsCount:3 malwaresCount:2]; // When CMScanResult *result = [sut scan]; CMEntity *entity = [result.items firstObject]; // Then XCTAssertTrue([entity isKindOfClass:[CMMalwareModel class]]); });} To write or not to write, by Paul Taykalo, #MacPaw 53
  • 54.
    The Good - (void)testMalwareDetectionTasksReturnsMalwareModels{ // Given MPFileSizerMockWithStubbedAnyPath(^(MPFileSize size) { CMMalwaresDetectionTask * sut = [self _sutWithItemsCount:3 malwaresCount:2]; // When CMScanResult *result = [sut scan]; CMEntity *entity = [result.items firstObject]; // Then XCTAssertTrue([entity isKindOfClass:[CMMalwareModel class]]); });} To write or not to write, by Paul Taykalo, #MacPaw 54
  • 55.
    The Good - (void)testMalwareDetectionTasksReturnsMalwareModels{ // Given MPFileSizerMockWithStubbedAnyPath(^(MPFileSize size) { CMMalwaresDetectionTask * sut = [self _sutWithItemsCount:3 malwaresCount:2]; // When CMScanResult *result = [sut scan]; CMEntity *entity = [result.items firstObject]; // Then XCTAssertTrue([entity isKindOfClass:[CMMalwareModel class]]); });} To write or not to write, by Paul Taykalo, #MacPaw 55
  • 56.
    The Good - (void)testMalwareDetectionTasksReturnsMalwareModels{ // Given MPFileSizerMockWithStubbedAnyPath(^(MPFileSize size) { CMMalwaresDetectionTask * sut = [self _sutWithItemsCount:3 malwaresCount:2]; // When CMScanResult *result = [sut scan]; CMEntity *entity = [result.items firstObject]; // Then XCTAssertTrue([entity isKindOfClass:[CMMalwareModel class]]); });} To write or not to write, by Paul Taykalo, #MacPaw 56
  • 57.
    Why aren't wewriting tests? To write or not to write, by Paul Taykalo, #MacPaw 57
  • 58.
    Why aren't wewriting tests? » Too small (project) » Simple project » Clear code » No complaints from product owner/users To write or not to write, by Paul Taykalo, #MacPaw 58
  • 59.
    Some tips for thosewho doesn't write tests To write or not to write, by Paul Taykalo, #MacPaw 59
  • 60.
    Some tips for thosewho doesn't write tests To write or not to write, by Paul Taykalo, #MacPaw 60
  • 61.
    Don't show/require alldata YAGNI struct User { let id: String let name: String let address: String let friends: [User] let dogName: String? let hairStyle: HairStyle ... } To write or not to write, by Paul Taykalo, #MacPaw 61
  • 62.
    Own Data layer Layerthings out class AppApi { func getUser(by id: String) -> SignalProducer<User, AppApiError> } To write or not to write, by Paul Taykalo, #MacPaw 62
  • 63.
    Layer things out enumAppApiError: Error { case userNotLoggedIn case serverFeelsBad case subscriptionEnded case unknown(NetworkError) // } To write or not to write, by Paul Taykalo, #MacPaw 63
  • 64.
    Layer things out //Struct for showing error imeediately to the user struct UserError { let title: String let message: String // just in some really rare cases let underlyingError: Error } To write or not to write, by Paul Taykalo, #MacPaw 64
  • 65.
    Don't allow invaliddata struct UserForm { let id: String? let name: String? let lastName: String? } struct User { let id: String let name: String let lastName: String } To write or not to write, by Paul Taykalo, #MacPaw 65
  • 66.
    Isolate Order DependentCode dataSource.addItems(["1","2","3") tableView.insertItems(at indexPaths:[IndexPath]) tableView.deleteItems(at indexPaths:[]) tableView.reloadData() To write or not to write, by Paul Taykalo, #MacPaw 66
  • 67.
    Semantic meaning if itemsCount> 4 if user.rating > 200 && user.userName.isNotEmpty if view.tag == 13 To write or not to write, by Paul Taykalo, #MacPaw 67
  • 68.
    Unidirectional data flow To writeor not to write, by Paul Taykalo, #MacPaw 68
  • 69.
    Linear code loginUser(onSuccess: {user in getAllImages(for: user, onSuccess: { images is verify(images: images, onSuccess: { verified, unverified is verifyDeletion(images: unverified, onSuccess: { shouldDelete in }, onFailure: { // TODO Handle it print(error) }) }, onFailure: { // TODO Handle it print(error) }) }, onFailure: { // TODO Handle it print(error) }) }, onFailure: { // TODO Handle it print(error) }) To write or not to write, by Paul Taykalo, #MacPaw 69
  • 70.
    Linear code loggeduserRequest .flatMap(.latest, {user in getAllimages(for: user)}) .flatMap(.latest, { images in verify(images: images)}) .flatMap { verified, unverified in askForDeletion(images: unverified)} .filter { shouldDelete in deleteImages(shouldDelete) } To write or not to write, by Paul Taykalo, #MacPaw 70
  • 71.
    Linear code loggeduserRequest .flatMap(.latest, getAllimages) .flatMap(.latest,verify) .flatMap { verified, unverified in askForDeletion(images: unverified)} .filter(deleteImages) To write or not to write, by Paul Taykalo, #MacPaw 71
  • 72.
    Non failable code file.open() guardsomething else { file.close(); return} file.read() file.close() To write or not to write, by Paul Taykalo, #MacPaw 72
  • 73.
    Non failable code extensionFile { func opened(process: (File -> Void)) { open() process(self) close(); } } file.opened { f in guard something else { return } f.read() } To write or not to write, by Paul Taykalo, #MacPaw 73
  • 74.
    Decrease amount ofpossible states » Optionals » BFO (Object) » Exponential number of states » Mutability To write or not to write, by Paul Taykalo, #MacPaw 74
  • 75.
    Some tips forthose who doesn't write tests » Don't show/require all data » Layer things out » Don't allow invalid data » Semantic meaning » Linear flow » Decrease amount of possible states To write or not to write, by Paul Taykalo, #MacPaw 75
  • 76.
    More tips forthose who doesn't write tests To write or not to write, by Paul Taykalo, #MacPaw 76
  • 77.
    More tips forthose who doesn't write tests » Visual snapshots » Revealing hidden info (i.e. Analytics) » Logs To write or not to write, by Paul Taykalo, #MacPaw 77
  • 78.
    How (not) tofail adding tests To write or not to write, by Paul Taykalo, #MacPaw 78
  • 79.
    How (not) tofail adding tests » Read a lot about testing and types of testing » Try to test-first, try to test-last » Determine your core » Isolate features and test them » Start with Unit tests » For Custom UI you can run snapshot testing » Hire someone and let them help you To write or not to write, by Paul Taykalo, #MacPaw 79
  • 80.
    Te$t$ To write ornot to write, by Paul Taykalo, #MacPaw 80
  • 81.
    - I won'tpay additionally for tests - But there'll be bugs - They'll be there even if you write tests! To write or not to write, by Paul Taykalo, #MacPaw 81
  • 82.
    Tests readiness To writeor not to write, by Paul Taykalo, #MacPaw 82
  • 83.
    There are manyways to write an application. We're not building spaceships, you know? — Unknown programmer To write or not to write, by Paul Taykalo, #MacPaw 83
  • 84.
    Thank you To writeor not to write, by Paul Taykalo, #MacPaw 84
  • 85.
    Q & A Towrite or not to write, by Paul Taykalo, #MacPaw 85
  • 86.
    Why we are(not) writing tests in iOS apps by Paul Taykalo, MacPaw @TT_Kilew To write or not to write, by Paul Taykalo, #MacPaw 86
  • 87.
    The end To writeor not to write, by Paul Taykalo, #MacPaw 87
  • 88.
    Bonus Building a SwiftQuiz App with TDD and Modular Design https://goo.gl/cUzTkr To write or not to write, by Paul Taykalo, #MacPaw 88
  • 89.
  • 90.
    More links https://medium.com/essential-developer-ios To writeor not to write, by Paul Taykalo, #MacPaw 90