SlideShare uma empresa Scribd logo
1 de 30
Baixar para ler 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

Mais conteúdo relacionado

Mais procurados

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
 

Mais procurados (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
 

Semelhante a 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
 

Semelhante a 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 観察
 

Último

The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfEnterprise Knowledge
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherRemote DBA Services
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking MenDelhi Call girls
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking MenDelhi Call girls
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEarley Information Science
 
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
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
Tech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfTech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfhans926745
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
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
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProduct Anonymous
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUK Journal
 

Último (20)

The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men08448380779 Call Girls In Civil Lines Women Seeking Men
08448380779 Call Girls In Civil Lines Women Seeking Men
 
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptxEIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
EIS-Webinar-Prompt-Knowledge-Eng-2024-04-08.pptx
 
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
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Tech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdfTech Trends Report 2024 Future Today Institute.pdf
Tech Trends Report 2024 Future Today Institute.pdf
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
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)
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.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