Peter Heisig
Software consultant and developer from Dresden.
Write Specifications describing Features (properties, aspects)
for a System under Specification (SUS) with Groovy
import spock.lang.*
class AnySpecification extends Specification {
// not shared between feature methods
def obj = new SystemUnderSpecification()
def col = new Collaborator()
@Shared res = new VeryExpensiveResource()
def setupSpec() {} // runs once - before the first feature method
def setup() {} // runs before every feature method
def cleanup() {} // runs after every feature method
def cleanupSpec() {} // runs once - after the last feature method
// feature methods
}
Set up the feature’s fixture
Provide a stimulus to the system under specification
Describe the response expected from the system
Clean up the feature’s fixture
def "pushing an element on the stack"() {
given:
def stack = new Stack()
def elem = "push me"
when:
stack.push(elem)
then:
!stack.empty
stack.size() == 1
stack.peek() == elem
}
Condition not satisfied:
stack.size() == 2
| | |
| 1 false
[push me]
def "pushing an element on the stack"() {
// [...]
then:
stack.size() == 2
// [...]
}
def "popping from an empty stack should throw exception"() {
// [...]
when:
stack.pop()
then:
thrown(EmptyStackException)
}
def "popping from an empty stack should throw exception"() {
// [...]
when:
stack.pop()
then:
def e = thrown(EmptyStackException)
e.cause == null
}
def "HashMap accepts null key"() {
given:
def map = new HashMap()
when:
map.put(null, "elem")
then:
notThrown(NullPointerException)
}
class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
Math.max(1, 3) == 3
Math.max(7, 4) == 7
Math.max(0, 0) == 0
}
def "data driven maximum of two numbers"(int a, int b, int c) {
expect:
Math.max(a, b) == c
where:
a | b | c
1 | 3 | 3
7 | 4 | 7
0 | 0 | 0
}
}
class PublisherSpec extends Specification {
Publisher publisher = new Publisher()
Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()
def setup() {
publisher.subscribers << subscriber
publisher.subscribers << subscriber2
}
def "should send messages to all subscribers"() {
when:
publisher.send("hello")
then:
1 * subscriber.receive("hello")
1 * subscriber2.receive("hello")
}
}
class Publisher {
List<Subscriber> subscribers = []
int messageCount = 0
void send(String message){
subscribers*.receive(message)
messageCount++
}
}
interface Subscriber {
void receive(String message)
}
Mocking is the act of describing (mandatory) interactions between the object under specification and its collaborators.
class PublisherSpec extends Specification {
// [...]
def "should send messages to all subscribers"() {
given:
subscriber.receive(_) >> "ok"
// Mockito equivalent
// when(entityManager.find(Customer.class,1L))
// .thenReturn(sampleCustomer);
and:
subscriber2.receive("message1") >> "ok"
subscriber2.receive("message2") >> "fail"
and:
subscriber.receive(_) >> { String message ->
message.size() > 3 ? "ok" : "fail"
}
// [...]
}
}
interface Subscriber {
String receive(String message)
}
Stubbing is the act of making collaborators respond to method calls in a certain way
// Cardinality
1 * subscriber.receive("hello") // exactly one call
0 * subscriber.receive("hello") // zero calls
(1..3) * subscriber.receive("hello") // between one and three calls (inclusive)
(1.._) * subscriber.receive("hello") // at least one call
(_..3) * subscriber.receive("hello") // at most three calls
_ * subscriber.receive("hello") // any number of calls, including zero
// Target Constraint
1 * subscriber.receive("hello") // a call to 'subscriber'
1 * _.receive("hello") // a call to any mock object
// Method Constraint
1 * subscriber.receive("hello") // a method named 'receive'
1 * subscriber./r.*e/("hello") // a method whose name matches the given regular expression
// Argument Constraints
1 * subscriber.receive("hello") // an argument that is equal to the String "hello"
1 * subscriber.receive(!"hello") // an argument that is unequal to the String "hello"
1 * subscriber.receive() // the empty argument list (would never match in our example)
1 * subscriber.receive(_) // any single argument (including null)
1 * subscriber.receive(*_) // any argument list (including the empty argument list)
1 * subscriber.receive(!null) // any non-null argument
1 * subscriber.receive(_ as String) // any non-null argument that is-a String
1 * subscriber.receive(endsWith("lo")) // any non-null argument that is-a String
1 * subscriber.receive({ // an argument that satisfies the given predicate
it.size() > 3 &&
it.contains('a') })
Spock | JUnit |
---|---|
Specification | Test class |
setup() | @Before |
cleanup() | @After |
setupSpec() | @BeforeClass |
cleanupSpec() | @AfterClass |
Feature | Test |
Feature method | Test Method |
Data-driven feature | Theory |
Condition | Assertion |
Exception condition | @Test(expected=…) |
Interaction | Mock expectation (e.g. in Mockito) |
By Peter Heisig