Craig Atkinson
Behavior-style test framework in Groovy with support for easy data-driven testing
Even though Spock tests are written in Groovy, you can test code written in Java or other JVM languages with Spock.Test cases separated into three main sections
Run same test body with multiple sets of test inputs and expected outputs
Spock's simple syntax for data-driven tests is one of it's main wins.Test class name ends in Spec or Specification and class extends spock.lang.Specification
class BankAccountSpec extends Specification { }
Test case method names can be descriptive sentences
def "after depositing 10 dollars then balance should be 10 dollars"() { }These long sentence test names are more useful to future readers of the code than previous names such as 'depositTestCase1' as they illustrate the full intent of the test.
class BankAccountSpec extends Specification { def "after depositing 10 dollars then balance should be 10 dollars"() { given: BankAccount bankAccount = new BankAccount() when: bankAccount.deposit(10) then: assert bankAccount.balance == 10 } }
Run code before each test method
def setup() { // Setup code goes here }
Run code after each test method
def cleanup() { // Cleanup code goes here }
where: input1 | input2 || output 4 | 6 || 5 12 | 18 || 15 20 | 14 || 17
Include inputs and outputs from where: block in tests results
@Unroll def 'depositing #amount should increase balance to #expectedBalance'() { given: BankAccount bankAccount = new BankAccount() when: bankAccount.deposit(amount) then: assert bankAccount.balance == expectedBalance where: amount || expectedBalance 10 || 10 20 || 20 }The test body is executed once for each row in the 'where' block. Single pipes are used to separate test input variables and the double-pipe is a convention (though not required) to separate the test input section from the test output variables.
assert result == expectedValueThe Groovy Power assert prints out a very descriptive and useful failure message when an assertion fails.
def 'x plus y equals z'() { when: int x = 4 int y = 5 int z = 10 then: assert x + y == z }
Condition not satisfied: x + y == z | | | | | 4 9 5 | 10 falseThe power assert prints out the result of calling the .toString() method on each item in the assert statement.
Use tests to help guide development
Extensive test suite that serves as a safety net for code changes
Thoroughly document expected behavior in tests
BankAccount class
git clone https://github.com/craigatk/tdd-spock.git cd tdd-spock
src/main/groovy src/test/groovy
gradlew test --info
BankAccountSpec with one test that creates a new BankAccount and verifies bankAccount.balance is 0
class BankAccountSpec extends Specification { def "bank account starting balance should be 0"() { given: BankAccount bankAccount = new BankAccount() when: BigDecimal startingBalance = bankAccount.balance then: assert startingBalance == 0 } }
Hint: Should be a test compilation failure because BankAccount class does not exist yet
gradlew test --info
Write minimal code to make test pass
class BankAccount { BigDecimal balance = 0 }
Takes one parameter, a BigDecimal 'amount' and should increase the balance
def 'deposit should increase balance'() { given: BankAccount bankAccount = new BankAccount() when: bankAccount.deposit(10) then: assert bankAccount.balance == 10 }
void deposit(BigDecimal amount) { balance = 10 }
Using a where: block, expand our test method to two cases, one that deposits 10 dollars and one that deposits 20 dollars
@Unroll def 'depositing #amount should increase balance to #expectedBalance'() { given: BankAccount bankAccount = new BankAccount() when: bankAccount.deposit(amount) then: assert bankAccount.balance == expectedBalance where: amount || expectedBalance 10 || 10 20 || 20 }
void deposit(BigDecimal amount) { balance += amount }
Write a test case for a withdraw(BigDecimal amount) method that reduces the balance by the given amount
Hint: In the test setup, create a new BankAccount() and deposit money into it.
def "withdraw should reduce balance"() { given: BankAccount bankAccount = new BankAccount() bankAccount.deposit(20) when: bankAccount.withdraw(15) then: assert bankAccount.balance == 5 }
void withdraw(BigDecimal amount) { balance = 5 }
Similar to deposit() method, use where: block to add two additional test cases that withdraw different amounts
@Unroll def "withdrawing #amount should reduce balance to #expectedBalance"() { given: BankAccount bankAccount = new BankAccount() bankAccount.deposit(20) when: bankAccount.withdraw(amount) then: assert bankAccount.balance == expectedBalance where: amount || expectedBalance 5 || 15 10 || 10 15 || 5 }
void withdraw(BigDecimal amount) { balance -= amount }
Contact info
craig.atkinson@objectpartners.com
@craigatk1