SlideShare une entreprise Scribd logo
1  sur  97
Télécharger pour lire hors ligne
IDIOMATIC SPOCK
@rfletcherEWRob Fletcher
SPOCK WITH STYLE
REFACTOR WITH HELPER METHODS
def "user can purchase uniform"() {
given:
to LoginPage
loginForm.with {
email = "aaron.pyle@teleworm.us"
password = "ahroh6tie4oCh"
submit()
}
!
and:
to ProductPage, "Starfleet uniform (TOS)"
addToBasketButton.click()
!
and:
at BasketPage
checkoutButton.click()
!
expect:
at CheckoutPage
when:
checkoutForm.with {
street = "2415 Woodland Avenue"
city = "Garyville"
state = "LA"
zip = "70051"
cardType = "Mastercard"
cardNo = "5555700540812370"
expiry = "11/2017"
cvc = "003"
submit()
}
!
then:
message == "Purchase successful"
and:
1 * paymentMock.charge("USD", 65)
}
def "user can purchase uniform"() {
given:
loggedInAs user
addedToBasket product
!
when:
completeCheckout user
!
then:
message == "Purchase successful"
and:
1 * paymentMock.charge(product.price)
}
@Shared user = [
email: "aaron.pyle@teleworm.us"
password: "ahroh6tie4oCh"
street: "2415 Woodland Avenue"
city: "Garyville"
state: "LA"
zip: "70051"
cardType: "Mastercard"
cardNo: "5555700540812370"
expiry: "11/2017"
cvc: "003"
]
!
@Shared product = [
name: "Starfleet uniform (TOS)"
price: ["USD", 65]
]
SHARED HELPER METHODS
@Category(GebSpec)
class StoreInteractions {
void loggedInAs(user) {}
!
void addedToBasket(String productName) {}
!
void completeCheckout(user) {}
}
!
@Mixin(StoreInteractions)
class CheckoutSpec extends GebSpec {}
…IN GROOVY 2.3
trait StoreInteractions {
void loggedInAs(user) {}
!
void addedToBasket(String productName) {}
!
void completeCheckout(user) {}
}
!
class CheckoutSpec extends GebSpec
implements StoreInteractions {}
FUNCTIONAL GROOVY FOR ASSERTIONS
when:
def results = ships.findByAllegiance("Federation")
!
then:
results.size() == 3
results[0].allegiance == "Federation"
results[1].allegiance == "Federation"
results[2].allegiance == "Federation"
when:
def results = ships.findByAllegiance("Federation")
!
then:
results.every {
it.allegiance == "Federation"
}
Condition not satisfied:
!
results.every { it.allegiance == "Federation" }
| |
| false
[Gr'oth, Enterprise, Constitution, Constellation, M'Char, Haakona]
when:
def results = ships.findByAllegiance("Federation")
!
then:
results.allegiance.every {
it == "Federation"
}
Condition not satisfied:
!
results.allegiance.every { it == "Federation" }
| | |
| | false
| [Klingon, Federation, Federation, Federation, Klingon, Romulan]
[Gr'oth, Enterprise, Constitution, Constellation, M'Char, Haakona]
ONE (LOGICAL) ASSERTION
The Single Responsibility Principle for tests
def "can list ships by allegiance"() {
when:
def results = ships.findByAllegiance("Federation")
!
then:
results.allegiance.every {
it == "Federation"
}
!
and:
results instanceof ImmutableList
}
BLOCK GRAMMAR
when/then or given/expect
when:
crew << "Kirk" << "Bones" << "Scotty"
!
then:
"Science officer" in crew.openPositions
given:
crew << "Kirk" << "Bones" << "Scotty"
!
expect:
"Science officer" in crew.openPositions
given:
crew << "Kirk"
!
when:
crew << "Picard"
!
then:
thrown TooManyCaptainsException
MOCKS & STUBS
The right tool for the job
def paymentMock = Mock(PaymentService)
!
def "payment is taken at the end of checkout"() {
given:
loggedInAs user
addedToBasket product
!
when:
completeCheckout user
!
then:
1 * paymentMock.charge(product.price)
}
def "presents ships as a bullet list"() {
given:
def shipList = ["Enterprise", "Constellation"]
!
and:
def shipStore = Mock(ShipStore)
!
when:
def output = ships.render()
!
then:
1 * shipStore.list() >> shipList
!
and:
$(output).find("ul > li")*.text() == shipList
}
def "presents ships as a bullet list"() {
given:
def shipList = ["Enterprise", "Constellation"]
!
and:
def shipStore = Stub(ShipStore) {
list() >> shipList
}
!
when:
def output = ships.render()
!
then:
$(output).find("ul > li")*.text() == shipList
}
CLEANING UP RESOURCES
def handle = DBI.open("jdbc:h2:mem:test")
@Subject ships = handle.attach(ShipStore)
!
def setup() {
ships.createTable()
}
!
def cleanup() {
ships.dropTable()
handle.close()
}
@AutoCleanup handle = DBI.open("jdbc:h2:mem:test")
!
@AutoCleanup("dropTable")
@Subject ships = handle.attach(ShipStore)
!
def setup() {
ships.createTable()
}
@Shared
@AutoCleanup handle = DBI.open("jdbc:h2:mem:test")
!
@Shared
@AutoCleanup("dropTable")
@Subject ships = handle.attach(ShipStore)
!
def setupSpec() {
ships.createTable()
}
ENFORCE PRECONDITIONS
Another use for the expect block
@Shared handle = DBI.open("jdbc:h2:mem:test")
!
def "writes to the database when data is added"() {
given:
def user = new User(name: "Spock")
!
when:
userStore.persist(user)
!
then:
selectInt("select count(*) from user") == 1
}
@Shared handle = DBI.open("jdbc:h2:mem:test")
!
def "writes to the database when data is added"() {
expect:
selectInt("select count(*) from user") == 0
!
given:
def user = new User(name: "Spock")
!
when:
userStore.persist(user)
!
then:
selectInt("select count(*) from user") == 1
}
def "a completed job cannot be restarted"() {
given: "all tasks succeed"
task1.execute(_) >> new DefaultTaskResult(SUCCEEDED)
task2.execute(_) >> new DefaultTaskResult(SUCCEEDED)
task3.execute(_) >> new DefaultTaskResult(SUCCEEDED)
!
and: "the job has been run"
def jobExecution = launchJob()
!
expect: "the job to have completed successfully"
jobExecution.exitStatus == ExitStatus.COMPLETED
!
when: "the job is restarted"
resumeJob jobExecution
!
then: "an exception is thrown"
thrown JobInstanceAlreadyCompleteException
}
ACCESS PREVIOUS VALUES
@Subject stack = new Stack()
!
def "size increases when we add items to a stack"() {
when:
stack.push "foo"
!
then:
stack.size() == 1
}
@Subject stack = new Stack()
!
def "size increases when we add items to a stack"() {
given:
def oldSize = stack.size()
!
when:
stack.push "foo"
!
then:
stack.size() == oldSize + 1
}
@Subject stack = new Stack()
!
def "size increases when we add items to a stack"() {
when:
stack.push "foo"
!
then:
stack.size() == old(stack.size()) + 1
}
TESTS AS DOCUMENTATION
—Abelson and Sussman
“Programs must be written for people
to read, and only incidentally for
machines to execute.”
“Always code as if the guy who ends up
maintaining your code will be a violent
psychopath who knows where you live.”
—M. Golding
RED… GREEN… REFACTOR
Write a failing
test
Write
enough code
to make test
pass
Refactor
code while
keeping tests
passing
RED… AMBER… GREEN… REFACTOR
Write a failing
test
Write
enough code
to make test
pass
Refactor
code while
keeping tests
passing
Ensure
correct failure
& good
diagnostic
THE PRIME DIRECTIVE
NEVERTRUST A TEST YOU
HAVEN'T SEEN FAIL
USE DOCUMENTATION ANNOTATIONS
@Subject ship = new Starship("Enterprise")
!
@Subject(Starship)
!
@Issue("https://github.com/robfletcher/betamax/issues/37")
!
@See("http://atompunkera.tumblr.com/")
!
@Title("a readable title")
!
@Narrative("""as a developer
I want to die
so I can end the pain""")
EFFECTIVE UNROLLING
@Unroll
def "rejects an input value of '#input'"()
@Unroll("#input converted to #format is '#output'")
def "translates strings to another format"()
@Unroll
class ParameterizedSpec extends Specification
USE UNROLL DESCRIPTIONS
def "can tell if the string '#string' is an integer or not"() {
expect:
string.isInteger() == shouldBeInteger
!
where:
string | shouldBeInteger
"ABC" | false
"123" | true
"1.2" | false
"1 2" | false
"12a" | false
}
@Unroll
def "the string '#string' is #description"() {
expect:
string.isInteger() == expected
!
where:
string | expected
"ABC" | false
"123" | true
"1.2" | false
"1 2" | false
"12a" | false
!
description = expected ? "an integer" : "not an integer"
}
@Unroll("the string '#string' is #description")
def "identifies strings that are integers"() {
expect:
string.isInteger() == expected
!
where:
string | expected
"ABC" | false
"123" | true
"1.2" | false
"1 2" | false
"12a" | false
!
description = expected ? "an integer" : "not an integer"
}
SEPARATE TEST DATA FROM TEST LOGIC
where blocks without @Unroll
def "passes data from stream to callback"() {
given:
def callback = Mock(Function)
!
and:
service.source = Stub(StreamSource) {
connect() >> Observable.from("foo", "bar", "baz")
}
!
when:
service.readStream(callback)
!
then:
1 * callback.apply("foo")
1 * callback.apply("bar")
1 * callback.apply("baz")
}
def "passes data from stream to callback"() {
given:
def callback = Mock(Function)
!
and:
service.source = Stub(StreamSource) {
connect() >> Observable.from(*data)
}
!
when:
service.readStream(callback)
!
then:
data.each {
1 * callback.apply(it)
}
!
where:
data = ["foo", "bar", "baz"]
}
SPOCK ANTI-PATTERNS
TEST ORGANIZATION
Think behavior not units of code
@AutoCleanup
@Shared handle = DBI.open("jdbc:h2:mem:test")
@Subject ships = handle.attach(ShipStore)
!
def setup() {
ships.createTable()
!
insert("ship", "Enterprise", "Federation")
insert("ship", "Constellation", "Federation")
insert("ship", "M'Char", "Klingon")
}
!
def "can find by allegiance"() {
when:
def results = ships.findByAllegiance("Federation")
!
then:
results.allegiance.every {
it == "Federation"
}
}
!
def "can find by name"() {
when:
def result = ships.findByName(name)
!
then:
result.name == name
!
where:
name = "M'Char"
}
!
def "inserting writes to the database"() {
when:
ships << new Ship(name, allegiance)
!
then:
count("ship", name: name) == 1
!
where:
name = "Haakona"
allegiance = "Romulan"
}
TAUTOLOGICAL TESTS
FALSE MONIKER TESTING
@Subject def ships = new ShipStore()
!
def "can find ships by allegiance ordered by age"() {
given:
ships <<
new Ship("Enterprise", "Federation", Year.of(2245)) <<
new Ship("Adventure", "Federation", Year.of(2376)) <<
new Ship("Haakona", "Romulan", Year.of(2357))
!
expect:
def matches = ships.findByAllegianceNewestFirst("Federation")
matches.name == ["Enterprise", "Haakona", "Adventure"]
}
def "can find ships by allegiance ordered by age"() {
given:
ships <<
new Ship("Enterprise", "Federation", Year.of(2245)) <<
new Ship("Adventure", "Federation", Year.of(2376)) <<
new Ship("Haakona", "Romulan", Year.of(2357))
!
expect:
def matches = ships.findByAllegianceNewestFirst("Federation")
matches.allegiance.every { it == "Federation" }
matches.enteredService == matches.enteredService.sort().reverse()
}
INTERROGATING INTERNAL STATE
WHEN BLOCK BLOAT
def "items can be viewed in the basket after selection"() {
when:
products.each {
to ProductPage, it
addToBasket()
}
to BasketPage
!
then:
basket.size() == 3
basket.items.title == products
!
where:
products = ["Starfleet uniform", "Tricorder", "Phaser"]
}
def "items can be viewed in the basket after selection"() {
given:
products.each {
to ProductPage, it
addToBasket()
}
!
when:
to BasketPage
!
then:
basket.size() == 3
basket.items.title == products
!
where:
products = ["Starfleet uniform", "Tricorder", "Phaser"]
}
FAIL-FAST ASSERTIONS
STEPWISE SPECS
@Stepwise
class StackSpec extends Specification {
!
@Shared @Subject stack = new Stack()
@Shared value = "foo"
!
def "can push to the stack"() {
expect:
stack.push(value) == value
}
!
def "stack should have content"() {
expect:
stack.peek() == value
}
!
def "can pop from the stack"() {
expect:
stack.pop() == value
}
!
def "the stack should be empty"() {
expect:
stack.empty()
}
}
THINKING OUTSIDE THE BOX
Strange new worlds of testing
TCK SPECIFICATIONS
One specification, multiple implementations
abstract class ShipStoreSpec<T extends ShipStore> extends Specification {
!
@Subject T ships
!
def "can insert a new ship"() {
when:
ships.insert(new Ship("Enterprise", "Federation"))
!
then:
ships.list().size() == old(ships.list().size()) + 1
}
!
def "can find ships by allegiance"() {
// ...
}
}
class MemoryShipStoreSpec extends ShipStoreSpec<MemoryShipStore> {
def setup() {
ships = new MemoryShipStore()
}
}
class PersistentShipStoreSpec extends ShipStoreSpec<PersistentShipStore> {
!
@AutoCleanup Handle handle
!
def setup() {
handle = DBI.open("jdbc:h2:mem:test")
ships = handle.attach(PersistentShipStore)
ships.createTable()
}
!
def cleanup() {
ships.dropTable()
}
}
DATA-DRIVEN PARAMETERIZATION
Beyond data tables
@Unroll
void "the #table table has a primary key"() {
expect:
with(handle.connection) { Connection connection ->
connection.metaData.getPrimaryKeys(null, null, table).next()
}
!
where:
table << tableNames
}
@Shared @AutoCleanup Handle handle
@Shared Collection<String> tableNames = []
!
def setupSpec() {
handle.connection.with { connection ->
def rs = connection.metaData.getTables(null, null, "%", ["TABLE"] as String[])
while (rs.next()) {
tableNames << rs.getString(3)
}
}
}
CONDITIONAL TESTS
@IgnoreIf({ javaVersion <= 1.7 })
!
@IgnoreIf({ env.SKIP_INTEGRATION_TESTS == "yes" })
!
@IgnoreIf({ properties."os.name" ==~ /Windows.*/) })
!
static boolean isReachable(String url) {
try {
def connection = url.toURL().openConnection()
connection.connectTimeout = 1000
connection.connect()
true
} catch (IOException ex) {
false
}
}
!
@IgnoreIf({ !isReachable("http://www.netflix.bv/") })
class NeedsRealNetworkConnectionSpec extends Specification {
!
static boolean isReachable(String url) {
try {
def connection = url.toURL().openConnection()
connection.connectTimeout = 1000
connection.connect()
true
} catch (IOException ex) {
false
}
}
!
@IgnoreIf({ !isReachable("http://www.netflix.bv/") })
class NeedsRealNetworkConnectionSpec extends Specification {
@Memoized
TEST JAVASCRIPT WITH NASHORN
@Shared engine = new ScriptEngineManager().getEngineByName("nashorn")
@Shared @Subject moment
!
def setupSpec() {
getClass().getResourceAsStream("/moment.js").withReader { reader ->
engine.eval reader
}
!
moment = engine.invokeFunction("moment")
}
@Unroll
def "The date #date in friendly format is #expectedResult"() {
expect:
engine.invokeMethod(moment, "from", date.toString()) == expectedResult
!
where:
date | expectedResult
now().plusDays(2) | "2 days ago"
now().plusMinutes(2) | "2 minutes ago"
now() | "a few seconds ago"
now().minusDays(1) | "in a day"
}
COOL FEATURES
Fascinating
UNROLL AUTOCOMPLETION
@Unroll("#string converted to #style is '#ex'")
def "can convert case"() {
expect:
string.convert(style) == expected
!
where:
string | style || expected
"foo bar baz" | CAMEL || "FooBarBaz"
"foo bar baz" | KEBAB || "foo-bar-baz"
"foo bar baz" | SNAKE || "foo_bar_baz"
}
INPUT & OUTPUT PARAMETERS
def "can convert case"() {
expect:
string.convert(style) == expected
!
where:
string | style || expected
"foo bar baz" | CAMEL || "FooBarBaz"
"foo bar baz" | KEBAB || "foo-bar-baz"
"foo bar baz" | SNAKE || "foo_bar_baz"
}
TYPED PARAMETERS
def "can convert case"(String string, CaseFormat style, String expected) {
expect:
string.convert(style) == expected
!
where:
string | style || expected
"foo bar baz" | CAMEL || "FooBarBaz"
"foo bar baz" | KEBAB || "foo-bar-baz"
"foo bar baz" | SNAKE || "foo_bar_baz"
}
JUNIT RULES
@Rule TemporaryFolder temporaryFolder = new TemporaryFolder()
!
def "can copy a resource from classpath to a file"() {
given:
def resource = getClass().getResource("/test.txt")
def file = temporaryFolder.newFile()
!
when:
resource.withReader { file << it }
!
then:
file.text == resource.text
}
HAMCREST MATCHERS
STUPID SPOCK TRICKS
IMPORT ALIASING
import java.lang.Void as Should
!
class MyBddSpec {
!
Should "exhibit some behavior"() {
// ...
}
!
}
MULTIPLE WHEN/THEN BLOCKS
given:
def stack = new Stack()
!
when:
stack.push "foo"
!
then:
stack.pop() == "foo"
!
when:
stack.pop()
!
then:
thrown EmptyStackException

Contenu connexe

Tendances

Spock Testing Framework - The Next Generation
Spock Testing Framework - The Next GenerationSpock Testing Framework - The Next Generation
Spock Testing Framework - The Next GenerationBTI360
 
JavaOne 2015 - Having fun with Javassist
JavaOne 2015 - Having fun with JavassistJavaOne 2015 - Having fun with Javassist
JavaOne 2015 - Having fun with JavassistAnton Arhipov
 
Interceptors: Into the Core of Pedestal
Interceptors: Into the Core of PedestalInterceptors: Into the Core of Pedestal
Interceptors: Into the Core of PedestalKent Ohashi
 
ES6 in Production [JSConfUY2015]
ES6 in Production [JSConfUY2015]ES6 in Production [JSConfUY2015]
ES6 in Production [JSConfUY2015]Guillermo Paz
 
BSDM with BASH: Command Interpolation
BSDM with BASH: Command InterpolationBSDM with BASH: Command Interpolation
BSDM with BASH: Command InterpolationWorkhorse Computing
 
Spock: Test Well and Prosper
Spock: Test Well and ProsperSpock: Test Well and Prosper
Spock: Test Well and ProsperKen Kousen
 
Smarter Testing with Spock
Smarter Testing with SpockSmarter Testing with Spock
Smarter Testing with SpockDmitry Voloshko
 
Building native Android applications with Mirah and Pindah
Building native Android applications with Mirah and PindahBuilding native Android applications with Mirah and Pindah
Building native Android applications with Mirah and PindahNick Plante
 
Alfresco the clojure way
Alfresco the clojure wayAlfresco the clojure way
Alfresco the clojure wayCarlo Sciolla
 
Hypers and Gathers and Takes! Oh my!
Hypers and Gathers and Takes! Oh my!Hypers and Gathers and Takes! Oh my!
Hypers and Gathers and Takes! Oh my!Workhorse Computing
 
Minimal MVC in JavaScript
Minimal MVC in JavaScriptMinimal MVC in JavaScript
Minimal MVC in JavaScriptMosky Liu
 
AST Transformations
AST TransformationsAST Transformations
AST TransformationsHamletDRC
 
Oral presentation v2
Oral presentation v2Oral presentation v2
Oral presentation v2Yeqi He
 
ECMAScript 6: A Better JavaScript for the Ambient Computing Era
ECMAScript 6: A Better JavaScript for the Ambient Computing EraECMAScript 6: A Better JavaScript for the Ambient Computing Era
ECMAScript 6: A Better JavaScript for the Ambient Computing EraAllen Wirfs-Brock
 

Tendances (19)

Memory Manglement in Raku
Memory Manglement in RakuMemory Manglement in Raku
Memory Manglement in Raku
 
Spock Testing Framework - The Next Generation
Spock Testing Framework - The Next GenerationSpock Testing Framework - The Next Generation
Spock Testing Framework - The Next Generation
 
node ffi
node ffinode ffi
node ffi
 
JavaOne 2015 - Having fun with Javassist
JavaOne 2015 - Having fun with JavassistJavaOne 2015 - Having fun with Javassist
JavaOne 2015 - Having fun with Javassist
 
Interceptors: Into the Core of Pedestal
Interceptors: Into the Core of PedestalInterceptors: Into the Core of Pedestal
Interceptors: Into the Core of Pedestal
 
ES6 in Production [JSConfUY2015]
ES6 in Production [JSConfUY2015]ES6 in Production [JSConfUY2015]
ES6 in Production [JSConfUY2015]
 
BSDM with BASH: Command Interpolation
BSDM with BASH: Command InterpolationBSDM with BASH: Command Interpolation
BSDM with BASH: Command Interpolation
 
Spock: Test Well and Prosper
Spock: Test Well and ProsperSpock: Test Well and Prosper
Spock: Test Well and Prosper
 
Smarter Testing with Spock
Smarter Testing with SpockSmarter Testing with Spock
Smarter Testing with Spock
 
Building native Android applications with Mirah and Pindah
Building native Android applications with Mirah and PindahBuilding native Android applications with Mirah and Pindah
Building native Android applications with Mirah and Pindah
 
Metadata-driven Testing
Metadata-driven TestingMetadata-driven Testing
Metadata-driven Testing
 
Effective ES6
Effective ES6Effective ES6
Effective ES6
 
Alfresco the clojure way
Alfresco the clojure wayAlfresco the clojure way
Alfresco the clojure way
 
Hypers and Gathers and Takes! Oh my!
Hypers and Gathers and Takes! Oh my!Hypers and Gathers and Takes! Oh my!
Hypers and Gathers and Takes! Oh my!
 
Minimal MVC in JavaScript
Minimal MVC in JavaScriptMinimal MVC in JavaScript
Minimal MVC in JavaScript
 
AST Transformations
AST TransformationsAST Transformations
AST Transformations
 
Findbin libs
Findbin libsFindbin libs
Findbin libs
 
Oral presentation v2
Oral presentation v2Oral presentation v2
Oral presentation v2
 
ECMAScript 6: A Better JavaScript for the Ambient Computing Era
ECMAScript 6: A Better JavaScript for the Ambient Computing EraECMAScript 6: A Better JavaScript for the Ambient Computing Era
ECMAScript 6: A Better JavaScript for the Ambient Computing Era
 

Similaire à Idiomatic Spock

Slides chapter3part1 ruby-forjavaprogrammers
Slides chapter3part1 ruby-forjavaprogrammersSlides chapter3part1 ruby-forjavaprogrammers
Slides chapter3part1 ruby-forjavaprogrammersGiovanni924
 
Writing a compiler in go
Writing a compiler in goWriting a compiler in go
Writing a compiler in goYusuke Kita
 
A Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert FornalA Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert FornalQA or the Highway
 
Security Challenges in Node.js
Security Challenges in Node.jsSecurity Challenges in Node.js
Security Challenges in Node.jsWebsecurify
 
Kotlin Basics - Apalon Kotlin Sprint Part 2
Kotlin Basics - Apalon Kotlin Sprint Part 2Kotlin Basics - Apalon Kotlin Sprint Part 2
Kotlin Basics - Apalon Kotlin Sprint Part 2Kirill Rozov
 
Async Redux Actions With RxJS - React Rally 2016
Async Redux Actions With RxJS - React Rally 2016Async Redux Actions With RxJS - React Rally 2016
Async Redux Actions With RxJS - React Rally 2016Ben Lesh
 
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
 
Enter the app era with ruby on rails (rubyday)
Enter the app era with ruby on rails (rubyday)Enter the app era with ruby on rails (rubyday)
Enter the app era with ruby on rails (rubyday)Matteo Collina
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on AndroidSven Haiges
 
JavaScript Unit Testing with Jasmine
JavaScript Unit Testing with JasmineJavaScript Unit Testing with Jasmine
JavaScript Unit Testing with JasmineRaimonds Simanovskis
 
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
 
C# 6 and 7 and Futures 20180607
C# 6 and 7 and Futures 20180607C# 6 and 7 and Futures 20180607
C# 6 and 7 and Futures 20180607Kevin Hazzard
 
Lego: A brick system build by scala
Lego: A brick system build by scalaLego: A brick system build by scala
Lego: A brick system build by scalalunfu zhong
 

Similaire à Idiomatic Spock (20)

Slides chapter3part1 ruby-forjavaprogrammers
Slides chapter3part1 ruby-forjavaprogrammersSlides chapter3part1 ruby-forjavaprogrammers
Slides chapter3part1 ruby-forjavaprogrammers
 
Writing a compiler in go
Writing a compiler in goWriting a compiler in go
Writing a compiler in go
 
A Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert FornalA Lifecycle Of Code Under Test by Robert Fornal
A Lifecycle Of Code Under Test by Robert Fornal
 
Security Challenges in Node.js
Security Challenges in Node.jsSecurity Challenges in Node.js
Security Challenges in Node.js
 
Benefits of Kotlin
Benefits of KotlinBenefits of Kotlin
Benefits of Kotlin
 
Kotlin Basics - Apalon Kotlin Sprint Part 2
Kotlin Basics - Apalon Kotlin Sprint Part 2Kotlin Basics - Apalon Kotlin Sprint Part 2
Kotlin Basics - Apalon Kotlin Sprint Part 2
 
Testing with Kotlin
Testing with KotlinTesting with Kotlin
Testing with Kotlin
 
Async Redux Actions With RxJS - React Rally 2016
Async Redux Actions With RxJS - React Rally 2016Async Redux Actions With RxJS - React Rally 2016
Async Redux Actions With RxJS - React Rally 2016
 
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
 
Json the-x-in-ajax1588
Json the-x-in-ajax1588Json the-x-in-ajax1588
Json the-x-in-ajax1588
 
Swift core
Swift coreSwift core
Swift core
 
Swift Study #7
Swift Study #7Swift Study #7
Swift Study #7
 
Specs2
Specs2Specs2
Specs2
 
Enter the app era with ruby on rails (rubyday)
Enter the app era with ruby on rails (rubyday)Enter the app era with ruby on rails (rubyday)
Enter the app era with ruby on rails (rubyday)
 
CouchDB on Android
CouchDB on AndroidCouchDB on Android
CouchDB on Android
 
JavaScript Unit Testing with Jasmine
JavaScript Unit Testing with JasmineJavaScript Unit Testing with Jasmine
JavaScript Unit Testing with Jasmine
 
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
 
SDC - Einführung in Scala
SDC - Einführung in ScalaSDC - Einführung in Scala
SDC - Einführung in Scala
 
C# 6 and 7 and Futures 20180607
C# 6 and 7 and Futures 20180607C# 6 and 7 and Futures 20180607
C# 6 and 7 and Futures 20180607
 
Lego: A brick system build by scala
Lego: A brick system build by scalaLego: A brick system build by scala
Lego: A brick system build by scala
 

Plus de GR8Conf

DevOps Enabling Your Team
DevOps Enabling Your TeamDevOps Enabling Your Team
DevOps Enabling Your TeamGR8Conf
 
Creating and testing REST contracts with Accurest Gradle
Creating and testing REST contracts with Accurest Gradle Creating and testing REST contracts with Accurest Gradle
Creating and testing REST contracts with Accurest Gradle GR8Conf
 
Mum, I want to be a Groovy full-stack developer
Mum, I want to be a Groovy full-stack developerMum, I want to be a Groovy full-stack developer
Mum, I want to be a Groovy full-stack developerGR8Conf
 
Metaprogramming with Groovy
Metaprogramming with GroovyMetaprogramming with Groovy
Metaprogramming with GroovyGR8Conf
 
Scraping with Geb
Scraping with GebScraping with Geb
Scraping with GebGR8Conf
 
How to create a conference android app with Groovy and Android
How to create a conference android app with Groovy and AndroidHow to create a conference android app with Groovy and Android
How to create a conference android app with Groovy and AndroidGR8Conf
 
Ratpack On the Docks
Ratpack On the DocksRatpack On the Docks
Ratpack On the DocksGR8Conf
 
Groovy Powered Clean Code
Groovy Powered Clean CodeGroovy Powered Clean Code
Groovy Powered Clean CodeGR8Conf
 
Cut your Grails application to pieces - build feature plugins
Cut your Grails application to pieces - build feature pluginsCut your Grails application to pieces - build feature plugins
Cut your Grails application to pieces - build feature pluginsGR8Conf
 
Performance tuning Grails applications
 Performance tuning Grails applications Performance tuning Grails applications
Performance tuning Grails applicationsGR8Conf
 
Ratpack and Grails 3
 Ratpack and Grails 3 Ratpack and Grails 3
Ratpack and Grails 3GR8Conf
 
Grails & DevOps: continuous integration and delivery in the cloud
Grails & DevOps: continuous integration and delivery in the cloudGrails & DevOps: continuous integration and delivery in the cloud
Grails & DevOps: continuous integration and delivery in the cloudGR8Conf
 
Functional testing your Grails app with GEB
Functional testing your Grails app with GEBFunctional testing your Grails app with GEB
Functional testing your Grails app with GEBGR8Conf
 
Deploying, Scaling, and Running Grails on AWS and VPC
Deploying, Scaling, and Running Grails on AWS and VPCDeploying, Scaling, and Running Grails on AWS and VPC
Deploying, Scaling, and Running Grails on AWS and VPCGR8Conf
 
The Grails introduction workshop
The Grails introduction workshopThe Grails introduction workshop
The Grails introduction workshopGR8Conf
 
The Groovy Ecosystem Revisited
The Groovy Ecosystem RevisitedThe Groovy Ecosystem Revisited
The Groovy Ecosystem RevisitedGR8Conf
 
Groovy 3 and the new Groovy Meta Object Protocol in examples
Groovy 3 and the new Groovy Meta Object Protocol in examplesGroovy 3 and the new Groovy Meta Object Protocol in examples
Groovy 3 and the new Groovy Meta Object Protocol in examplesGR8Conf
 
Integration using Apache Camel and Groovy
Integration using Apache Camel and GroovyIntegration using Apache Camel and Groovy
Integration using Apache Camel and GroovyGR8Conf
 
CRaSH the shell for the Java Virtual Machine
CRaSH the shell for the Java Virtual MachineCRaSH the shell for the Java Virtual Machine
CRaSH the shell for the Java Virtual MachineGR8Conf
 
Grooscript gr8conf
Grooscript gr8confGrooscript gr8conf
Grooscript gr8confGR8Conf
 

Plus de GR8Conf (20)

DevOps Enabling Your Team
DevOps Enabling Your TeamDevOps Enabling Your Team
DevOps Enabling Your Team
 
Creating and testing REST contracts with Accurest Gradle
Creating and testing REST contracts with Accurest Gradle Creating and testing REST contracts with Accurest Gradle
Creating and testing REST contracts with Accurest Gradle
 
Mum, I want to be a Groovy full-stack developer
Mum, I want to be a Groovy full-stack developerMum, I want to be a Groovy full-stack developer
Mum, I want to be a Groovy full-stack developer
 
Metaprogramming with Groovy
Metaprogramming with GroovyMetaprogramming with Groovy
Metaprogramming with Groovy
 
Scraping with Geb
Scraping with GebScraping with Geb
Scraping with Geb
 
How to create a conference android app with Groovy and Android
How to create a conference android app with Groovy and AndroidHow to create a conference android app with Groovy and Android
How to create a conference android app with Groovy and Android
 
Ratpack On the Docks
Ratpack On the DocksRatpack On the Docks
Ratpack On the Docks
 
Groovy Powered Clean Code
Groovy Powered Clean CodeGroovy Powered Clean Code
Groovy Powered Clean Code
 
Cut your Grails application to pieces - build feature plugins
Cut your Grails application to pieces - build feature pluginsCut your Grails application to pieces - build feature plugins
Cut your Grails application to pieces - build feature plugins
 
Performance tuning Grails applications
 Performance tuning Grails applications Performance tuning Grails applications
Performance tuning Grails applications
 
Ratpack and Grails 3
 Ratpack and Grails 3 Ratpack and Grails 3
Ratpack and Grails 3
 
Grails & DevOps: continuous integration and delivery in the cloud
Grails & DevOps: continuous integration and delivery in the cloudGrails & DevOps: continuous integration and delivery in the cloud
Grails & DevOps: continuous integration and delivery in the cloud
 
Functional testing your Grails app with GEB
Functional testing your Grails app with GEBFunctional testing your Grails app with GEB
Functional testing your Grails app with GEB
 
Deploying, Scaling, and Running Grails on AWS and VPC
Deploying, Scaling, and Running Grails on AWS and VPCDeploying, Scaling, and Running Grails on AWS and VPC
Deploying, Scaling, and Running Grails on AWS and VPC
 
The Grails introduction workshop
The Grails introduction workshopThe Grails introduction workshop
The Grails introduction workshop
 
The Groovy Ecosystem Revisited
The Groovy Ecosystem RevisitedThe Groovy Ecosystem Revisited
The Groovy Ecosystem Revisited
 
Groovy 3 and the new Groovy Meta Object Protocol in examples
Groovy 3 and the new Groovy Meta Object Protocol in examplesGroovy 3 and the new Groovy Meta Object Protocol in examples
Groovy 3 and the new Groovy Meta Object Protocol in examples
 
Integration using Apache Camel and Groovy
Integration using Apache Camel and GroovyIntegration using Apache Camel and Groovy
Integration using Apache Camel and Groovy
 
CRaSH the shell for the Java Virtual Machine
CRaSH the shell for the Java Virtual MachineCRaSH the shell for the Java Virtual Machine
CRaSH the shell for the Java Virtual Machine
 
Grooscript gr8conf
Grooscript gr8confGrooscript gr8conf
Grooscript gr8conf
 

Dernier

Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptxLBM Solutions
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsMark Billinghurst
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreternaman860154
 
Artificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraArtificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraDeakin University
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksSoftradix Technologies
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 3652toLead Limited
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersThousandEyes
 
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxMaking_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxnull - The Open Security Community
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Scott Keck-Warren
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024BookNet Canada
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphNeo4j
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machinePadma Pradeep
 

Dernier (20)

Pigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping ElbowsPigging Solutions Piggable Sweeping Elbows
Pigging Solutions Piggable Sweeping Elbows
 
Key Features Of Token Development (1).pptx
Key  Features Of Token  Development (1).pptxKey  Features Of Token  Development (1).pptx
Key Features Of Token Development (1).pptx
 
DMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special EditionDMCC Future of Trade Web3 - Special Edition
DMCC Future of Trade Web3 - Special Edition
 
The transition to renewables in India.pdf
The transition to renewables in India.pdfThe transition to renewables in India.pdf
The transition to renewables in India.pdf
 
Human Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR SystemsHuman Factors of XR: Using Human Factors to Design XR Systems
Human Factors of XR: Using Human Factors to Design XR Systems
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
Artificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning eraArtificial intelligence in the post-deep learning era
Artificial intelligence in the post-deep learning era
 
Pigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food ManufacturingPigging Solutions in Pet Food Manufacturing
Pigging Solutions in Pet Food Manufacturing
 
Benefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other FrameworksBenefits Of Flutter Compared To Other Frameworks
Benefits Of Flutter Compared To Other Frameworks
 
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
Tech-Forward - Achieving Business Readiness For Copilot in Microsoft 365
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for PartnersEnhancing Worker Digital Experience: A Hands-on Workshop for Partners
Enhancing Worker Digital Experience: A Hands-on Workshop for Partners
 
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptxMaking_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
Making_way_through_DLL_hollowing_inspite_of_CFG_by_Debjeet Banerjee.pptx
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024Advanced Test Driven-Development @ php[tek] 2024
Advanced Test Driven-Development @ php[tek] 2024
 
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
#StandardsGoals for 2024: What’s new for BISAC - Tech Forum 2024
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge GraphSIEMENS: RAPUNZEL – A Tale About Knowledge Graph
SIEMENS: RAPUNZEL – A Tale About Knowledge Graph
 
Install Stable Diffusion in windows machine
Install Stable Diffusion in windows machineInstall Stable Diffusion in windows machine
Install Stable Diffusion in windows machine
 

Idiomatic Spock

  • 4. def "user can purchase uniform"() { given: to LoginPage loginForm.with { email = "aaron.pyle@teleworm.us" password = "ahroh6tie4oCh" submit() } ! and: to ProductPage, "Starfleet uniform (TOS)" addToBasketButton.click() ! and: at BasketPage checkoutButton.click() ! expect: at CheckoutPage when: checkoutForm.with { street = "2415 Woodland Avenue" city = "Garyville" state = "LA" zip = "70051" cardType = "Mastercard" cardNo = "5555700540812370" expiry = "11/2017" cvc = "003" submit() } ! then: message == "Purchase successful" and: 1 * paymentMock.charge("USD", 65) }
  • 5.
  • 6. def "user can purchase uniform"() { given: loggedInAs user addedToBasket product ! when: completeCheckout user ! then: message == "Purchase successful" and: 1 * paymentMock.charge(product.price) } @Shared user = [ email: "aaron.pyle@teleworm.us" password: "ahroh6tie4oCh" street: "2415 Woodland Avenue" city: "Garyville" state: "LA" zip: "70051" cardType: "Mastercard" cardNo: "5555700540812370" expiry: "11/2017" cvc: "003" ] ! @Shared product = [ name: "Starfleet uniform (TOS)" price: ["USD", 65] ]
  • 7. SHARED HELPER METHODS @Category(GebSpec) class StoreInteractions { void loggedInAs(user) {} ! void addedToBasket(String productName) {} ! void completeCheckout(user) {} } ! @Mixin(StoreInteractions) class CheckoutSpec extends GebSpec {}
  • 8. …IN GROOVY 2.3 trait StoreInteractions { void loggedInAs(user) {} ! void addedToBasket(String productName) {} ! void completeCheckout(user) {} } ! class CheckoutSpec extends GebSpec implements StoreInteractions {}
  • 10. when: def results = ships.findByAllegiance("Federation") ! then: results.size() == 3 results[0].allegiance == "Federation" results[1].allegiance == "Federation" results[2].allegiance == "Federation"
  • 11. when: def results = ships.findByAllegiance("Federation") ! then: results.every { it.allegiance == "Federation" }
  • 12. Condition not satisfied: ! results.every { it.allegiance == "Federation" } | | | false [Gr'oth, Enterprise, Constitution, Constellation, M'Char, Haakona]
  • 13. when: def results = ships.findByAllegiance("Federation") ! then: results.allegiance.every { it == "Federation" }
  • 14. Condition not satisfied: ! results.allegiance.every { it == "Federation" } | | | | | false | [Klingon, Federation, Federation, Federation, Klingon, Romulan] [Gr'oth, Enterprise, Constitution, Constellation, M'Char, Haakona]
  • 15. ONE (LOGICAL) ASSERTION The Single Responsibility Principle for tests
  • 16. def "can list ships by allegiance"() { when: def results = ships.findByAllegiance("Federation") ! then: results.allegiance.every { it == "Federation" } ! and: results instanceof ImmutableList }
  • 18. when: crew << "Kirk" << "Bones" << "Scotty" ! then: "Science officer" in crew.openPositions
  • 19. given: crew << "Kirk" << "Bones" << "Scotty" ! expect: "Science officer" in crew.openPositions
  • 20. given: crew << "Kirk" ! when: crew << "Picard" ! then: thrown TooManyCaptainsException
  • 21. MOCKS & STUBS The right tool for the job
  • 22. def paymentMock = Mock(PaymentService) ! def "payment is taken at the end of checkout"() { given: loggedInAs user addedToBasket product ! when: completeCheckout user ! then: 1 * paymentMock.charge(product.price) }
  • 23. def "presents ships as a bullet list"() { given: def shipList = ["Enterprise", "Constellation"] ! and: def shipStore = Mock(ShipStore) ! when: def output = ships.render() ! then: 1 * shipStore.list() >> shipList ! and: $(output).find("ul > li")*.text() == shipList }
  • 24. def "presents ships as a bullet list"() { given: def shipList = ["Enterprise", "Constellation"] ! and: def shipStore = Stub(ShipStore) { list() >> shipList } ! when: def output = ships.render() ! then: $(output).find("ul > li")*.text() == shipList }
  • 26. def handle = DBI.open("jdbc:h2:mem:test") @Subject ships = handle.attach(ShipStore) ! def setup() { ships.createTable() } ! def cleanup() { ships.dropTable() handle.close() }
  • 27. @AutoCleanup handle = DBI.open("jdbc:h2:mem:test") ! @AutoCleanup("dropTable") @Subject ships = handle.attach(ShipStore) ! def setup() { ships.createTable() }
  • 28. @Shared @AutoCleanup handle = DBI.open("jdbc:h2:mem:test") ! @Shared @AutoCleanup("dropTable") @Subject ships = handle.attach(ShipStore) ! def setupSpec() { ships.createTable() }
  • 29. ENFORCE PRECONDITIONS Another use for the expect block
  • 30. @Shared handle = DBI.open("jdbc:h2:mem:test") ! def "writes to the database when data is added"() { given: def user = new User(name: "Spock") ! when: userStore.persist(user) ! then: selectInt("select count(*) from user") == 1 }
  • 31. @Shared handle = DBI.open("jdbc:h2:mem:test") ! def "writes to the database when data is added"() { expect: selectInt("select count(*) from user") == 0 ! given: def user = new User(name: "Spock") ! when: userStore.persist(user) ! then: selectInt("select count(*) from user") == 1 }
  • 32. def "a completed job cannot be restarted"() { given: "all tasks succeed" task1.execute(_) >> new DefaultTaskResult(SUCCEEDED) task2.execute(_) >> new DefaultTaskResult(SUCCEEDED) task3.execute(_) >> new DefaultTaskResult(SUCCEEDED) ! and: "the job has been run" def jobExecution = launchJob() ! expect: "the job to have completed successfully" jobExecution.exitStatus == ExitStatus.COMPLETED ! when: "the job is restarted" resumeJob jobExecution ! then: "an exception is thrown" thrown JobInstanceAlreadyCompleteException }
  • 34. @Subject stack = new Stack() ! def "size increases when we add items to a stack"() { when: stack.push "foo" ! then: stack.size() == 1 }
  • 35. @Subject stack = new Stack() ! def "size increases when we add items to a stack"() { given: def oldSize = stack.size() ! when: stack.push "foo" ! then: stack.size() == oldSize + 1 }
  • 36. @Subject stack = new Stack() ! def "size increases when we add items to a stack"() { when: stack.push "foo" ! then: stack.size() == old(stack.size()) + 1 }
  • 38. —Abelson and Sussman “Programs must be written for people to read, and only incidentally for machines to execute.”
  • 39. “Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.” —M. Golding
  • 40. RED… GREEN… REFACTOR Write a failing test Write enough code to make test pass Refactor code while keeping tests passing
  • 41. RED… AMBER… GREEN… REFACTOR Write a failing test Write enough code to make test pass Refactor code while keeping tests passing Ensure correct failure & good diagnostic
  • 42. THE PRIME DIRECTIVE NEVERTRUST A TEST YOU HAVEN'T SEEN FAIL
  • 44. @Subject ship = new Starship("Enterprise") ! @Subject(Starship) ! @Issue("https://github.com/robfletcher/betamax/issues/37") ! @See("http://atompunkera.tumblr.com/") ! @Title("a readable title") ! @Narrative("""as a developer I want to die so I can end the pain""")
  • 46. @Unroll def "rejects an input value of '#input'"()
  • 47. @Unroll("#input converted to #format is '#output'") def "translates strings to another format"()
  • 50. def "can tell if the string '#string' is an integer or not"() { expect: string.isInteger() == shouldBeInteger ! where: string | shouldBeInteger "ABC" | false "123" | true "1.2" | false "1 2" | false "12a" | false }
  • 51.
  • 52. @Unroll def "the string '#string' is #description"() { expect: string.isInteger() == expected ! where: string | expected "ABC" | false "123" | true "1.2" | false "1 2" | false "12a" | false ! description = expected ? "an integer" : "not an integer" }
  • 53. @Unroll("the string '#string' is #description") def "identifies strings that are integers"() { expect: string.isInteger() == expected ! where: string | expected "ABC" | false "123" | true "1.2" | false "1 2" | false "12a" | false ! description = expected ? "an integer" : "not an integer" }
  • 54.
  • 55. SEPARATE TEST DATA FROM TEST LOGIC where blocks without @Unroll
  • 56. def "passes data from stream to callback"() { given: def callback = Mock(Function) ! and: service.source = Stub(StreamSource) { connect() >> Observable.from("foo", "bar", "baz") } ! when: service.readStream(callback) ! then: 1 * callback.apply("foo") 1 * callback.apply("bar") 1 * callback.apply("baz") }
  • 57. def "passes data from stream to callback"() { given: def callback = Mock(Function) ! and: service.source = Stub(StreamSource) { connect() >> Observable.from(*data) } ! when: service.readStream(callback) ! then: data.each { 1 * callback.apply(it) } ! where: data = ["foo", "bar", "baz"] }
  • 59. TEST ORGANIZATION Think behavior not units of code
  • 60. @AutoCleanup @Shared handle = DBI.open("jdbc:h2:mem:test") @Subject ships = handle.attach(ShipStore) ! def setup() { ships.createTable() ! insert("ship", "Enterprise", "Federation") insert("ship", "Constellation", "Federation") insert("ship", "M'Char", "Klingon") } ! def "can find by allegiance"() { when: def results = ships.findByAllegiance("Federation") ! then: results.allegiance.every { it == "Federation" } } ! def "can find by name"() { when: def result = ships.findByName(name) ! then: result.name == name ! where: name = "M'Char" } ! def "inserting writes to the database"() { when: ships << new Ship(name, allegiance) ! then: count("ship", name: name) == 1 ! where: name = "Haakona" allegiance = "Romulan" }
  • 63. @Subject def ships = new ShipStore() ! def "can find ships by allegiance ordered by age"() { given: ships << new Ship("Enterprise", "Federation", Year.of(2245)) << new Ship("Adventure", "Federation", Year.of(2376)) << new Ship("Haakona", "Romulan", Year.of(2357)) ! expect: def matches = ships.findByAllegianceNewestFirst("Federation") matches.name == ["Enterprise", "Haakona", "Adventure"] }
  • 64. def "can find ships by allegiance ordered by age"() { given: ships << new Ship("Enterprise", "Federation", Year.of(2245)) << new Ship("Adventure", "Federation", Year.of(2376)) << new Ship("Haakona", "Romulan", Year.of(2357)) ! expect: def matches = ships.findByAllegianceNewestFirst("Federation") matches.allegiance.every { it == "Federation" } matches.enteredService == matches.enteredService.sort().reverse() }
  • 67. def "items can be viewed in the basket after selection"() { when: products.each { to ProductPage, it addToBasket() } to BasketPage ! then: basket.size() == 3 basket.items.title == products ! where: products = ["Starfleet uniform", "Tricorder", "Phaser"] }
  • 68. def "items can be viewed in the basket after selection"() { given: products.each { to ProductPage, it addToBasket() } ! when: to BasketPage ! then: basket.size() == 3 basket.items.title == products ! where: products = ["Starfleet uniform", "Tricorder", "Phaser"] }
  • 71. @Stepwise class StackSpec extends Specification { ! @Shared @Subject stack = new Stack() @Shared value = "foo" ! def "can push to the stack"() { expect: stack.push(value) == value } ! def "stack should have content"() { expect: stack.peek() == value } ! def "can pop from the stack"() { expect: stack.pop() == value } ! def "the stack should be empty"() { expect: stack.empty() } }
  • 72. THINKING OUTSIDE THE BOX Strange new worlds of testing
  • 73. TCK SPECIFICATIONS One specification, multiple implementations
  • 74. abstract class ShipStoreSpec<T extends ShipStore> extends Specification { ! @Subject T ships ! def "can insert a new ship"() { when: ships.insert(new Ship("Enterprise", "Federation")) ! then: ships.list().size() == old(ships.list().size()) + 1 } ! def "can find ships by allegiance"() { // ... } }
  • 75. class MemoryShipStoreSpec extends ShipStoreSpec<MemoryShipStore> { def setup() { ships = new MemoryShipStore() } }
  • 76. class PersistentShipStoreSpec extends ShipStoreSpec<PersistentShipStore> { ! @AutoCleanup Handle handle ! def setup() { handle = DBI.open("jdbc:h2:mem:test") ships = handle.attach(PersistentShipStore) ships.createTable() } ! def cleanup() { ships.dropTable() } }
  • 77.
  • 79. @Unroll void "the #table table has a primary key"() { expect: with(handle.connection) { Connection connection -> connection.metaData.getPrimaryKeys(null, null, table).next() } ! where: table << tableNames }
  • 80. @Shared @AutoCleanup Handle handle @Shared Collection<String> tableNames = [] ! def setupSpec() { handle.connection.with { connection -> def rs = connection.metaData.getTables(null, null, "%", ["TABLE"] as String[]) while (rs.next()) { tableNames << rs.getString(3) } } }
  • 82. @IgnoreIf({ javaVersion <= 1.7 }) ! @IgnoreIf({ env.SKIP_INTEGRATION_TESTS == "yes" }) ! @IgnoreIf({ properties."os.name" ==~ /Windows.*/) })
  • 83. ! static boolean isReachable(String url) { try { def connection = url.toURL().openConnection() connection.connectTimeout = 1000 connection.connect() true } catch (IOException ex) { false } } ! @IgnoreIf({ !isReachable("http://www.netflix.bv/") }) class NeedsRealNetworkConnectionSpec extends Specification {
  • 84. ! static boolean isReachable(String url) { try { def connection = url.toURL().openConnection() connection.connectTimeout = 1000 connection.connect() true } catch (IOException ex) { false } } ! @IgnoreIf({ !isReachable("http://www.netflix.bv/") }) class NeedsRealNetworkConnectionSpec extends Specification { @Memoized
  • 86. @Shared engine = new ScriptEngineManager().getEngineByName("nashorn") @Shared @Subject moment ! def setupSpec() { getClass().getResourceAsStream("/moment.js").withReader { reader -> engine.eval reader } ! moment = engine.invokeFunction("moment") }
  • 87. @Unroll def "The date #date in friendly format is #expectedResult"() { expect: engine.invokeMethod(moment, "from", date.toString()) == expectedResult ! where: date | expectedResult now().plusDays(2) | "2 days ago" now().plusMinutes(2) | "2 minutes ago" now() | "a few seconds ago" now().minusDays(1) | "in a day" }
  • 89. UNROLL AUTOCOMPLETION @Unroll("#string converted to #style is '#ex'") def "can convert case"() { expect: string.convert(style) == expected ! where: string | style || expected "foo bar baz" | CAMEL || "FooBarBaz" "foo bar baz" | KEBAB || "foo-bar-baz" "foo bar baz" | SNAKE || "foo_bar_baz" }
  • 90. INPUT & OUTPUT PARAMETERS def "can convert case"() { expect: string.convert(style) == expected ! where: string | style || expected "foo bar baz" | CAMEL || "FooBarBaz" "foo bar baz" | KEBAB || "foo-bar-baz" "foo bar baz" | SNAKE || "foo_bar_baz" }
  • 91. TYPED PARAMETERS def "can convert case"(String string, CaseFormat style, String expected) { expect: string.convert(style) == expected ! where: string | style || expected "foo bar baz" | CAMEL || "FooBarBaz" "foo bar baz" | KEBAB || "foo-bar-baz" "foo bar baz" | SNAKE || "foo_bar_baz" }
  • 93. @Rule TemporaryFolder temporaryFolder = new TemporaryFolder() ! def "can copy a resource from classpath to a file"() { given: def resource = getClass().getResource("/test.txt") def file = temporaryFolder.newFile() ! when: resource.withReader { file << it } ! then: file.text == resource.text }
  • 96. IMPORT ALIASING import java.lang.Void as Should ! class MyBddSpec { ! Should "exhibit some behavior"() { // ... } ! }
  • 97. MULTIPLE WHEN/THEN BLOCKS given: def stack = new Stack() ! when: stack.push "foo" ! then: stack.pop() == "foo" ! when: stack.pop() ! then: thrown EmptyStackException