3. 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
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 or not to write, by Paul Taykalo, #MacPaw 6
7. 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
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 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
14. 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
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 there so big
difference?
To write or not to write, by Paul Taykalo, #MacPaw 17
18. Starting Test is hard
To write or not to write, by Paul Taykalo, #MacPaw 18
19. To write or not to write, by Paul Taykalo, #MacPaw 19
20. When do we start
thinking about tests?
To write or not to write, by Paul Taykalo, #MacPaw 20
22. 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
23. 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
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
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
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 we writing
tests?
To write or not to write, by Paul Taykalo, #MacPaw 57
58. 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
59. Some tips for
those who
doesn't write
tests
To write or not to write, by Paul Taykalo, #MacPaw 59
60. Some tips for
those who
doesn't write
tests
To write or not to write, by Paul Taykalo, #MacPaw 60
61. 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
62. 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
63. 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
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 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
66. 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
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
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
72. 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
73. 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
74. Decrease amount of possible
states
» Optionals
» BFO (Object)
» Exponential number of states
» Mutability
To write or not to write, by Paul Taykalo, #MacPaw 74
75. 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
76. More tips for those
who doesn't write
tests
To write or not to write, by Paul Taykalo, #MacPaw 76
77. 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
78. How (not) to fail
adding tests
To write or not to write, by Paul Taykalo, #MacPaw 78
79. 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
81. - 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
83. 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