SlideShare a Scribd company logo
1 of 30
Download to read offline
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

More Related Content

What's hot

Java Puzzle
Java PuzzleJava Puzzle
Java PuzzleSFilipp
 
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
 
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 KotlinDmitry Pranchuk
 
Java_practical_handbook
Java_practical_handbookJava_practical_handbook
Java_practical_handbookManusha 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 VisageStephen 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 matterDawid 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 titaniumAxway 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 184Mahmoud Samir Fayed
 
JEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistJEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistAnton Arhipov
 
JPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream APIJPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream APItvaleev
 
Predictably
PredictablyPredictably
Predictablyztellman
 
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 185Mahmoud 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 Chinjaxconf
 
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 DumpsAndrei Pangin
 

What's hot (20)

Java Puzzle
Java PuzzleJava Puzzle
Java Puzzle
 
Java Language fundamental
Java Language fundamentalJava Language fundamental
Java Language fundamental
 
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
 
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
 
Java_practical_handbook
Java_practical_handbookJava_practical_handbook
Java_practical_handbook
 
Angular2 rxjs
Angular2 rxjsAngular2 rxjs
Angular2 rxjs
 
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
 
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
 
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
 
Spock framework
Spock frameworkSpock framework
Spock framework
 
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)
 
Java puzzles
Java puzzlesJava puzzles
Java puzzles
 
Codestrong 2012 breakout session hacking titanium
Codestrong 2012 breakout session   hacking titaniumCodestrong 2012 breakout session   hacking titanium
Codestrong 2012 breakout session hacking titanium
 
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
 
JEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with JavassistJEEConf 2017 - Having fun with Javassist
JEEConf 2017 - Having fun with Javassist
 
JPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream APIJPoint 2016 - Валеев Тагир - Странности Stream API
JPoint 2016 - Валеев Тагир - Странности Stream API
 
Predictably
PredictablyPredictably
Predictably
 
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
 
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
 
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
 

Similar to Testing a 2D Platformer with Spock

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 GebChristian 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 codeLaurence Svekis ✔
 
Making Games in JavaScript
Making Games in JavaScriptMaking Games in JavaScript
Making Games in JavaScriptSam 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 HorizonAlex 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 212Mahmoud Samir Fayed
 
Pocket Talk; Spock framework
Pocket Talk; Spock frameworkPocket Talk; Spock framework
Pocket Talk; Spock frameworkInfoway
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good TestsTomek Kaczanowski
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 SpringKiyotaka 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 TestsTomek Kaczanowski
 
Intro to Game Programming
Intro to Game ProgrammingIntro to Game Programming
Intro to Game ProgrammingRichard 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 SwiftGiordano 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
 

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
 
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
 
Making Games in JavaScript
Making Games in JavaScriptMaking Games in JavaScript
Making Games in 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...
Fullstack Conference - Proxies before proxies: The hidden gems of Javascript...
 
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
 
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
 
Pocket Talk; Spock framework
Pocket Talk; Spock frameworkPocket Talk; Spock framework
Pocket Talk; Spock framework
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests
 
JJUG CCC 2011 Spring
JJUG CCC 2011 SpringJJUG CCC 2011 Spring
JJUG CCC 2011 Spring
 
Unity3 d devfest-2014
Unity3 d devfest-2014Unity3 d devfest-2014
Unity3 d devfest-2014
 
Introduction to Groovy
Introduction to GroovyIntroduction to Groovy
Introduction to Groovy
 
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...
 
HTML5 - Daha Flash bir web?
HTML5 - Daha Flash bir web?HTML5 - Daha Flash bir web?
HTML5 - Daha Flash bir web?
 
W-JAX 09 - Lift
W-JAX 09 - LiftW-JAX 09 - Lift
W-JAX 09 - Lift
 
Game dev 101 part 3
Game dev 101 part 3Game dev 101 part 3
Game dev 101 part 3
 
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
 
Intro to Game Programming
Intro to Game ProgrammingIntro to Game Programming
Intro to Game Programming
 
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
 
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?
 
Jggug 2010 330 Grails 1.3 観察
Jggug 2010 330 Grails 1.3 観察Jggug 2010 330 Grails 1.3 観察
Jggug 2010 330 Grails 1.3 観察
 

Recently uploaded

TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc
 
Deploy with confidence: VMware Cloud Foundation 5.1 on next gen Dell PowerEdg...
Deploy with confidence: VMware Cloud Foundation 5.1 on next gen Dell PowerEdg...Deploy with confidence: VMware Cloud Foundation 5.1 on next gen Dell PowerEdg...
Deploy with confidence: VMware Cloud Foundation 5.1 on next gen Dell PowerEdg...Principled Technologies
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century educationjfdjdjcjdnsjd
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?Igalia
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CVKhem
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodJuan lago vázquez
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAndrey Devyatkin
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...DianaGray10
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsRoshan Dwivedi
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)wesley chun
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Scriptwesley chun
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingEdi Saputra
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FMESafe Software
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...apidays
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfsudhanshuwaghmare1
 

Recently uploaded (20)

TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
Deploy with confidence: VMware Cloud Foundation 5.1 on next gen Dell PowerEdg...
Deploy with confidence: VMware Cloud Foundation 5.1 on next gen Dell PowerEdg...Deploy with confidence: VMware Cloud Foundation 5.1 on next gen Dell PowerEdg...
Deploy with confidence: VMware Cloud Foundation 5.1 on next gen Dell PowerEdg...
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Automating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps ScriptAutomating Google Workspace (GWS) & more with Apps Script
Automating Google Workspace (GWS) & more with Apps Script
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 

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 alexander.tarlinder@crisp.se
  • 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
  • 13. About Spock https://github.com/spockframework 2009 2010 2011 2012 2013 2014 2015 2016 0.1 0.7 1.0
  • 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