SlideShare a Scribd company logo
1 of 88
Download to read offline
Taking the boilerplate out of
your tests with Sourcery
Vincent Pradeilles (@v_pradeilles) – Worldline
!
2
3
import XCTest
class MyTests: XCTestCase {
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
}
4
5
!
6
Sourcery 101
7
What is Sourcery?
8
What is Sourcery?
"Sourcery is a code generator for the Swift language,
built on top of Apple's own SourceKit. It extends the
language abstractions to allow you to generate
boilerplate code automatically."1
1 
https://github.com/krzysztofzablocki/Sourcery/blob/master/README.md
9
What can we do with Sourcery?
10
What can we do with Sourcery?
enum Direction {
case up
case right
case down
case left
}
Direction.allCases // [.up, .right, .down, .left]
11
What can we do with Sourcery?
Since Swift 4.2, this can be achieved through the
CaseIterable protocol.
But before, generating .allCases was a great use case
to illustrate how Sourcery works.
12
Let's implement it
13
1st – Setting Up Sourcery
14
Setting Up Sourcery
$ brew install sourcery
15
Setting Up Sourcery
Xcode Project > Build Phases > New Run Script Phase
sourcery 
--sources <sources path> 
--templates <templates path> 
--output <output path>
16
2nd – Phantom Protocol
17
Phantom Protocol
protocol EnumIterable { }
extension Direction: EnumIterable { }
18
3rd – Sourcery Template
19
Sourcery Template
{% for enum in types.implementing.EnumIterable|enum %}
{% if not enum.hasAssociatedValues %}
{{ enum.accessLevel }} extension {{ enum.name }} {
static let allCases: [{{ enum.name }}] = [
{% for case in enum.cases %}
.{{ case.name }} {% if not forloop.last %} , {% endif %}
{% endfor %}
]
}
{% endif %}
{% endfor %}
20
4th - Generated Code
21
Generated Code
Build your target
22
Generated Code
internal extension Direction {
static let allCases: [Direction] = [
.up ,
.right ,
.down ,
.left
]
}
23
Generated Code
Add the generated file to your project
That's it
!
24
To Sum Up
25
To Sum Up
→ Sourcery parses your source code
→ It then uses it to execute templates
→ Those templates generate new source code
→ Your project can use this generated code
26
End of
Sourcery 101
27
Back to
writing tests
28
func testEquality() {
let personA = Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true)
let personB = Person(firstName: "Charlie", lastName: "Webb", age: 11, hasDriverLicense: false, isAmerican: true)
XCTAssertEqual(personA, personB)
}
29
!
30
A human-readable diff would be nice
31
Incorrect age: expected 10, received 11
32
Diffing methods are a perfect example of boilerplate
33
internal extension Person {
func diff(against other: Person) -> String {
var result = [String]()
if self.firstName != other.firstName {
var diff = "Incorrect firstName: "
diff += "expected (self.firstName), "
diff += "received (other.firstName)"
result.append(diff)
}
if self.lastName != other.lastName {
var diff = "Incorrect lastName: "
diff += "expected (self.lastName), "
diff += "received (other.lastName)"
result.append(diff)
}
if self.age != other.age {
var diff = "Incorrect age: "
diff += "expected (self.age), "
diff += "received (other.age)"
result.append(diff)
}
if self.hasDriverLicense != other.hasDriverLicense {
var diff = "Incorrect hasDriverLicense: "
diff += "expected (self.hasDriverLicense), "
diff += "received (other.hasDriverLicense)"
result.append(diff)
}
if self.isAmerican != other.isAmerican {
var diff = "Incorrect isAmerican: "
diff += "expected (self.isAmerican), "
diff += "received (other.isAmerican)"
result.append(diff)
}
return result.joined(separator: ". ")
}
}
34
How about we use Sourcery to generate it?
35
1st – Phantom Protocol
36
protocol Diffable { }
extension Person: Diffable { }
37
2nd – Sourcery Template
38
{% for type in types.implementing.Diffable %}
{{ type.accessLevel }} extension {{ type.name }} {
func diff(against other: {{ type.name }}) -> String {
var result = [String]()
{% for variable in type.variables %}
if self.{{ variable.name }} != other.{{ variable.name }} {
var diff = "Incorrect {{ variable.name }}: "
diff += "expected (self.{{ variable.name }}), "
diff += "received (other.{{ variable.name }})"
result.append(diff)
}
{% endfor %}
return result.joined(separator: ". ")
}
}
{% endfor %}
39
3rd – Generated Code
40
internal extension Person {
func diff(against other: Person) -> String {
var result = [String]()
if self.firstName != other.firstName {
var diff = "Incorrect firstName: "
diff += "expected (self.firstName), "
diff += "received (other.firstName)"
result.append(diff)
}
if self.lastName != other.lastName {
var diff = "Incorrect lastName: "
diff += "expected (self.lastName), "
diff += "received (other.lastName)"
result.append(diff)
}
if self.age != other.age {
var diff = "Incorrect age: "
diff += "expected (self.age), "
diff += "received (other.age)"
result.append(diff)
}
if self.hasDriverLicense != other.hasDriverLicense {
var diff = "Incorrect hasDriverLicense: "
diff += "expected (self.hasDriverLicense), "
diff += "received (other.hasDriverLicense)"
result.append(diff)
}
if self.isAmerican != other.isAmerican {
var diff = "Incorrect isAmerican: "
diff += "expected (self.isAmerican), "
diff += "received (other.isAmerican)"
result.append(diff)
}
return result.joined(separator: ". ")
}
}
41
4th – Updated Tests
42
let personA = Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true)
let personB = Person(firstName: "Charlie", lastName: "Webb", age: 11, hasDriverLicense: false, isAmerican: true)
XCTAssertEqual(personA, personB, personA.diff(against: personB))
43
!
44
How about we craft more complex tools?
45
Classic MVVM Architecture
protocol UserService {
func fetchUserName() -> String
}
class ViewModel {
var userNameUpdated: ((String) -> Void)?
private let service: UserService
init(service: UserService) {
self.service = service
}
func fetchData() {
let userName = self.service.fetchUserName()
self.userNameUpdated?(userName)
}
}
46
How do we test a component with dependencies?
Obvious, just inject mocked dependencies!
47
How do we write mocked dependencies?
How about we just don't? (And let Sourcery do it)
48
Generating Mocked Implementations
49
1st – Phantom Protocol
50
protocol MockedImplementation { }
protocol UserService: MockedImplementation {
func fetchUserName() -> String
}
51
2nd – Sourcery Template
52
{% for protocol in types.implementing.MockedImplementation|protocol %}
{{ protocol.accessLevel }} class Mocked{{ protocol.name }}: {{ protocol.name }} {
{% for method in protocol.methods %}
var {{ method.callName }}CallCounter: Int = 0
var {{ method.callName }}ReturnValue: {{ method.returnTypeName }}?
func {{ method.name }} -> {{ method.returnTypeName }} {
{{ method.callName }}CallCounter += 1
return {{ method.callName }}ReturnValue!
}
{% endfor %}
}
{% endfor %}
53
3rd – Generated Code
54
internal class MockedUserService: UserService {
var fetchUserNameCallCounter: Int = 0
var fetchUserNameReturnValue: String?
func fetchUserName() -> String {
fetchUserNameCallCounter += 1
return fetchUserNameReturnValue!
}
}
55
4th – Writing Tests
56
class ViewModelTests: XCTestCase {
func testUserServiceCalls() {
let mockedService = MockedUserService()
let viewModel = ViewModel(service: mockedService)
mockedService.fetchUserNameReturnValue = "John Appleseed"
viewModel.fetchData()
XCTAssertEqual(mockedService.fetchUserNameCallCounter, 1)
}
}
57
No more boilerplate
58
No more boilerplate
Now we only focus on writing tests for the business logic
Of course, there's a lot more features we could add:
→ variables to store arguments
→ calling completion handlers
→ dealing with throwing functions
→ etc.
59
No more boilerplate
Sourcery actually ships with a template that takes
care of all those needs: AutoMockable
(But beware, it is MUCH harder to understand )
60
We are now able to generate tools for testing...
61
...but there's still room to go even further!
62
Testing Dependency Injection
63
Dependency Injection
Many apps rely on Swinject to provide the
architectural basis of dependency injection.
64
Dependency Injection
import Swinject
class ViewModelAssembly: Assembly {
func assemble(container: Container) {
container.register(UserService.self) { _ in return ImplUserService() }
container.register(ViewModel.self) { resolver in
let service = resolver.resolve(UserService.self)!
return ViewModel(service: service)
}
}
}
65
Dependency Injection
class GreetingsViewControllerAssembly: Assembly {
func assemble(container: Container) {
container.register(GreetingsViewController.self) { resolver in
let viewModel = resolver.resolve(ViewModel.self)!
let viewController = UIStoryboard(name: "Main", bundle: nil)
.instantiateViewController(withIdentifier: "GreetingsViewController")
as! GreetingsViewController
viewController.viewModel = viewModel
return viewController
}
}
}
66
Dependency Injection
struct ViewControllerFactory {
static var greetingsVC: GreetingsViewController {
let assembler = Assembler([ViewModelAssembly(), GreetingsViewControllerAssembly()])
return assembler.resolver.resolve(GreetingsViewController.self)!
}
}
67
Swinject relies exclusively on runtime checks
68
So we need tests, to avoid production crashes
69
How do we test those injections?
70
How do we test those injections?
Let's reason about the situation:
→ The dependencies follow a tree structure
→ The view controllers are the roots of those trees
→ By instantiating them, we trigger the whole
injection process
71
How do we test those injections?
Conclusion: we need to write tests that attempt to
instantiate all the controllers.
72
73
Sourcery Template
import XCTest
@testable import YourApp
class DependencyInjectionTests: XCTestCase {
func testDependencyInjection() {
{% for variable in type["ViewControllerFactory"].staticVariables %}
_ = ViewControllerFactory.{{ variable.name }}
{% endfor %}
}
}
74
Generated Code
import XCTest
@testable import TestSourcery
class DependencyInjectionTests: XCTestCase {
func testDependencyInjection() {
_ = ViewControllerFactory.greetingsVC
}
}
75
That's it!
As new controllers are added to the factory, the
corresponding tests will be written automatically
No more room for mistakes, pretty cool!
76
Recap
77
Recap
→ Sourcery is really easy to set up, don't feel scared to try it!
→ (It's also easy to take it out of a project, should you need)
→ It's a great tool to avoid writing boilerplate code by hand
→ Tests are a perfect place to use Sourcery, because they
involve lots of boilerplate
→ Every time a "one-size-fits-all" approach makes sense,
there's a good chance Sourcery can help
78
Don't rely on Humans
!"
to do the job of a Robot
79
Pro Tips
80
Be careful!
81
Be careful!
Sourcery lets us manipulate familiar concepts
(types, protocols, etc.) in an unfamiliar manner.
We get to look at our code the same way that Xcode
does.
This is not an approach we are used to, and it is very
easy to fail to consider some edge cases
82
First, ask Google
83
First, ask Google
Templates tend to be much more complicated than
initially thought (remember AutoMockable).
A little Google search might just save you a lot of
time by pointing you in the right direction.
84
Should I commit generated files?
85
Should I commit generated files?
We might initially think that generated code should not be
versionned.
However, you should seriously consider versionning it.
This way, changes to generated files will appear during code review,
providing the opportunity to check that everything still works fine.
If you don't version generated files, it becomes really easy to forget
that they even exist...
86
!
87
Taking the boilerplate out of
your tests with Sourcery
Vincent Pradeilles @v_pradeilles – Worldline

More Related Content

What's hot

Smarter Testing with Spock
Smarter Testing with SpockSmarter Testing with Spock
Smarter Testing with SpockDmitry Voloshko
 
Testing a 2D Platformer with Spock
Testing a 2D Platformer with SpockTesting a 2D Platformer with Spock
Testing a 2D Platformer with SpockAlexander Tarlinder
 
Proxies are Awesome!
Proxies are Awesome!Proxies are Awesome!
Proxies are Awesome!Brendan Eich
 
The uniform interface is 42
The uniform interface is 42The uniform interface is 42
The uniform interface is 42Yevhen Bobrov
 
Google Guava - Core libraries for Java & Android
Google Guava - Core libraries for Java & AndroidGoogle Guava - Core libraries for Java & Android
Google Guava - Core libraries for Java & AndroidJordi Gerona
 
Обзор фреймворка Twisted
Обзор фреймворка TwistedОбзор фреймворка Twisted
Обзор фреймворка TwistedMaxim Kulsha
 
sizeof(Object): how much memory objects take on JVMs and when this may matter
sizeof(Object): how much memory objects take on JVMs and when this may mattersizeof(Object): how much memory objects take on JVMs and when this may matter
sizeof(Object): how much memory objects take on JVMs and when this may matterDawid Weiss
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 SpringKiyotaka Oku
 
Spock: Test Well and Prosper
Spock: Test Well and ProsperSpock: Test Well and Prosper
Spock: Test Well and ProsperKen Kousen
 
The Ring programming language version 1.2 book - Part 79 of 84
The Ring programming language version 1.2 book - Part 79 of 84The Ring programming language version 1.2 book - Part 79 of 84
The Ring programming language version 1.2 book - Part 79 of 84Mahmoud Samir Fayed
 
Concurrency Concepts in Java
Concurrency Concepts in JavaConcurrency Concepts in Java
Concurrency Concepts in JavaDoug Hawkins
 
Dealing with combinatorial explosions and boring tests
Dealing with combinatorial explosions and boring testsDealing with combinatorial explosions and boring tests
Dealing with combinatorial explosions and boring testsAlexander Tarlinder
 
Why (I think) CoffeeScript Is Awesome
Why (I think) CoffeeScript Is AwesomeWhy (I think) CoffeeScript Is Awesome
Why (I think) CoffeeScript Is AwesomeJo Cranford
 
Clean code with google guava jee conf
Clean code with google guava jee confClean code with google guava jee conf
Clean code with google guava jee confIgor Anishchenko
 
What’s new in C# 6
What’s new in C# 6What’s new in C# 6
What’s new in C# 6Fiyaz Hasan
 
Google guava overview
Google guava overviewGoogle guava overview
Google guava overviewSteve Min
 

What's hot (20)

Smarter Testing with Spock
Smarter Testing with SpockSmarter Testing with Spock
Smarter Testing with Spock
 
Testing a 2D Platformer with Spock
Testing a 2D Platformer with SpockTesting a 2D Platformer with Spock
Testing a 2D Platformer with Spock
 
Mastering Java ByteCode
Mastering Java ByteCodeMastering Java ByteCode
Mastering Java ByteCode
 
groovy & grails - lecture 2
groovy & grails - lecture 2groovy & grails - lecture 2
groovy & grails - lecture 2
 
Proxies are Awesome!
Proxies are Awesome!Proxies are Awesome!
Proxies are Awesome!
 
The uniform interface is 42
The uniform interface is 42The uniform interface is 42
The uniform interface is 42
 
Google Guava - Core libraries for Java & Android
Google Guava - Core libraries for Java & AndroidGoogle Guava - Core libraries for Java & Android
Google Guava - Core libraries for Java & Android
 
Обзор фреймворка Twisted
Обзор фреймворка TwistedОбзор фреймворка Twisted
Обзор фреймворка Twisted
 
sizeof(Object): how much memory objects take on JVMs and when this may matter
sizeof(Object): how much memory objects take on JVMs and when this may mattersizeof(Object): how much memory objects take on JVMs and when this may matter
sizeof(Object): how much memory objects take on JVMs and when this may matter
 
Java 104
Java 104Java 104
Java 104
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 Spring
 
Java.lang.object
Java.lang.objectJava.lang.object
Java.lang.object
 
Spock: Test Well and Prosper
Spock: Test Well and ProsperSpock: Test Well and Prosper
Spock: Test Well and Prosper
 
The Ring programming language version 1.2 book - Part 79 of 84
The Ring programming language version 1.2 book - Part 79 of 84The Ring programming language version 1.2 book - Part 79 of 84
The Ring programming language version 1.2 book - Part 79 of 84
 
Concurrency Concepts in Java
Concurrency Concepts in JavaConcurrency Concepts in Java
Concurrency Concepts in Java
 
Dealing with combinatorial explosions and boring tests
Dealing with combinatorial explosions and boring testsDealing with combinatorial explosions and boring tests
Dealing with combinatorial explosions and boring tests
 
Why (I think) CoffeeScript Is Awesome
Why (I think) CoffeeScript Is AwesomeWhy (I think) CoffeeScript Is Awesome
Why (I think) CoffeeScript Is Awesome
 
Clean code with google guava jee conf
Clean code with google guava jee confClean code with google guava jee conf
Clean code with google guava jee conf
 
What’s new in C# 6
What’s new in C# 6What’s new in C# 6
What’s new in C# 6
 
Google guava overview
Google guava overviewGoogle guava overview
Google guava overview
 

Similar to Taking the boilerplate out of your tests with Sourcery

Cool JVM Tools to Help You Test
Cool JVM Tools to Help You TestCool JVM Tools to Help You Test
Cool JVM Tools to Help You TestSchalk Cronjé
 
CoffeeScript - TechTalk 21/10/2013
CoffeeScript - TechTalk 21/10/2013CoffeeScript - TechTalk 21/10/2013
CoffeeScript - TechTalk 21/10/2013Spyros Ioakeimidis
 
Spock Framework - Slidecast
Spock Framework - SlidecastSpock Framework - Slidecast
Spock Framework - SlidecastDaniel Kolman
 
Real life-coffeescript
Real life-coffeescriptReal life-coffeescript
Real life-coffeescriptDavid Furber
 
Cool Jvm Tools to Help you Test - Aylesbury Testers Version
Cool Jvm Tools to Help you Test - Aylesbury Testers VersionCool Jvm Tools to Help you Test - Aylesbury Testers Version
Cool Jvm Tools to Help you Test - Aylesbury Testers VersionSchalk Cronjé
 
A Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert FornalA Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert FornalQA or the Highway
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasminePaulo Ragonha
 
The Great Scala Makeover
The Great Scala MakeoverThe Great Scala Makeover
The Great Scala MakeoverGarth Gilmour
 
The Future of JVM Languages
The Future of JVM Languages The Future of JVM Languages
The Future of JVM Languages VictorSzoltysek
 
Tests unitaires mock_kesako_20130516
Tests unitaires mock_kesako_20130516Tests unitaires mock_kesako_20130516
Tests unitaires mock_kesako_20130516SOAT
 
An Introduction to Property Based Testing
An Introduction to Property Based TestingAn Introduction to Property Based Testing
An Introduction to Property Based TestingC4Media
 
Scala for Java Programmers
Scala for Java ProgrammersScala for Java Programmers
Scala for Java ProgrammersEric Pederson
 

Similar to Taking the boilerplate out of your tests with Sourcery (20)

Kpi driven-java-development-fn conf
Kpi driven-java-development-fn confKpi driven-java-development-fn conf
Kpi driven-java-development-fn conf
 
Intro to ruby
Intro to rubyIntro to ruby
Intro to ruby
 
Cool JVM Tools to Help You Test
Cool JVM Tools to Help You TestCool JVM Tools to Help You Test
Cool JVM Tools to Help You Test
 
Spock
SpockSpock
Spock
 
CoffeeScript - TechTalk 21/10/2013
CoffeeScript - TechTalk 21/10/2013CoffeeScript - TechTalk 21/10/2013
CoffeeScript - TechTalk 21/10/2013
 
Spock Framework - Slidecast
Spock Framework - SlidecastSpock Framework - Slidecast
Spock Framework - Slidecast
 
Spock Framework
Spock FrameworkSpock Framework
Spock Framework
 
Real life-coffeescript
Real life-coffeescriptReal life-coffeescript
Real life-coffeescript
 
Cool Jvm Tools to Help you Test - Aylesbury Testers Version
Cool Jvm Tools to Help you Test - Aylesbury Testers VersionCool Jvm Tools to Help you Test - Aylesbury Testers Version
Cool Jvm Tools to Help you Test - Aylesbury Testers Version
 
Ruby Basics
Ruby BasicsRuby Basics
Ruby Basics
 
Unit testing
Unit testingUnit testing
Unit testing
 
A Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert FornalA Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert Fornal
 
Getting testy with Perl
Getting testy with PerlGetting testy with Perl
Getting testy with Perl
 
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and JasmineSingle Page Web Applications with CoffeeScript, Backbone and Jasmine
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
 
The Great Scala Makeover
The Great Scala MakeoverThe Great Scala Makeover
The Great Scala Makeover
 
The Future of JVM Languages
The Future of JVM Languages The Future of JVM Languages
The Future of JVM Languages
 
Tests unitaires mock_kesako_20130516
Tests unitaires mock_kesako_20130516Tests unitaires mock_kesako_20130516
Tests unitaires mock_kesako_20130516
 
An Introduction to Property Based Testing
An Introduction to Property Based TestingAn Introduction to Property Based Testing
An Introduction to Property Based Testing
 
Scala for Java Programmers
Scala for Java ProgrammersScala for Java Programmers
Scala for Java Programmers
 
ES6: The Awesome Parts
ES6: The Awesome PartsES6: The Awesome Parts
ES6: The Awesome Parts
 

More from Vincent Pradeilles

The underestimated power of KeyPaths
The underestimated power of KeyPathsThe underestimated power of KeyPaths
The underestimated power of KeyPathsVincent Pradeilles
 
Solving callback hell with good old function composition
Solving callback hell with good old function compositionSolving callback hell with good old function composition
Solving callback hell with good old function compositionVincent Pradeilles
 
How to build a debug view almost for free
How to build a debug view almost for freeHow to build a debug view almost for free
How to build a debug view almost for freeVincent Pradeilles
 
An introduction to property-based testing
An introduction to property-based testingAn introduction to property-based testing
An introduction to property-based testingVincent Pradeilles
 
Implementing pseudo-keywords through Functional Programing
Implementing pseudo-keywords through Functional ProgramingImplementing pseudo-keywords through Functional Programing
Implementing pseudo-keywords through Functional ProgramingVincent Pradeilles
 
Property Wrappers or how Swift decided to become Java
Property Wrappers or how Swift decided to become JavaProperty Wrappers or how Swift decided to become Java
Property Wrappers or how Swift decided to become JavaVincent Pradeilles
 
Advanced functional programing in Swift
Advanced functional programing in SwiftAdvanced functional programing in Swift
Advanced functional programing in SwiftVincent Pradeilles
 

More from Vincent Pradeilles (10)

On-Boarding New Engineers
On-Boarding New EngineersOn-Boarding New Engineers
On-Boarding New Engineers
 
The underestimated power of KeyPaths
The underestimated power of KeyPathsThe underestimated power of KeyPaths
The underestimated power of KeyPaths
 
Solving callback hell with good old function composition
Solving callback hell with good old function compositionSolving callback hell with good old function composition
Solving callback hell with good old function composition
 
How to build a debug view almost for free
How to build a debug view almost for freeHow to build a debug view almost for free
How to build a debug view almost for free
 
An introduction to property-based testing
An introduction to property-based testingAn introduction to property-based testing
An introduction to property-based testing
 
Implementing pseudo-keywords through Functional Programing
Implementing pseudo-keywords through Functional ProgramingImplementing pseudo-keywords through Functional Programing
Implementing pseudo-keywords through Functional Programing
 
Property Wrappers or how Swift decided to become Java
Property Wrappers or how Swift decided to become JavaProperty Wrappers or how Swift decided to become Java
Property Wrappers or how Swift decided to become Java
 
Cocoa heads 09112017
Cocoa heads 09112017Cocoa heads 09112017
Cocoa heads 09112017
 
Advanced functional programing in Swift
Advanced functional programing in SwiftAdvanced functional programing in Swift
Advanced functional programing in Swift
 
Monads in Swift
Monads in SwiftMonads in Swift
Monads in Swift
 

Recently uploaded

Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataBradBedford3
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number SystemsJheuzeDellosa
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWave PLM
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfjoe51371421
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
 
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝soniya singh
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about usDynamic Netsoft
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationkaushalgiri8080
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)OPEN KNOWLEDGE GmbH
 
XpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsXpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsMehedi Hasan Shohan
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
 

Recently uploaded (20)

Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
 
What is Binary Language? Computer Number Systems
What is Binary Language?  Computer Number SystemsWhat is Binary Language?  Computer Number Systems
What is Binary Language? Computer Number Systems
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need It
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdf
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about us
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanation
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)
 
XpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsXpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software Solutions
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
 

Taking the boilerplate out of your tests with Sourcery

  • 1. Taking the boilerplate out of your tests with Sourcery Vincent Pradeilles (@v_pradeilles) – Worldline !
  • 2. 2
  • 3. 3
  • 4. import XCTest class MyTests: XCTestCase { func testExample() { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } } 4
  • 5. 5
  • 6. ! 6
  • 9. What is Sourcery? "Sourcery is a code generator for the Swift language, built on top of Apple's own SourceKit. It extends the language abstractions to allow you to generate boilerplate code automatically."1 1  https://github.com/krzysztofzablocki/Sourcery/blob/master/README.md 9
  • 10. What can we do with Sourcery? 10
  • 11. What can we do with Sourcery? enum Direction { case up case right case down case left } Direction.allCases // [.up, .right, .down, .left] 11
  • 12. What can we do with Sourcery? Since Swift 4.2, this can be achieved through the CaseIterable protocol. But before, generating .allCases was a great use case to illustrate how Sourcery works. 12
  • 14. 1st – Setting Up Sourcery 14
  • 15. Setting Up Sourcery $ brew install sourcery 15
  • 16. Setting Up Sourcery Xcode Project > Build Phases > New Run Script Phase sourcery --sources <sources path> --templates <templates path> --output <output path> 16
  • 17. 2nd – Phantom Protocol 17
  • 18. Phantom Protocol protocol EnumIterable { } extension Direction: EnumIterable { } 18
  • 19. 3rd – Sourcery Template 19
  • 20. Sourcery Template {% for enum in types.implementing.EnumIterable|enum %} {% if not enum.hasAssociatedValues %} {{ enum.accessLevel }} extension {{ enum.name }} { static let allCases: [{{ enum.name }}] = [ {% for case in enum.cases %} .{{ case.name }} {% if not forloop.last %} , {% endif %} {% endfor %} ] } {% endif %} {% endfor %} 20
  • 21. 4th - Generated Code 21
  • 23. Generated Code internal extension Direction { static let allCases: [Direction] = [ .up , .right , .down , .left ] } 23
  • 24. Generated Code Add the generated file to your project That's it ! 24
  • 26. To Sum Up → Sourcery parses your source code → It then uses it to execute templates → Those templates generate new source code → Your project can use this generated code 26
  • 29. func testEquality() { let personA = Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true) let personB = Person(firstName: "Charlie", lastName: "Webb", age: 11, hasDriverLicense: false, isAmerican: true) XCTAssertEqual(personA, personB) } 29
  • 30. ! 30
  • 31. A human-readable diff would be nice 31
  • 32. Incorrect age: expected 10, received 11 32
  • 33. Diffing methods are a perfect example of boilerplate 33
  • 34. internal extension Person { func diff(against other: Person) -> String { var result = [String]() if self.firstName != other.firstName { var diff = "Incorrect firstName: " diff += "expected (self.firstName), " diff += "received (other.firstName)" result.append(diff) } if self.lastName != other.lastName { var diff = "Incorrect lastName: " diff += "expected (self.lastName), " diff += "received (other.lastName)" result.append(diff) } if self.age != other.age { var diff = "Incorrect age: " diff += "expected (self.age), " diff += "received (other.age)" result.append(diff) } if self.hasDriverLicense != other.hasDriverLicense { var diff = "Incorrect hasDriverLicense: " diff += "expected (self.hasDriverLicense), " diff += "received (other.hasDriverLicense)" result.append(diff) } if self.isAmerican != other.isAmerican { var diff = "Incorrect isAmerican: " diff += "expected (self.isAmerican), " diff += "received (other.isAmerican)" result.append(diff) } return result.joined(separator: ". ") } } 34
  • 35. How about we use Sourcery to generate it? 35
  • 36. 1st – Phantom Protocol 36
  • 37. protocol Diffable { } extension Person: Diffable { } 37
  • 38. 2nd – Sourcery Template 38
  • 39. {% for type in types.implementing.Diffable %} {{ type.accessLevel }} extension {{ type.name }} { func diff(against other: {{ type.name }}) -> String { var result = [String]() {% for variable in type.variables %} if self.{{ variable.name }} != other.{{ variable.name }} { var diff = "Incorrect {{ variable.name }}: " diff += "expected (self.{{ variable.name }}), " diff += "received (other.{{ variable.name }})" result.append(diff) } {% endfor %} return result.joined(separator: ". ") } } {% endfor %} 39
  • 40. 3rd – Generated Code 40
  • 41. internal extension Person { func diff(against other: Person) -> String { var result = [String]() if self.firstName != other.firstName { var diff = "Incorrect firstName: " diff += "expected (self.firstName), " diff += "received (other.firstName)" result.append(diff) } if self.lastName != other.lastName { var diff = "Incorrect lastName: " diff += "expected (self.lastName), " diff += "received (other.lastName)" result.append(diff) } if self.age != other.age { var diff = "Incorrect age: " diff += "expected (self.age), " diff += "received (other.age)" result.append(diff) } if self.hasDriverLicense != other.hasDriverLicense { var diff = "Incorrect hasDriverLicense: " diff += "expected (self.hasDriverLicense), " diff += "received (other.hasDriverLicense)" result.append(diff) } if self.isAmerican != other.isAmerican { var diff = "Incorrect isAmerican: " diff += "expected (self.isAmerican), " diff += "received (other.isAmerican)" result.append(diff) } return result.joined(separator: ". ") } } 41
  • 42. 4th – Updated Tests 42
  • 43. let personA = Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true) let personB = Person(firstName: "Charlie", lastName: "Webb", age: 11, hasDriverLicense: false, isAmerican: true) XCTAssertEqual(personA, personB, personA.diff(against: personB)) 43
  • 44. ! 44
  • 45. How about we craft more complex tools? 45
  • 46. Classic MVVM Architecture protocol UserService { func fetchUserName() -> String } class ViewModel { var userNameUpdated: ((String) -> Void)? private let service: UserService init(service: UserService) { self.service = service } func fetchData() { let userName = self.service.fetchUserName() self.userNameUpdated?(userName) } } 46
  • 47. How do we test a component with dependencies? Obvious, just inject mocked dependencies! 47
  • 48. How do we write mocked dependencies? How about we just don't? (And let Sourcery do it) 48
  • 50. 1st – Phantom Protocol 50
  • 51. protocol MockedImplementation { } protocol UserService: MockedImplementation { func fetchUserName() -> String } 51
  • 52. 2nd – Sourcery Template 52
  • 53. {% for protocol in types.implementing.MockedImplementation|protocol %} {{ protocol.accessLevel }} class Mocked{{ protocol.name }}: {{ protocol.name }} { {% for method in protocol.methods %} var {{ method.callName }}CallCounter: Int = 0 var {{ method.callName }}ReturnValue: {{ method.returnTypeName }}? func {{ method.name }} -> {{ method.returnTypeName }} { {{ method.callName }}CallCounter += 1 return {{ method.callName }}ReturnValue! } {% endfor %} } {% endfor %} 53
  • 54. 3rd – Generated Code 54
  • 55. internal class MockedUserService: UserService { var fetchUserNameCallCounter: Int = 0 var fetchUserNameReturnValue: String? func fetchUserName() -> String { fetchUserNameCallCounter += 1 return fetchUserNameReturnValue! } } 55
  • 56. 4th – Writing Tests 56
  • 57. class ViewModelTests: XCTestCase { func testUserServiceCalls() { let mockedService = MockedUserService() let viewModel = ViewModel(service: mockedService) mockedService.fetchUserNameReturnValue = "John Appleseed" viewModel.fetchData() XCTAssertEqual(mockedService.fetchUserNameCallCounter, 1) } } 57
  • 59. No more boilerplate Now we only focus on writing tests for the business logic Of course, there's a lot more features we could add: → variables to store arguments → calling completion handlers → dealing with throwing functions → etc. 59
  • 60. No more boilerplate Sourcery actually ships with a template that takes care of all those needs: AutoMockable (But beware, it is MUCH harder to understand ) 60
  • 61. We are now able to generate tools for testing... 61
  • 62. ...but there's still room to go even further! 62
  • 64. Dependency Injection Many apps rely on Swinject to provide the architectural basis of dependency injection. 64
  • 65. Dependency Injection import Swinject class ViewModelAssembly: Assembly { func assemble(container: Container) { container.register(UserService.self) { _ in return ImplUserService() } container.register(ViewModel.self) { resolver in let service = resolver.resolve(UserService.self)! return ViewModel(service: service) } } } 65
  • 66. Dependency Injection class GreetingsViewControllerAssembly: Assembly { func assemble(container: Container) { container.register(GreetingsViewController.self) { resolver in let viewModel = resolver.resolve(ViewModel.self)! let viewController = UIStoryboard(name: "Main", bundle: nil) .instantiateViewController(withIdentifier: "GreetingsViewController") as! GreetingsViewController viewController.viewModel = viewModel return viewController } } } 66
  • 67. Dependency Injection struct ViewControllerFactory { static var greetingsVC: GreetingsViewController { let assembler = Assembler([ViewModelAssembly(), GreetingsViewControllerAssembly()]) return assembler.resolver.resolve(GreetingsViewController.self)! } } 67
  • 68. Swinject relies exclusively on runtime checks 68
  • 69. So we need tests, to avoid production crashes 69
  • 70. How do we test those injections? 70
  • 71. How do we test those injections? Let's reason about the situation: → The dependencies follow a tree structure → The view controllers are the roots of those trees → By instantiating them, we trigger the whole injection process 71
  • 72. How do we test those injections? Conclusion: we need to write tests that attempt to instantiate all the controllers. 72
  • 73. 73
  • 74. Sourcery Template import XCTest @testable import YourApp class DependencyInjectionTests: XCTestCase { func testDependencyInjection() { {% for variable in type["ViewControllerFactory"].staticVariables %} _ = ViewControllerFactory.{{ variable.name }} {% endfor %} } } 74
  • 75. Generated Code import XCTest @testable import TestSourcery class DependencyInjectionTests: XCTestCase { func testDependencyInjection() { _ = ViewControllerFactory.greetingsVC } } 75
  • 76. That's it! As new controllers are added to the factory, the corresponding tests will be written automatically No more room for mistakes, pretty cool! 76
  • 78. Recap → Sourcery is really easy to set up, don't feel scared to try it! → (It's also easy to take it out of a project, should you need) → It's a great tool to avoid writing boilerplate code by hand → Tests are a perfect place to use Sourcery, because they involve lots of boilerplate → Every time a "one-size-fits-all" approach makes sense, there's a good chance Sourcery can help 78
  • 79. Don't rely on Humans !" to do the job of a Robot 79
  • 82. Be careful! Sourcery lets us manipulate familiar concepts (types, protocols, etc.) in an unfamiliar manner. We get to look at our code the same way that Xcode does. This is not an approach we are used to, and it is very easy to fail to consider some edge cases 82
  • 84. First, ask Google Templates tend to be much more complicated than initially thought (remember AutoMockable). A little Google search might just save you a lot of time by pointing you in the right direction. 84
  • 85. Should I commit generated files? 85
  • 86. Should I commit generated files? We might initially think that generated code should not be versionned. However, you should seriously consider versionning it. This way, changes to generated files will appear during code review, providing the opportunity to check that everything still works fine. If you don't version generated files, it becomes really easy to forget that they even exist... 86
  • 87. ! 87
  • 88. Taking the boilerplate out of your tests with Sourcery Vincent Pradeilles @v_pradeilles – Worldline