SlideShare a Scribd company logo
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 Spock
Dmitry Voloshko
 
Testing a 2D Platformer with Spock
Testing a 2D Platformer with SpockTesting a 2D Platformer with Spock
Testing a 2D Platformer with Spock
Alexander Tarlinder
 
Mastering Java ByteCode
Mastering Java ByteCodeMastering Java ByteCode
Mastering Java ByteCode
Ecommerce Solution Provider SysIQ
 
groovy & grails - lecture 2
groovy & grails - lecture 2groovy & grails - lecture 2
groovy & grails - lecture 2
Alexandre Masselot
 
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 42
Yevhen 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 & Android
Jordi Gerona
 
Обзор фреймворка Twisted
Обзор фреймворка TwistedОбзор фреймворка Twisted
Обзор фреймворка Twisted
Maxim 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 matter
Dawid Weiss
 
Java 104
Java 104Java 104
Java 104
Manuela Grindei
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 Spring
Kiyotaka Oku
 
Java.lang.object
Java.lang.objectJava.lang.object
Java.lang.object
Soham Sengupta
 
Spock: Test Well and Prosper
Spock: Test Well and ProsperSpock: Test Well and Prosper
Spock: Test Well and Prosper
Ken 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 84
Mahmoud Samir Fayed
 
Concurrency Concepts in Java
Concurrency Concepts in JavaConcurrency Concepts in Java
Concurrency Concepts in Java
Doug 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 tests
Alexander Tarlinder
 
Why (I think) CoffeeScript Is Awesome
Why (I think) CoffeeScript Is AwesomeWhy (I think) CoffeeScript Is Awesome
Why (I think) CoffeeScript Is Awesome
Jo 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 conf
Igor Anishchenko
 
What’s new in C# 6
What’s new in C# 6What’s new in C# 6
What’s new in C# 6
Fiyaz Hasan
 
Google guava overview
Google guava overviewGoogle guava overview
Google guava overview
Steve 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

Kpi driven-java-development-fn conf
Kpi driven-java-development-fn confKpi driven-java-development-fn conf
Kpi driven-java-development-fn conf
Anirban Bhattacharjee
 
Intro to ruby
Intro to rubyIntro to ruby
Intro to ruby
Heather Campbell
 
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
Schalk Cronjé
 
Spock
SpockSpock
Spock
nklmish
 
CoffeeScript - TechTalk 21/10/2013
CoffeeScript - TechTalk 21/10/2013CoffeeScript - TechTalk 21/10/2013
CoffeeScript - TechTalk 21/10/2013
Spyros Ioakeimidis
 
Spock Framework
Spock FrameworkSpock Framework
Spock Framework
Daniel Kolman
 
Spock Framework - Slidecast
Spock Framework - SlidecastSpock Framework - Slidecast
Spock Framework - Slidecast
Daniel Kolman
 
Real life-coffeescript
Real life-coffeescriptReal life-coffeescript
Real life-coffeescript
David 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 Version
Schalk Cronjé
 
Ruby Basics
Ruby BasicsRuby Basics
Ruby Basics
NagaLakshmi_N
 
Unit testing
Unit testingUnit testing
Unit testing
davidahaskins
 
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
QA or the Highway
 
Getting testy with Perl
Getting testy with PerlGetting testy with Perl
Getting testy with Perl
Workhorse Computing
 
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
Paulo Ragonha
 
The Great Scala Makeover
The Great Scala MakeoverThe Great Scala Makeover
The Great Scala Makeover
Garth 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_20130516
SOAT
 
An Introduction to Property Based Testing
An Introduction to Property Based TestingAn Introduction to Property Based Testing
An Introduction to Property Based Testing
C4Media
 
Scala for Java Programmers
Scala for Java ProgrammersScala for Java Programmers
Scala for Java Programmers
Eric Pederson
 
ES6: The Awesome Parts
ES6: The Awesome PartsES6: The Awesome Parts
ES6: The Awesome Parts
Domenic Denicola
 

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
Spock FrameworkSpock Framework
Spock Framework
 
Spock Framework - Slidecast
Spock Framework - SlidecastSpock Framework - Slidecast
Spock Framework - Slidecast
 
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

On-Boarding New Engineers
On-Boarding New EngineersOn-Boarding New Engineers
On-Boarding New Engineers
Vincent Pradeilles
 
The underestimated power of KeyPaths
The underestimated power of KeyPathsThe underestimated power of KeyPaths
The underestimated power of KeyPaths
Vincent 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 composition
Vincent 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 free
Vincent Pradeilles
 
An introduction to property-based testing
An introduction to property-based testingAn introduction to property-based testing
An introduction to property-based testing
Vincent Pradeilles
 
Implementing pseudo-keywords through Functional Programing
Implementing pseudo-keywords through Functional ProgramingImplementing pseudo-keywords through Functional Programing
Implementing pseudo-keywords through Functional Programing
Vincent 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 Java
Vincent Pradeilles
 
Cocoa heads 09112017
Cocoa heads 09112017Cocoa heads 09112017
Cocoa heads 09112017
Vincent Pradeilles
 
Advanced functional programing in Swift
Advanced functional programing in SwiftAdvanced functional programing in Swift
Advanced functional programing in Swift
Vincent Pradeilles
 
Monads in Swift
Monads in SwiftMonads in Swift
Monads in Swift
Vincent 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

Hand Rolled Applicative User Validation Code Kata
Hand Rolled Applicative User ValidationCode KataHand Rolled Applicative User ValidationCode Kata
Hand Rolled Applicative User Validation Code Kata
Philip Schwarz
 
LORRAINE ANDREI_LEQUIGAN_HOW TO USE WHATSAPP.pptx
LORRAINE ANDREI_LEQUIGAN_HOW TO USE WHATSAPP.pptxLORRAINE ANDREI_LEQUIGAN_HOW TO USE WHATSAPP.pptx
LORRAINE ANDREI_LEQUIGAN_HOW TO USE WHATSAPP.pptx
lorraineandreiamcidl
 
Vitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdfVitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke
 
DDS-Security 1.2 - What's New? Stronger security for long-running systems
DDS-Security 1.2 - What's New? Stronger security for long-running systemsDDS-Security 1.2 - What's New? Stronger security for long-running systems
DDS-Security 1.2 - What's New? Stronger security for long-running systems
Gerardo Pardo-Castellote
 
OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024
OpenMetadata
 
Atelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissancesAtelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissances
Neo4j
 
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Łukasz Chruściel
 
Oracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptxOracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptx
Remote DBA Services
 
What is Master Data Management by PiLog Group
What is Master Data Management by PiLog GroupWhat is Master Data Management by PiLog Group
What is Master Data Management by PiLog Group
aymanquadri279
 
GraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph TechnologyGraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph Technology
Neo4j
 
E-commerce Application Development Company.pdf
E-commerce Application Development Company.pdfE-commerce Application Development Company.pdf
E-commerce Application Development Company.pdf
Hornet Dynamics
 
Using Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional SafetyUsing Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional Safety
Ayan Halder
 
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdfAutomated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
timtebeek1
 
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOMLORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
lorraineandreiamcidl
 
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
Crescat
 
Webinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for EmbeddedWebinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for Embedded
ICS
 
Graspan: A Big Data System for Big Code Analysis
Graspan: A Big Data System for Big Code AnalysisGraspan: A Big Data System for Big Code Analysis
Graspan: A Big Data System for Big Code Analysis
Aftab Hussain
 
How to write a program in any programming language
How to write a program in any programming languageHow to write a program in any programming language
How to write a program in any programming language
Rakesh Kumar R
 
Neo4j - Product Vision and Knowledge Graphs - GraphSummit Paris
Neo4j - Product Vision and Knowledge Graphs - GraphSummit ParisNeo4j - Product Vision and Knowledge Graphs - GraphSummit Paris
Neo4j - Product Vision and Knowledge Graphs - GraphSummit Paris
Neo4j
 
Revolutionizing Visual Effects Mastering AI Face Swaps.pdf
Revolutionizing Visual Effects Mastering AI Face Swaps.pdfRevolutionizing Visual Effects Mastering AI Face Swaps.pdf
Revolutionizing Visual Effects Mastering AI Face Swaps.pdf
Undress Baby
 

Recently uploaded (20)

Hand Rolled Applicative User Validation Code Kata
Hand Rolled Applicative User ValidationCode KataHand Rolled Applicative User ValidationCode Kata
Hand Rolled Applicative User Validation Code Kata
 
LORRAINE ANDREI_LEQUIGAN_HOW TO USE WHATSAPP.pptx
LORRAINE ANDREI_LEQUIGAN_HOW TO USE WHATSAPP.pptxLORRAINE ANDREI_LEQUIGAN_HOW TO USE WHATSAPP.pptx
LORRAINE ANDREI_LEQUIGAN_HOW TO USE WHATSAPP.pptx
 
Vitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdfVitthal Shirke Java Microservices Resume.pdf
Vitthal Shirke Java Microservices Resume.pdf
 
DDS-Security 1.2 - What's New? Stronger security for long-running systems
DDS-Security 1.2 - What's New? Stronger security for long-running systemsDDS-Security 1.2 - What's New? Stronger security for long-running systems
DDS-Security 1.2 - What's New? Stronger security for long-running systems
 
OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024OpenMetadata Community Meeting - 5th June 2024
OpenMetadata Community Meeting - 5th June 2024
 
Atelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissancesAtelier - Innover avec l’IA Générative et les graphes de connaissances
Atelier - Innover avec l’IA Générative et les graphes de connaissances
 
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
 
Oracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptxOracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptx
 
What is Master Data Management by PiLog Group
What is Master Data Management by PiLog GroupWhat is Master Data Management by PiLog Group
What is Master Data Management by PiLog Group
 
GraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph TechnologyGraphSummit Paris - The art of the possible with Graph Technology
GraphSummit Paris - The art of the possible with Graph Technology
 
E-commerce Application Development Company.pdf
E-commerce Application Development Company.pdfE-commerce Application Development Company.pdf
E-commerce Application Development Company.pdf
 
Using Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional SafetyUsing Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional Safety
 
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdfAutomated software refactoring with OpenRewrite and Generative AI.pptx.pdf
Automated software refactoring with OpenRewrite and Generative AI.pptx.pdf
 
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOMLORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
LORRAINE ANDREI_LEQUIGAN_HOW TO USE ZOOM
 
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
Introducing Crescat - Event Management Software for Venues, Festivals and Eve...
 
Webinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for EmbeddedWebinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for Embedded
 
Graspan: A Big Data System for Big Code Analysis
Graspan: A Big Data System for Big Code AnalysisGraspan: A Big Data System for Big Code Analysis
Graspan: A Big Data System for Big Code Analysis
 
How to write a program in any programming language
How to write a program in any programming languageHow to write a program in any programming language
How to write a program in any programming language
 
Neo4j - Product Vision and Knowledge Graphs - GraphSummit Paris
Neo4j - Product Vision and Knowledge Graphs - GraphSummit ParisNeo4j - Product Vision and Knowledge Graphs - GraphSummit Paris
Neo4j - Product Vision and Knowledge Graphs - GraphSummit Paris
 
Revolutionizing Visual Effects Mastering AI Face Swaps.pdf
Revolutionizing Visual Effects Mastering AI Face Swaps.pdfRevolutionizing Visual Effects Mastering AI Face Swaps.pdf
Revolutionizing Visual Effects Mastering AI Face Swaps.pdf
 

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