2. Why Don't We Test?
Hard To Get Started
My Code's Perfect Code Too Monolithic
Tests Broke, Nobody Fixed, Turned Off
Uh, We Used To? More Code Is More Code
Test Code Hard To Maintain
3. What if ...
Test code was readable?
Tests were concise?
Test reports were useful?
Failures were well described?
Mocking was easy?
… wouldn't that be most logical?
4. My Path
Wrote own test framework –
1990's
in PL/1 – 5000 - 7000 tests
First JUnit tests (Tapestry
2001
template parser)
2003-ish Started using TestNG
2004-ish Started using EasyMock
2005-ish Started using Selenium 1
2006-ish Dabbled in Groovy
2010 Spock! ~ 0.4
5. Terminology
Fixture
Specification
Collaborator
System
Feature
Feature Under
Specification
Collaborator
6. First Specification sp
o
SkySpecification.groovy gro ck 0
ov .
package org.example y-1 6-
.8
import spock.lang.*
class SkySpecification extends Specification {
...
} All Specifications extend from this base class
Sky.java
package org.example.sus;
public class Sky {
public String getColor() {
return "blue";
}
}
7. Feature Methods
def "sky is blue"() {
setup:
def sky = new Sky()
expect:
sky.color == "blue"
}
8. Execution
$ gradle test
Note: the Gradle build daemon is an experimental feature.
As such, you may experience unexpected build failures. You may need to
occasionally stop the daemon.
:compileJava UP-TO-DATE
:compileGroovy UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:compileTestGroovy $ tree build/reports/
:processTestResources UP-TO-DATE build/reports/
:testClasses └── tests
:test ├── base-style.css
├── css3-pie-1.0beta3.htc
BUILD SUCCESSFUL ├── index.html
├── org.example.SkySpecification.html
Total time: 3.831 secs ├── org.example.html
~/workspaces/github/spock-examples ├── report.js
$ └── style.css
1 directory, 7 files
~/workspaces/github/spock-examples
$
15. Feature Method Blocks
def "sky is blue"() {
setup:
def sky = new Sky()
expect:
sky.color == "blue"
}
setup: or given:
expect:
when:
then:
where:
cleanup:
Feature methods must contain at least one block
otherwise, not a feature method
16. setup:
Initialization of the fixture
Always at top of method
Can't be repeated
Can be given: instead
Anything up to first label is implicit setup:
17. expect: Response
expect:
sky.color == "blue"
Stimulus
Combines stimulus and response
Contains only conditions and variable definitions
Conditions assert Groovy truth
Best for purely functional (no-side effects) functions
18. when: / then:
def "clouds are grey"() {
def sky = new Sky()
when:
Stimulus sky.addStormSystem()
then:
sky.color == "grey" Response
}
Used as a pair
Tests method with side effects
then: may only contain conditions, exception
conditions, interactions and variable definitions
21. then: Old Values
def "pushing an element on the stack increases its size by one"() {
def stack = new Stack()
when:
stack.push("element")
then:
stack.size() == old(stack.size()) + 1
}
Expression value captured before where: block
22. Extended Assert
def "use of extended assert"() {
expect:
assert 4 == 5, "Big Brother says there are four fingers"
}
23. cleanup:
setup:
def file = new File("/some/path")
file.createNewFile()
// ...
cleanup:
file.delete()
Cleanup external resources
Always invoked, even if previous exceptions
24. where:
def "length of crew member names"() {
expect:
name.length() == length
where:
name | length
"Spock" | 5
"Kirk" | 4
"Scotty" | 6
}
Parameterizes feature method with data
Must be last block
Can use | or || as separator
25. where:
One entry for three
feature method
executions
26. where: using lists
def "length of crew member names (using lists)"() {
expect:
name.length() == length
where:
name << ["Spock", "Kirk", "Scotty"]
length << [5, 4, 6]
}
Could come from external file or database
where:
[name, age, gender] = sql.execute("select name, age, sex from ⏎
customer")
27. where: derived values
def "length of crew member names (with derived values)"() {
expect:
name.length() == length
where:
name << ["Spock", "Kirk", "Scotty"]
length = name.length()
}
28. Block labels
def "clouds are grey"() {
given: "A fresh Sky"
def sky = new Sky()
when: "A storm system rolls in"
sky.addStormSystem()
then: "It all goes grey"
sky.color == "grey"
}
Allowed, but not (currently) used
and: "block" allowed, does nothing
30. Fields
package org.example
import org.example.sus.Sky
import spock.lang.Specification
class SkySpecification extends Specification {
def sky = new Sky() New for each feature method
def "sky is blue by default"() {
expect:
sky.color == "blue"
}
def "clouds are grey"() {
given: "A fresh Sky"
when: "A storm system rolls in"
sky.addStormSystem()
then: "It all goes grey"
sky.color == "grey"
}
}
31. Shared Fields
Created once, shared across all instances
class MySpecification extends Specification {
@Shared def resource = new AnExepensiveResource()
static final PASSWORD = "sayfriendandenter"
…
}
Statics should be final and immutable
44. Target and Method Constraints
Argument Constraints
1 * dao.getById(12345) >> null
Number of invocations:
Returns a value
cardinality
45. Cardinality
Omitted
➠ Interaction is optional, must have return value
n * mock.method(…)
➠ exactly n times
(n.._) * mock.method(…)
➠ at least n times
(_..n) * mock.method(…)
➠ Up to n times
46. Argument Constraints
_
➠ Any argument
*_
➠ Any number of arguments
!null
➠ Any non-null argument
value
➠ Argument equals value
!value
➠ Argument not equal to value
_ as Type
➠ Non-null argument assignable to Type
48. def "wrong number of invocations"() {
when:
assert mock.op(7) == 14
then:
(2..7) * mock.op({ isOdd(it) }) >> { 2 * it }
}
Too few invocations for:
(2..7) * mock.op({ result = 2 * it; return it % 2 != 0 }) >> { result } (1 invocation)
& at org.spockframework.mock.InteractionScope.verifyInteractions(InteractionScope.java:66)
& at org.spockframework.mock.MockController.leaveScope(MockController.java:35)
& at org.example.InteractionsSpecification.wrong number of
invocations(InteractionsSpecification.groovy:32)
49. Mocks are Lenient
def "parameter does not match closure constraint"() {
when:
assert mock.op(3) == 6
assert mock.op(4) == null
assert mock.op(7) == 14
then:
_ * mock.op({ isOdd(it) }) >> { 2 * it }
}
50. Less Lenient Mocks
def "detecting parameter that doesn't match"() {
when:
assert mock.op(3) == 6
assert mock.op(4) == null
assert mock.op(7) == 14
then:
_ * mock.op({ isOdd(it) }) >> { 2 * it }
0 * _ aka "any interaction"
}
Too many invocations for:
0 * _ (1 invocation)
Last invocation: mock.op(4)
& at org.spockframework.mock.MockInteraction.accept(MockInteraction.java:58)
& at org.spockframework.mock.MockInteractionDecorator.accept(MockInteractionDecorator.java:41)
& at org.spockframework.mock.InteractionScope$1.accept(InteractionScope.java:38)
& at org.spockframework.mock.MockController.dispatch(MockController.java:42)
& at org.spockframework.mock.DefaultMockFactory$1.invoke(DefaultMockFactory.java:70)
& at org.example.InteractionsSpecification.detecting parameter that doesn't
match(InteractionsSpecification.groovy:58)
53. Method Constraints
name
➠ Match method with given name
/re/
➠ Match methods matching regular expression
➠ e.g. bean./set.*/(_)
_
➠ Match any method
54. Return Values
>> value
➠ Return the value
>> { … }
➠ Evaluate the closure and return the result
>>> [ a, b, c]
➠ Return a, then return b, then keep returning c
➠ Any kind of iterable list, any size
55. Chained Return Values
then:
service.getStatus()
>>> ["ok", "ok", "fail"]
>> { throw new RuntimeException("Status failure."); }
The last value sticks
56. Ordered Interactions
def "test three amigos"() {
when:
facade.doSomething()
then:
1 * collab1.firstPart() No order checking
1 * collab2.secondPart() on firstPart(),
secondPart()
then:
1 * collab3.thirdPart()
}
thirdPart() only
allowed after both
firstPart(),
secondPart()
57. Stubbing
def "frobs the gnop"() {
A "global interaction"
checker.isValid(_) >> true valid to end of
when: method
…
}
59. @Unroll
@Unroll
def "Crew member '#name' length is #length"() {
expect:
name.length() == length
where:
name | length
"Spock" | 5
"Kirk" | 4
"Scotty" | 6
}
60.
61. @Unroll
class CanonicalWhereBlockExampleSpecification extends Specification {
def "Crew member '#name' length is #length"() {
…
}
def "length of crew member names (using lists)"() {
…
}
def "length of crew member names (with derived values)"() {
…
}
}
62. @Timeout
@Timeout(5)
def "can access data in under five seconds"() {
…
}
@Timeout(value=100, unit=TimeUnit.MILLISECONDS)
def "can update data in under 100 ms"() {
…
}
Feature and fixture methods run on main thread
A second thread may interrupt the main thread
Can be placed on class to affect all feature methods
63. @Stepwise
@Stepwise
class BuildingBlocksSpecification extends Specification {
def "first step()" { … }
def "second step"() { … }
def "third step"() { … }
Feature methods run in declaration order
Failed methods cause remainder to be skipped
64. @AutoCleanup
def DatabaseSpecification … {
@AutoCleanup @Shared
Connection connection
…
}
Will invoke close() on field's value
Exceptions are reported but are not failures
quiet=true
➠ Don't report exceptions
value attribute is name of method to invoke
65. @Ignore / @IgnoreRest
Used to temporarily control which feature methods
execute
@Ignore
➠ Ignore this method, run others
@IgnoreRest
➠ Run this method, ignore others