SlideShare a Scribd company logo
Testing a 2D Platformer with
Spock
Alexander Tarlinder
Agile Testing Day Scandinavia
2016
The Why
COOL NOT COOL
▪ Developer (2000→) Java, Perl, C, C++, Groovy, C#, PHP, 

Visual Basic, Assembler
▪ Trainer – TDD, Unit testing, Clean Code, WebDriver, 

Specification by Example
▪ Developer mentor
▪ Author
▪ Scrum Master
▪ Professional coach
Alexander Tarlinder
https://www.crisp.se/konsulter/alexander-tarlinder
alexander_tar
alexander.tarlinder@crisp.se
After This Talk You’ll…
• Know the basics of 2D platformers
• Have seen many features of Spock
• Have developed a sense of game testing
challenges
2D Platformers These Days
• Are made using engines!
• Are made up of
– Maps
– Sprites
– Entities & Components
– Game loops/update
methods
Out of Scope Today
Real physics
Performance
Animation
Scripting
Maps
▪ Loading
▪ Getting them into the
tests
Testing Challenges
Sprites & Collisions
▪ Hard to automate
▪ Require visual aids
▪ The owning entity
does the physics
Testing Challenges
Entity Hierarchy
Entity
x, y, width, height, (imageId)

update()
BlockBase
bump()
MovingEntity
velocity, direction
PlayerGoomba
Game Loop And Update Method
WHILE (game runs)
{
Process input
Update
Render scene
}
React to input
Do AI
Do physics
▪ Run at 60 FPS
▪ Requires player input
Testing Challenges
The Component Pattern –

Motivation
player.update() { 

Process movement

Resolve collisions with the world

Resolve collisions with enemies

Check life
…
Move camera

Pick an image to draw

}
Assembling with Components
Player Goomba Flying turtle
Input
Keyboard X
AI X X
Physics
Walking X X
Jumping X
Flying X
CD walls X X X
CD enemies X
CD bullets X X
Graphics
Draw X X X
Particle effects X
• 60 FPS
• No graphics
• State and world setup (aka “test data”)
My Initial Fears
About Spock
https://github.com/spockframework
2009 2010 2011 2012 2013 2014 2015 2016
0.1 0.7 1.0
Basic Spock Test Structure
def "A vanilla Spock test uses given/when/then"() {

given:

def greeting = "Hello"



when:

def message = greeting + ", world!"



then:

message == "Hello, world!"

}
Proper test name
GWT
Noise-free assertion
A First Test
@Subject

def physicsComponent = new PhysicsComponent()



def "A Goomba placed in mid-air will start falling"() {

given: "An empty level and a Goomba floating in mid-air"

def emptyLevel = new Level(10, 10, [])

def fallingGoomba = new Goomba(0, 0, null)



when: "Time is advanced by two frames"

2.times { physicsComponent.update(fallingGoomba, emptyLevel) }



then: "The Goomba has started falling in the second frame"

fallingGoomba.getVerticalVelocity() >
PhysicsComponent.BASE_VERTICAL_VELOCITY

fallingGoomba.getY() == PhysicsComponent.BASE_VERTICAL_VELOCITY

}

You Can Stack when/then
def "A Goomba placed in mid-air will start falling"() {

given: "An empty level and a Goomba floating in mid-air"

def emptyLevel = new Level(10, 10, [])

def fallingGoomba = new Goomba(0, 0, null)



when:

physicsComponent.update(fallingGoomba, emptyLevel)



then:

fallingGoomba.getVerticalVelocity() ==
PhysicsComponent.BASE_VERTICAL_VELOCITY

fallingGoomba.getY() == 0



when:

physicsComponent.update(fallingGoomba, emptyLevel)



then:

fallingGoomba.getVerticalVelocity() >
PhysicsComponent.BASE_VERTICAL_VELOCITY

fallingGoomba.getY() == PhysicsComponent.BASE_VERTICAL_VELOCITY

}

Twice
You Can Add ands Everywhere
def "A Goomba placed in mid-air will start falling #3"() {

given: "An empty level"

def emptyLevel = new Level(10, 10, [])



and: "A Goomba floating in mid-air"

def fallingGoomba = new Goomba(0, 0, null)



when: "The time is adanced by one frame"

physicsComponent.update(fallingGoomba, emptyLevel)



and: "The time is advanced by another frame"

physicsComponent.update(fallingGoomba, emptyLevel)



then: "The Goomba has started accelerating"

fallingGoomba.getVerticalVelocity() >
PhysicsComponent.BASE_VERTICAL_VELOCITY



and: "It has fallen some distance"

fallingGoomba.getY() > old(fallingGoomba.getY())

}

You’ve seen this, but forget that you did
And
Lifecycle Methods
Specification scope
setupSpec()
cleanupSpec()
setup()
cleanup()
def “tested feature”()
Test scope
@Shared
More Features
def "A Goomba placed in mid-air will start falling #4"() {

given:

def emptyLevel = new Level(10, 10, [])

def fallingGoomba = new Goomba(0, 0, null)



when:

5.times { physicsComponent.update(fallingGoomba, emptyLevel) }



then:

with(fallingGoomba) {

expect getVerticalVelocity(),
greaterThan(PhysicsComponent.BASE_VERTICAL_VELOCITY)

expect getY(),
greaterThan(PhysicsComponent.BASE_VERTICAL_VELOCITY)

}

}
With
block
Hamcrest matchers
Parameterized tests
def "Examine every single frame in an animation"() {

given:

def testedAnimation = new Animation()

testedAnimation.add("one", 1).add("two", 2).add("three", 3);



when:

ticks.times {testedAnimation.advance()}



then:

testedAnimation.getCurrentImageId() == expectedId



where:

ticks || expectedId

0 || "one"

1 || "two"

2 || "two"

3 || "three"

4 || "three"

5 || "three"

6 || "one"

}
This can be any type of
expression
Optional
Data pipes
def "Examine every single frame in an animation"() {

given:

def testedAnimation = new Animation()

testedAnimation.add("one", 1).add("two", 2).add("three", 3);



when:

ticks.times {testedAnimation.advance()}



then:

testedAnimation.getCurrentImageId() == expectedId



where:

ticks << (0..6)

expectedId << ["one", ["two"].multiply(2), 

["three"].multiply(3), "one"].flatten()}
Stubs
def "Level dimensions are acquired from the TMX loader" () {



final levelWidth = 20;

final levelHeight = 10;



given:

def tmxLoaderStub = Stub(SimpleTmxLoader)

tmxLoaderStub.getLevel() >> new int[levelHeight][levelWidth]

tmxLoaderStub.getMapHeight() >> levelHeight

tmxLoaderStub.getMapWidth() >> levelWidth



when:

def level = new LevelBuilder(tmxLoaderStub).buildLevel()



then:

level.heightInBlocks == levelHeight

level.widthInBlocks == levelWidth

}
Mocks
def "Three components are called during a Goomba's update"() {

given:

def aiComponentMock = Mock(AIComponent)

def keyboardInputComponentMock = Mock(KeyboardInputComponent)

def cameraComponentMock = Mock(CameraComponent)

def goomba = new Goomba(0, 0, new GameContext(new Level(10, 10, [])))

.withInputComponent(keyboardInputComponentMock)

.withAIComponent(aiComponentMock)

.withCameraComponent(cameraComponentMock)



when:

goomba.update()



then:

1 * aiComponentMock.update(goomba)

(1.._) * keyboardInputComponentMock.update(_ as MovingEntity)

(_..1) * cameraComponentMock.update(_)
}

This can get creative, like:
3 * _.update(*_)
or even:
3 * _./^u.*/(*_)
Some Annotations
• @Subject
• @Shared
• @Unroll("Advance #ticks and expect #expectedId")
• @Stepwise
• @IgnoreIf({ System.getenv("ENV").contains("ci") })
• @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
• @Title("One-line title of a specification")
• @Narrative("""Longer multi-line

description.""")
Using Visual Aids
def "A player standing still on a block won't move anywhere"() {

given: "A simple level with some ground"

def level = new StringLevelBuilder().buildLevel((String[]) [

" ",

" ",

"III"].toArray())

def gameContext = new GameContext(level)



and: "The player standing on top of it"

final int startX = BlockBase.BLOCK_SIZE;

final int startY = BlockBase.BLOCK_SIZE + 1
def player = new Player(startX, startY, gameContext, new NullInputComponent())

gameContext.addEntity(player)



def viewPort = new NullViewPort()

gameContext.setViewPort(viewPort)



when: "Time is advanced"

10.times { player.update(); viewPort.update(); }



then: "The player hasn't moved"

player.getX() == startX

player.getY() == startY

}

The level is made
visible in the test
def "A player standing still on a block won't move anywhere with visual aids"() {

given: "A simple level with some ground"

def level = new StringLevelBuilder().buildLevel((String[]) [

" ",

" ",

"III"].toArray())

def gameContext = new GameContext(level)



and: "The player standing on top of it"

final int startX = BlockBase.BLOCK_SIZE;

final int startY = BlockBase.BLOCK_SIZE + 1
def player = new Player(startX, startY, gameContext, new NullInputComponent())

gameContext.addEntity(player)



def viewPort = new SwingViewPort(gameContext)

gameContext.setViewPort(viewPort)



when: "Time is advanced"

10.times { slomo { player.update(); viewPort.update(); } }



then: "The player hasn't moved"

player.getX() == startX

player.getY() == startY

}
A real view port
Slow down!
Conclusions
• How was Spock useful?
– Test names and GWT labels really helped
– Groovy reduced the bloat
– Features for parameterized tests useful for some tests whereas mocking and
stubbing remained unutilized in this case
• Game testing
– The world is the test data - so make sure you can generate it easily
– Conciseness is crucial - because of all the math expressions
– One frame at the time - turned out to be a viable strategy for handling 60 FPS in
unit tests
– Games are huge state machines - virtually no stubbing and mocking in the core code
– The Component pattern - is more or less a must for testability
– Use visual aids - and write the unit tests so that they can run with real viewports
– Off-by-one errors - will torment you
– Test-driving is hard - because of the floating point math (the API can be teased out,
but knowing exactly where a player should be after falling and sliding for 15
frames is better determined by using an actual viewport)
Getting Spock
apply plugin: 'java'



repositories {

mavenCentral()

}



dependencies {

testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'

}
Spock Reports – Overview
Spock Reports – Details
Ad

More Related Content

What's hot (20)

Java Puzzle
Java PuzzleJava Puzzle
Java Puzzle
SFilipp
 
Java Language fundamental
Java Language fundamentalJava Language fundamental
Java Language fundamental
Infoviaan Technologies
 
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
 
Compact and safely: static DSL on Kotlin
Compact and safely: static DSL on KotlinCompact and safely: static DSL on Kotlin
Compact and safely: static DSL on Kotlin
Dmitry Pranchuk
 
Java_practical_handbook
Java_practical_handbookJava_practical_handbook
Java_practical_handbook
Manusha Dilan
 
Angular2 rxjs
Angular2 rxjsAngular2 rxjs
Angular2 rxjs
Christoffer Noring
 
Hacking JavaFX with Groovy, Clojure, Scala, and Visage
Hacking JavaFX with Groovy, Clojure, Scala, and VisageHacking JavaFX with Groovy, Clojure, Scala, and Visage
Hacking JavaFX with Groovy, Clojure, Scala, and Visage
Stephen Chin
 
ScalaDays 2014 - Reactive Scala 3D Game Engine
ScalaDays 2014 - Reactive Scala 3D Game Engine ScalaDays 2014 - Reactive Scala 3D Game Engine
ScalaDays 2014 - Reactive Scala 3D Game Engine
Aleksandar Prokopec
 
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
 
Spock framework
Spock frameworkSpock framework
Spock framework
Djair Carvalho
 
Advanced Dynamic Analysis for Leak Detection (Apple Internship 2008)
Advanced Dynamic Analysis for Leak Detection (Apple Internship 2008)Advanced Dynamic Analysis for Leak Detection (Apple Internship 2008)
Advanced Dynamic Analysis for Leak Detection (Apple Internship 2008)
James Clause
 
Java puzzles
Java puzzlesJava puzzles
Java puzzles
Nikola Petrov
 
Codestrong 2012 breakout session hacking titanium
Codestrong 2012 breakout session   hacking titaniumCodestrong 2012 breakout session   hacking titanium
Codestrong 2012 breakout session hacking titanium
Axway Appcelerator
 
The Ring programming language version 1.5.3 book - Part 10 of 184
The Ring programming language version 1.5.3 book - Part 10 of 184The Ring programming language version 1.5.3 book - Part 10 of 184
The Ring programming language version 1.5.3 book - Part 10 of 184
Mahmoud Samir Fayed
 
JEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistJEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with Javassist
Anton Arhipov
 
JPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream APIJPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream API
tvaleev
 
Predictably
PredictablyPredictably
Predictably
ztellman
 
The Ring programming language version 1.5.4 book - Part 10 of 185
The Ring programming language version 1.5.4 book - Part 10 of 185The Ring programming language version 1.5.4 book - Part 10 of 185
The Ring programming language version 1.5.4 book - Part 10 of 185
Mahmoud Samir Fayed
 
Hacking JavaFX with Groovy, Clojure, Scala, and Visage: Stephen Chin
Hacking JavaFX with Groovy, Clojure, Scala, and Visage: Stephen ChinHacking JavaFX with Groovy, Clojure, Scala, and Visage: Stephen Chin
Hacking JavaFX with Groovy, Clojure, Scala, and Visage: Stephen Chin
jaxconf
 
Down to Stack Traces, up from Heap Dumps
Down to Stack Traces, up from Heap DumpsDown to Stack Traces, up from Heap Dumps
Down to Stack Traces, up from Heap Dumps
Andrei Pangin
 
Java Puzzle
Java PuzzleJava Puzzle
Java Puzzle
SFilipp
 
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
 
Compact and safely: static DSL on Kotlin
Compact and safely: static DSL on KotlinCompact and safely: static DSL on Kotlin
Compact and safely: static DSL on Kotlin
Dmitry Pranchuk
 
Java_practical_handbook
Java_practical_handbookJava_practical_handbook
Java_practical_handbook
Manusha Dilan
 
Hacking JavaFX with Groovy, Clojure, Scala, and Visage
Hacking JavaFX with Groovy, Clojure, Scala, and VisageHacking JavaFX with Groovy, Clojure, Scala, and Visage
Hacking JavaFX with Groovy, Clojure, Scala, and Visage
Stephen Chin
 
ScalaDays 2014 - Reactive Scala 3D Game Engine
ScalaDays 2014 - Reactive Scala 3D Game Engine ScalaDays 2014 - Reactive Scala 3D Game Engine
ScalaDays 2014 - Reactive Scala 3D Game Engine
Aleksandar Prokopec
 
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
 
Advanced Dynamic Analysis for Leak Detection (Apple Internship 2008)
Advanced Dynamic Analysis for Leak Detection (Apple Internship 2008)Advanced Dynamic Analysis for Leak Detection (Apple Internship 2008)
Advanced Dynamic Analysis for Leak Detection (Apple Internship 2008)
James Clause
 
Codestrong 2012 breakout session hacking titanium
Codestrong 2012 breakout session   hacking titaniumCodestrong 2012 breakout session   hacking titanium
Codestrong 2012 breakout session hacking titanium
Axway Appcelerator
 
The Ring programming language version 1.5.3 book - Part 10 of 184
The Ring programming language version 1.5.3 book - Part 10 of 184The Ring programming language version 1.5.3 book - Part 10 of 184
The Ring programming language version 1.5.3 book - Part 10 of 184
Mahmoud Samir Fayed
 
JEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistJEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with Javassist
Anton Arhipov
 
JPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream APIJPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream API
tvaleev
 
Predictably
PredictablyPredictably
Predictably
ztellman
 
The Ring programming language version 1.5.4 book - Part 10 of 185
The Ring programming language version 1.5.4 book - Part 10 of 185The Ring programming language version 1.5.4 book - Part 10 of 185
The Ring programming language version 1.5.4 book - Part 10 of 185
Mahmoud Samir Fayed
 
Hacking JavaFX with Groovy, Clojure, Scala, and Visage: Stephen Chin
Hacking JavaFX with Groovy, Clojure, Scala, and Visage: Stephen ChinHacking JavaFX with Groovy, Clojure, Scala, and Visage: Stephen Chin
Hacking JavaFX with Groovy, Clojure, Scala, and Visage: Stephen Chin
jaxconf
 
Down to Stack Traces, up from Heap Dumps
Down to Stack Traces, up from Heap DumpsDown to Stack Traces, up from Heap Dumps
Down to Stack Traces, up from Heap Dumps
Andrei Pangin
 

Similar to Testing a 2D Platformer with Spock (20)

BDD - Behavior Driven Development Webapps mit Groovy Spock und Geb
BDD - Behavior Driven Development Webapps mit Groovy Spock und GebBDD - Behavior Driven Development Webapps mit Groovy Spock und Geb
BDD - Behavior Driven Development Webapps mit Groovy Spock und Geb
Christian Baranowski
 
JavaScript Advanced - Useful methods to power up your code
JavaScript Advanced - Useful methods to power up your codeJavaScript Advanced - Useful methods to power up your code
JavaScript Advanced - Useful methods to power up your code
Laurence Svekis ✔
 
Making Games in JavaScript
Making Games in JavaScriptMaking Games in JavaScript
Making Games in JavaScript
Sam Cartwright
 
Fullstack Conference - Proxies before proxies: The hidden gems of Javascript...
Fullstack Conference -  Proxies before proxies: The hidden gems of Javascript...Fullstack Conference -  Proxies before proxies: The hidden gems of Javascript...
Fullstack Conference - Proxies before proxies: The hidden gems of Javascript...
Tim Chaplin
 
Emerging Languages: A Tour of the Horizon
Emerging Languages: A Tour of the HorizonEmerging Languages: A Tour of the Horizon
Emerging Languages: A Tour of the Horizon
Alex Payne
 
The Ring programming language version 1.10 book - Part 22 of 212
The Ring programming language version 1.10 book - Part 22 of 212The Ring programming language version 1.10 book - Part 22 of 212
The Ring programming language version 1.10 book - Part 22 of 212
Mahmoud Samir Fayed
 
Pocket Talk; Spock framework
Pocket Talk; Spock frameworkPocket Talk; Spock framework
Pocket Talk; Spock framework
Infoway
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests
Tomek Kaczanowski
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 Spring
Kiyotaka Oku
 
Unity3 d devfest-2014
Unity3 d devfest-2014Unity3 d devfest-2014
Unity3 d devfest-2014
Vincenzo Favara
 
Introduction to Groovy
Introduction to GroovyIntroduction to Groovy
Introduction to Groovy
André Faria Gomes
 
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
DroidConTLV
 
HTML5 - Daha Flash bir web?
HTML5 - Daha Flash bir web?HTML5 - Daha Flash bir web?
HTML5 - Daha Flash bir web?
Ankara JUG
 
W-JAX 09 - Lift
W-JAX 09 - LiftW-JAX 09 - Lift
W-JAX 09 - Lift
Heiko Seeberger
 
Game dev 101 part 3
Game dev 101 part 3Game dev 101 part 3
Game dev 101 part 3
Christoffer Noring
 
33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests
Tomek Kaczanowski
 
Intro to Game Programming
Intro to Game ProgrammingIntro to Game Programming
Intro to Game Programming
Richard Jones
 
How to Clone Flappy Bird in Swift
How to Clone Flappy Bird in SwiftHow to Clone Flappy Bird in Swift
How to Clone Flappy Bird in Swift
Giordano Scalzo
 
Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?
Artur Latoszewski
 
Jggug 2010 330 Grails 1.3 観察
Jggug 2010 330 Grails 1.3 観察Jggug 2010 330 Grails 1.3 観察
Jggug 2010 330 Grails 1.3 観察
Tsuyoshi Yamamoto
 
BDD - Behavior Driven Development Webapps mit Groovy Spock und Geb
BDD - Behavior Driven Development Webapps mit Groovy Spock und GebBDD - Behavior Driven Development Webapps mit Groovy Spock und Geb
BDD - Behavior Driven Development Webapps mit Groovy Spock und Geb
Christian Baranowski
 
JavaScript Advanced - Useful methods to power up your code
JavaScript Advanced - Useful methods to power up your codeJavaScript Advanced - Useful methods to power up your code
JavaScript Advanced - Useful methods to power up your code
Laurence Svekis ✔
 
Making Games in JavaScript
Making Games in JavaScriptMaking Games in JavaScript
Making Games in JavaScript
Sam Cartwright
 
Fullstack Conference - Proxies before proxies: The hidden gems of Javascript...
Fullstack Conference -  Proxies before proxies: The hidden gems of Javascript...Fullstack Conference -  Proxies before proxies: The hidden gems of Javascript...
Fullstack Conference - Proxies before proxies: The hidden gems of Javascript...
Tim Chaplin
 
Emerging Languages: A Tour of the Horizon
Emerging Languages: A Tour of the HorizonEmerging Languages: A Tour of the Horizon
Emerging Languages: A Tour of the Horizon
Alex Payne
 
The Ring programming language version 1.10 book - Part 22 of 212
The Ring programming language version 1.10 book - Part 22 of 212The Ring programming language version 1.10 book - Part 22 of 212
The Ring programming language version 1.10 book - Part 22 of 212
Mahmoud Samir Fayed
 
Pocket Talk; Spock framework
Pocket Talk; Spock frameworkPocket Talk; Spock framework
Pocket Talk; Spock framework
Infoway
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests
Tomek Kaczanowski
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 Spring
Kiyotaka Oku
 
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
DroidConTLV
 
HTML5 - Daha Flash bir web?
HTML5 - Daha Flash bir web?HTML5 - Daha Flash bir web?
HTML5 - Daha Flash bir web?
Ankara JUG
 
33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests
Tomek Kaczanowski
 
Intro to Game Programming
Intro to Game ProgrammingIntro to Game Programming
Intro to Game Programming
Richard Jones
 
How to Clone Flappy Bird in Swift
How to Clone Flappy Bird in SwiftHow to Clone Flappy Bird in Swift
How to Clone Flappy Bird in Swift
Giordano Scalzo
 
Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?Kotlin coroutine - the next step for RxJava developer?
Kotlin coroutine - the next step for RxJava developer?
Artur Latoszewski
 
Jggug 2010 330 Grails 1.3 観察
Jggug 2010 330 Grails 1.3 観察Jggug 2010 330 Grails 1.3 観察
Jggug 2010 330 Grails 1.3 観察
Tsuyoshi Yamamoto
 
Ad

Recently uploaded (20)

tecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdftecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdf
fjgm517
 
Andrew Marnell: Transforming Business Strategy Through Data-Driven Insights
Andrew Marnell: Transforming Business Strategy Through Data-Driven InsightsAndrew Marnell: Transforming Business Strategy Through Data-Driven Insights
Andrew Marnell: Transforming Business Strategy Through Data-Driven Insights
Andrew Marnell
 
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager APIUiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPathCommunity
 
Electronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploitElectronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploit
niftliyevhuseyn
 
Build Your Own Copilot & Agents For Devs
Build Your Own Copilot & Agents For DevsBuild Your Own Copilot & Agents For Devs
Build Your Own Copilot & Agents For Devs
Brian McKeiver
 
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep DiveDesigning Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
ScyllaDB
 
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-UmgebungenHCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
panagenda
 
Cyber Awareness overview for 2025 month of security
Cyber Awareness overview for 2025 month of securityCyber Awareness overview for 2025 month of security
Cyber Awareness overview for 2025 month of security
riccardosl1
 
Big Data Analytics Quick Research Guide by Arthur Morgan
Big Data Analytics Quick Research Guide by Arthur MorganBig Data Analytics Quick Research Guide by Arthur Morgan
Big Data Analytics Quick Research Guide by Arthur Morgan
Arthur Morgan
 
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In FranceManifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
chb3
 
Cybersecurity Identity and Access Solutions using Azure AD
Cybersecurity Identity and Access Solutions using Azure ADCybersecurity Identity and Access Solutions using Azure AD
Cybersecurity Identity and Access Solutions using Azure AD
VICTOR MAESTRE RAMIREZ
 
Splunk Security Update | Public Sector Summit Germany 2025
Splunk Security Update | Public Sector Summit Germany 2025Splunk Security Update | Public Sector Summit Germany 2025
Splunk Security Update | Public Sector Summit Germany 2025
Splunk
 
Quantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur MorganQuantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur Morgan
Arthur Morgan
 
ThousandEyes Partner Innovation Updates for May 2025
ThousandEyes Partner Innovation Updates for May 2025ThousandEyes Partner Innovation Updates for May 2025
ThousandEyes Partner Innovation Updates for May 2025
ThousandEyes
 
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdfSAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
Precisely
 
Rusty Waters: Elevating Lakehouses Beyond Spark
Rusty Waters: Elevating Lakehouses Beyond SparkRusty Waters: Elevating Lakehouses Beyond Spark
Rusty Waters: Elevating Lakehouses Beyond Spark
carlyakerly1
 
Are Cloud PBX Providers in India Reliable for Small Businesses (1).pdf
Are Cloud PBX Providers in India Reliable for Small Businesses (1).pdfAre Cloud PBX Providers in India Reliable for Small Businesses (1).pdf
Are Cloud PBX Providers in India Reliable for Small Businesses (1).pdf
Telecoms Supermarket
 
Procurement Insights Cost To Value Guide.pptx
Procurement Insights Cost To Value Guide.pptxProcurement Insights Cost To Value Guide.pptx
Procurement Insights Cost To Value Guide.pptx
Jon Hansen
 
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Aqusag Technologies
 
AI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global TrendsAI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global Trends
InData Labs
 
tecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdftecnologias de las primeras civilizaciones.pdf
tecnologias de las primeras civilizaciones.pdf
fjgm517
 
Andrew Marnell: Transforming Business Strategy Through Data-Driven Insights
Andrew Marnell: Transforming Business Strategy Through Data-Driven InsightsAndrew Marnell: Transforming Business Strategy Through Data-Driven Insights
Andrew Marnell: Transforming Business Strategy Through Data-Driven Insights
Andrew Marnell
 
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager APIUiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPath Community Berlin: Orchestrator API, Swagger, and Test Manager API
UiPathCommunity
 
Electronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploitElectronic_Mail_Attacks-1-35.pdf by xploit
Electronic_Mail_Attacks-1-35.pdf by xploit
niftliyevhuseyn
 
Build Your Own Copilot & Agents For Devs
Build Your Own Copilot & Agents For DevsBuild Your Own Copilot & Agents For Devs
Build Your Own Copilot & Agents For Devs
Brian McKeiver
 
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep DiveDesigning Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
ScyllaDB
 
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-UmgebungenHCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
HCL Nomad Web – Best Practices und Verwaltung von Multiuser-Umgebungen
panagenda
 
Cyber Awareness overview for 2025 month of security
Cyber Awareness overview for 2025 month of securityCyber Awareness overview for 2025 month of security
Cyber Awareness overview for 2025 month of security
riccardosl1
 
Big Data Analytics Quick Research Guide by Arthur Morgan
Big Data Analytics Quick Research Guide by Arthur MorganBig Data Analytics Quick Research Guide by Arthur Morgan
Big Data Analytics Quick Research Guide by Arthur Morgan
Arthur Morgan
 
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In FranceManifest Pre-Seed Update | A Humanoid OEM Deeptech In France
Manifest Pre-Seed Update | A Humanoid OEM Deeptech In France
chb3
 
Cybersecurity Identity and Access Solutions using Azure AD
Cybersecurity Identity and Access Solutions using Azure ADCybersecurity Identity and Access Solutions using Azure AD
Cybersecurity Identity and Access Solutions using Azure AD
VICTOR MAESTRE RAMIREZ
 
Splunk Security Update | Public Sector Summit Germany 2025
Splunk Security Update | Public Sector Summit Germany 2025Splunk Security Update | Public Sector Summit Germany 2025
Splunk Security Update | Public Sector Summit Germany 2025
Splunk
 
Quantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur MorganQuantum Computing Quick Research Guide by Arthur Morgan
Quantum Computing Quick Research Guide by Arthur Morgan
Arthur Morgan
 
ThousandEyes Partner Innovation Updates for May 2025
ThousandEyes Partner Innovation Updates for May 2025ThousandEyes Partner Innovation Updates for May 2025
ThousandEyes Partner Innovation Updates for May 2025
ThousandEyes
 
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdfSAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
SAP Modernization: Maximizing the Value of Your SAP S/4HANA Migration.pdf
Precisely
 
Rusty Waters: Elevating Lakehouses Beyond Spark
Rusty Waters: Elevating Lakehouses Beyond SparkRusty Waters: Elevating Lakehouses Beyond Spark
Rusty Waters: Elevating Lakehouses Beyond Spark
carlyakerly1
 
Are Cloud PBX Providers in India Reliable for Small Businesses (1).pdf
Are Cloud PBX Providers in India Reliable for Small Businesses (1).pdfAre Cloud PBX Providers in India Reliable for Small Businesses (1).pdf
Are Cloud PBX Providers in India Reliable for Small Businesses (1).pdf
Telecoms Supermarket
 
Procurement Insights Cost To Value Guide.pptx
Procurement Insights Cost To Value Guide.pptxProcurement Insights Cost To Value Guide.pptx
Procurement Insights Cost To Value Guide.pptx
Jon Hansen
 
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Massive Power Outage Hits Spain, Portugal, and France: Causes, Impact, and On...
Aqusag Technologies
 
AI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global TrendsAI and Data Privacy in 2025: Global Trends
AI and Data Privacy in 2025: Global Trends
InData Labs
 
Ad

Testing a 2D Platformer with Spock

  • 1. Testing a 2D Platformer with Spock Alexander Tarlinder Agile Testing Day Scandinavia 2016
  • 3. ▪ Developer (2000→) Java, Perl, C, C++, Groovy, C#, PHP, 
 Visual Basic, Assembler ▪ Trainer – TDD, Unit testing, Clean Code, WebDriver, 
 Specification by Example ▪ Developer mentor ▪ Author ▪ Scrum Master ▪ Professional coach Alexander Tarlinder https://www.crisp.se/konsulter/alexander-tarlinder alexander_tar [email protected]
  • 4. After This Talk You’ll… • Know the basics of 2D platformers • Have seen many features of Spock • Have developed a sense of game testing challenges
  • 5. 2D Platformers These Days • Are made using engines! • Are made up of – Maps – Sprites – Entities & Components – Game loops/update methods Out of Scope Today Real physics Performance Animation Scripting
  • 6. Maps ▪ Loading ▪ Getting them into the tests Testing Challenges
  • 7. Sprites & Collisions ▪ Hard to automate ▪ Require visual aids ▪ The owning entity does the physics Testing Challenges
  • 8. Entity Hierarchy Entity x, y, width, height, (imageId)
 update() BlockBase bump() MovingEntity velocity, direction PlayerGoomba
  • 9. Game Loop And Update Method WHILE (game runs) { Process input Update Render scene } React to input Do AI Do physics ▪ Run at 60 FPS ▪ Requires player input Testing Challenges
  • 10. The Component Pattern –
 Motivation player.update() { 
 Process movement
 Resolve collisions with the world
 Resolve collisions with enemies
 Check life … Move camera
 Pick an image to draw
 }
  • 11. Assembling with Components Player Goomba Flying turtle Input Keyboard X AI X X Physics Walking X X Jumping X Flying X CD walls X X X CD enemies X CD bullets X X Graphics Draw X X X Particle effects X
  • 12. • 60 FPS • No graphics • State and world setup (aka “test data”) My Initial Fears
  • 14. Basic Spock Test Structure def "A vanilla Spock test uses given/when/then"() {
 given:
 def greeting = "Hello"
 
 when:
 def message = greeting + ", world!"
 
 then:
 message == "Hello, world!"
 } Proper test name GWT Noise-free assertion
  • 15. A First Test @Subject
 def physicsComponent = new PhysicsComponent()
 
 def "A Goomba placed in mid-air will start falling"() {
 given: "An empty level and a Goomba floating in mid-air"
 def emptyLevel = new Level(10, 10, [])
 def fallingGoomba = new Goomba(0, 0, null)
 
 when: "Time is advanced by two frames"
 2.times { physicsComponent.update(fallingGoomba, emptyLevel) }
 
 then: "The Goomba has started falling in the second frame"
 fallingGoomba.getVerticalVelocity() > PhysicsComponent.BASE_VERTICAL_VELOCITY
 fallingGoomba.getY() == PhysicsComponent.BASE_VERTICAL_VELOCITY
 }

  • 16. You Can Stack when/then def "A Goomba placed in mid-air will start falling"() {
 given: "An empty level and a Goomba floating in mid-air"
 def emptyLevel = new Level(10, 10, [])
 def fallingGoomba = new Goomba(0, 0, null)
 
 when:
 physicsComponent.update(fallingGoomba, emptyLevel)
 
 then:
 fallingGoomba.getVerticalVelocity() == PhysicsComponent.BASE_VERTICAL_VELOCITY
 fallingGoomba.getY() == 0
 
 when:
 physicsComponent.update(fallingGoomba, emptyLevel)
 
 then:
 fallingGoomba.getVerticalVelocity() > PhysicsComponent.BASE_VERTICAL_VELOCITY
 fallingGoomba.getY() == PhysicsComponent.BASE_VERTICAL_VELOCITY
 }
 Twice
  • 17. You Can Add ands Everywhere def "A Goomba placed in mid-air will start falling #3"() {
 given: "An empty level"
 def emptyLevel = new Level(10, 10, [])
 
 and: "A Goomba floating in mid-air"
 def fallingGoomba = new Goomba(0, 0, null)
 
 when: "The time is adanced by one frame"
 physicsComponent.update(fallingGoomba, emptyLevel)
 
 and: "The time is advanced by another frame"
 physicsComponent.update(fallingGoomba, emptyLevel)
 
 then: "The Goomba has started accelerating"
 fallingGoomba.getVerticalVelocity() > PhysicsComponent.BASE_VERTICAL_VELOCITY
 
 and: "It has fallen some distance"
 fallingGoomba.getY() > old(fallingGoomba.getY())
 }
 You’ve seen this, but forget that you did And
  • 19. More Features def "A Goomba placed in mid-air will start falling #4"() {
 given:
 def emptyLevel = new Level(10, 10, [])
 def fallingGoomba = new Goomba(0, 0, null)
 
 when:
 5.times { physicsComponent.update(fallingGoomba, emptyLevel) }
 
 then:
 with(fallingGoomba) {
 expect getVerticalVelocity(), greaterThan(PhysicsComponent.BASE_VERTICAL_VELOCITY)
 expect getY(), greaterThan(PhysicsComponent.BASE_VERTICAL_VELOCITY)
 }
 } With block Hamcrest matchers
  • 20. Parameterized tests def "Examine every single frame in an animation"() {
 given:
 def testedAnimation = new Animation()
 testedAnimation.add("one", 1).add("two", 2).add("three", 3);
 
 when:
 ticks.times {testedAnimation.advance()}
 
 then:
 testedAnimation.getCurrentImageId() == expectedId
 
 where:
 ticks || expectedId
 0 || "one"
 1 || "two"
 2 || "two"
 3 || "three"
 4 || "three"
 5 || "three"
 6 || "one"
 } This can be any type of expression Optional
  • 21. Data pipes def "Examine every single frame in an animation"() {
 given:
 def testedAnimation = new Animation()
 testedAnimation.add("one", 1).add("two", 2).add("three", 3);
 
 when:
 ticks.times {testedAnimation.advance()}
 
 then:
 testedAnimation.getCurrentImageId() == expectedId
 
 where:
 ticks << (0..6)
 expectedId << ["one", ["two"].multiply(2), 
 ["three"].multiply(3), "one"].flatten()}
  • 22. Stubs def "Level dimensions are acquired from the TMX loader" () {
 
 final levelWidth = 20;
 final levelHeight = 10;
 
 given:
 def tmxLoaderStub = Stub(SimpleTmxLoader)
 tmxLoaderStub.getLevel() >> new int[levelHeight][levelWidth]
 tmxLoaderStub.getMapHeight() >> levelHeight
 tmxLoaderStub.getMapWidth() >> levelWidth
 
 when:
 def level = new LevelBuilder(tmxLoaderStub).buildLevel()
 
 then:
 level.heightInBlocks == levelHeight
 level.widthInBlocks == levelWidth
 }
  • 23. Mocks def "Three components are called during a Goomba's update"() {
 given:
 def aiComponentMock = Mock(AIComponent)
 def keyboardInputComponentMock = Mock(KeyboardInputComponent)
 def cameraComponentMock = Mock(CameraComponent)
 def goomba = new Goomba(0, 0, new GameContext(new Level(10, 10, [])))
 .withInputComponent(keyboardInputComponentMock)
 .withAIComponent(aiComponentMock)
 .withCameraComponent(cameraComponentMock)
 
 when:
 goomba.update()
 
 then:
 1 * aiComponentMock.update(goomba)
 (1.._) * keyboardInputComponentMock.update(_ as MovingEntity)
 (_..1) * cameraComponentMock.update(_) }
 This can get creative, like: 3 * _.update(*_) or even: 3 * _./^u.*/(*_)
  • 24. Some Annotations • @Subject • @Shared • @Unroll("Advance #ticks and expect #expectedId") • @Stepwise • @IgnoreIf({ System.getenv("ENV").contains("ci") }) • @Timeout(value = 100, unit = TimeUnit.MILLISECONDS) • @Title("One-line title of a specification") • @Narrative("""Longer multi-line
 description.""")
  • 25. Using Visual Aids def "A player standing still on a block won't move anywhere"() {
 given: "A simple level with some ground"
 def level = new StringLevelBuilder().buildLevel((String[]) [
 " ",
 " ",
 "III"].toArray())
 def gameContext = new GameContext(level)
 
 and: "The player standing on top of it"
 final int startX = BlockBase.BLOCK_SIZE;
 final int startY = BlockBase.BLOCK_SIZE + 1 def player = new Player(startX, startY, gameContext, new NullInputComponent())
 gameContext.addEntity(player)
 
 def viewPort = new NullViewPort()
 gameContext.setViewPort(viewPort)
 
 when: "Time is advanced"
 10.times { player.update(); viewPort.update(); }
 
 then: "The player hasn't moved"
 player.getX() == startX
 player.getY() == startY
 }
 The level is made visible in the test
  • 26. def "A player standing still on a block won't move anywhere with visual aids"() {
 given: "A simple level with some ground"
 def level = new StringLevelBuilder().buildLevel((String[]) [
 " ",
 " ",
 "III"].toArray())
 def gameContext = new GameContext(level)
 
 and: "The player standing on top of it"
 final int startX = BlockBase.BLOCK_SIZE;
 final int startY = BlockBase.BLOCK_SIZE + 1 def player = new Player(startX, startY, gameContext, new NullInputComponent())
 gameContext.addEntity(player)
 
 def viewPort = new SwingViewPort(gameContext)
 gameContext.setViewPort(viewPort)
 
 when: "Time is advanced"
 10.times { slomo { player.update(); viewPort.update(); } }
 
 then: "The player hasn't moved"
 player.getX() == startX
 player.getY() == startY
 } A real view port Slow down!
  • 27. Conclusions • How was Spock useful? – Test names and GWT labels really helped – Groovy reduced the bloat – Features for parameterized tests useful for some tests whereas mocking and stubbing remained unutilized in this case • Game testing – The world is the test data - so make sure you can generate it easily – Conciseness is crucial - because of all the math expressions – One frame at the time - turned out to be a viable strategy for handling 60 FPS in unit tests – Games are huge state machines - virtually no stubbing and mocking in the core code – The Component pattern - is more or less a must for testability – Use visual aids - and write the unit tests so that they can run with real viewports – Off-by-one errors - will torment you – Test-driving is hard - because of the floating point math (the API can be teased out, but knowing exactly where a player should be after falling and sliding for 15 frames is better determined by using an actual viewport)
  • 28. Getting Spock apply plugin: 'java'
 
 repositories {
 mavenCentral()
 }
 
 dependencies {
 testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
 }
  • 29. Spock Reports – Overview
  • 30. Spock Reports – Details