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

Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rick Flair
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESmohitsingh558521
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brandgvaughan
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 3652toLead Limited
 
What is Artificial Intelligence?????????
What is Artificial Intelligence?????????What is Artificial Intelligence?????????
What is Artificial Intelligence?????????blackmambaettijean
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity PlanDatabarracks
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxLoriGlavin3
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsNathaniel Shimoni
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .Alan Dix
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteDianaGray10
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersRaghuram Pandurangan
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxLoriGlavin3
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningLars Bell
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc
 

Último (20)

Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...Rise of the Machines: Known As Drones...
Rise of the Machines: Known As Drones...
 
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICESSALESFORCE EDUCATION CLOUD | FEXLE SERVICES
SALESFORCE EDUCATION CLOUD | FEXLE SERVICES
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
WordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your BrandWordPress Websites for Engineers: Elevate Your Brand
WordPress Websites for Engineers: Elevate Your Brand
 
Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365Ensuring Technical Readiness For Copilot in Microsoft 365
Ensuring Technical Readiness For Copilot in Microsoft 365
 
What is Artificial Intelligence?????????
What is Artificial Intelligence?????????What is Artificial Intelligence?????????
What is Artificial Intelligence?????????
 
How to write a Business Continuity Plan
How to write a Business Continuity PlanHow to write a Business Continuity Plan
How to write a Business Continuity Plan
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptxDigital Identity is Under Attack: FIDO Paris Seminar.pptx
Digital Identity is Under Attack: FIDO Paris Seminar.pptx
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
Time Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directionsTime Series Foundation Models - current state and future directions
Time Series Foundation Models - current state and future directions
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
Take control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test SuiteTake control of your SAP testing with UiPath Test Suite
Take control of your SAP testing with UiPath Test Suite
 
Generative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information DevelopersGenerative AI for Technical Writer or Information Developers
Generative AI for Technical Writer or Information Developers
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptxThe Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
The Role of FIDO in a Cyber Secure Netherlands: FIDO Paris Seminar.pptx
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
DSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine TuningDSPy a system for AI to Write Prompts and Do Fine Tuning
DSPy a system for AI to Write Prompts and Do Fine Tuning
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data PrivacyTrustArc Webinar - How to Build Consumer Trust Through Data Privacy
TrustArc Webinar - How to Build Consumer Trust Through Data Privacy
 

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