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

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
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfIngrid Airi González
 
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...panagenda
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersNicole Novielli
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxLoriGlavin3
 
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
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality AssuranceInflectra
 
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
 
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
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Farhan Tariq
 
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
 
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
 
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
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Alkin Tezuysal
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfMounikaPolabathina
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfpanagenda
 
Decarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityDecarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityIES VE
 
2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch TuesdayIvanti
 

Dernier (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...
 
Generative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdfGenerative Artificial Intelligence: How generative AI works.pdf
Generative Artificial Intelligence: How generative AI works.pdf
 
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
Why device, WIFI, and ISP insights are crucial to supporting remote Microsoft...
 
A Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software DevelopersA Journey Into the Emotions of Software Developers
A Journey Into the Emotions of Software Developers
 
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptxPasskey Providers and Enabling Portability: FIDO Paris Seminar.pptx
Passkey Providers and Enabling Portability: FIDO Paris Seminar.pptx
 
From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .From Family Reminiscence to Scholarly Archive .
From Family Reminiscence to Scholarly Archive .
 
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance[Webinar] SpiraTest - Setting New Standards in Quality Assurance
[Webinar] SpiraTest - Setting New Standards in Quality Assurance
 
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
 
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
 
Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...Genislab builds better products and faster go-to-market with Lean project man...
Genislab builds better products and faster go-to-market with Lean project man...
 
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
 
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
 
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
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
Unleashing Real-time Insights with ClickHouse_ Navigating the Landscape in 20...
 
What is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdfWhat is DBT - The Ultimate Data Build Tool.pdf
What is DBT - The Ultimate Data Build Tool.pdf
 
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdfSo einfach geht modernes Roaming fuer Notes und Nomad.pdf
So einfach geht modernes Roaming fuer Notes und Nomad.pdf
 
Decarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a realityDecarbonising Buildings: Making a net-zero built environment a reality
Decarbonising Buildings: Making a net-zero built environment a reality
 
2024 April Patch Tuesday
2024 April Patch Tuesday2024 April Patch Tuesday
2024 April Patch Tuesday
 

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