├── .gitignore ├── smarter-testing-with-spock.key ├── smarter-testing-with-spock.pdf ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── test │ └── groovy │ │ ├── extension │ │ ├── custom │ │ │ ├── Report.groovy │ │ │ └── ReportExtension.groovy │ │ └── builtin │ │ │ ├── Stepwise.groovy │ │ │ └── JUnitRules.groovy │ │ ├── state │ │ ├── AccountSpec2.groovy │ │ ├── AccountSpec.groovy │ │ ├── AccountSpec3.groovy │ │ ├── AccountTest.java │ │ ├── AccountSpec4.groovy │ │ └── Conditions.groovy │ │ ├── coolstuff │ │ └── OptimizeRunOrder.groovy │ │ ├── interaction │ │ ├── PublisherSubscriberSpec.groovy │ │ ├── PublisherSubscriberSpec2.groovy │ │ ├── PublisherSubscriberSpec3.groovy │ │ └── Publisher2SubscriberSpec.groovy │ │ ├── datadriven │ │ ├── AccountSpecDataDriven.groovy │ │ ├── AccountSpecDataDriven2.groovy │ │ └── AccountSpecDatabaseDriven.groovy │ │ └── exercise │ │ ├── CodeMakerSpec.groovy │ │ └── CodeMaker.groovy └── main │ ├── groovy │ └── interaction │ │ ├── PublisherSubscriber.groovy │ │ └── PublisherSubscriber2.groovy │ └── java │ └── state │ ├── NegativeAmountWithdrawnException.java │ └── Account.java ├── README ├── gradlew.bat ├── exercises.md └── gradlew /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .gradle 3 | out 4 | *.iws 5 | *.ipr 6 | *.iml -------------------------------------------------------------------------------- /smarter-testing-with-spock.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spockframework/smarter-testing-with-spock/HEAD/smarter-testing-with-spock.key -------------------------------------------------------------------------------- /smarter-testing-with-spock.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spockframework/smarter-testing-with-spock/HEAD/smarter-testing-with-spock.pdf -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spockframework/smarter-testing-with-spock/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Apr 04 07:37:33 CEST 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-1.5-bin.zip 7 | -------------------------------------------------------------------------------- /src/test/groovy/extension/custom/Report.groovy: -------------------------------------------------------------------------------- 1 | package extension.custom 2 | 3 | import java.lang.annotation.* 4 | 5 | import org.spockframework.runtime.extension.ExtensionAnnotation 6 | 7 | @Retention(RetentionPolicy.RUNTIME) 8 | @Target(ElementType.TYPE) 9 | @ExtensionAnnotation(ReportExtension) 10 | @interface Report {} 11 | -------------------------------------------------------------------------------- /src/test/groovy/state/AccountSpec2.groovy: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | import spock.lang.Specification 4 | 5 | class AccountSpec2 extends Specification { 6 | def "withdraw some amount"() { 7 | given: 8 | def account = new Account(5.0) 9 | 10 | when: 11 | account.withdraw(2.0) 12 | 13 | then: 14 | account.balance == 3.0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/groovy/extension/builtin/Stepwise.groovy: -------------------------------------------------------------------------------- 1 | package extension.builtin 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Stepwise 5 | 6 | @spock.lang.Stepwise 7 | class Stepwise extends Specification { 8 | def "step 1"() { 9 | expect: true 10 | } 11 | 12 | def "step 2"() { 13 | expect: true 14 | } 15 | 16 | def "step 3"() { 17 | expect: true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/groovy/interaction/PublisherSubscriber.groovy: -------------------------------------------------------------------------------- 1 | package interaction 2 | 3 | interface Subscriber { 4 | void receive(String message) 5 | } 6 | 7 | class Publisher { 8 | List subscribers = [] 9 | 10 | void publish(String message) { 11 | for (subscriber in subscribers) { 12 | try { 13 | subscriber.receive(message) 14 | } catch (ignored) {} 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/test/groovy/state/AccountSpec.groovy: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | import spock.lang.Specification 4 | 5 | class AccountSpec extends Specification { 6 | void withdrawSomeAmount() { 7 | given: 8 | Account account = new Account(BigDecimal.valueOf(5)); 9 | 10 | when: 11 | account.withdraw(BigDecimal.valueOf(2)); 12 | 13 | then: 14 | account.getBalance() == BigDecimal.valueOf(3); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/state/NegativeAmountWithdrawnException.java: -------------------------------------------------------------------------------- 1 | package state; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class NegativeAmountWithdrawnException extends RuntimeException { 6 | private final BigDecimal amount; 7 | 8 | public NegativeAmountWithdrawnException(BigDecimal amount) { 9 | super("cannot withdraw " + amount); 10 | this.amount = amount; 11 | } 12 | 13 | public BigDecimal getAmount() { 14 | return amount; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/groovy/state/AccountSpec3.groovy: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | import spock.lang.Specification 4 | 5 | class AccountSpec3 extends Specification { 6 | def "withdraw some amount"() { 7 | given: "an account with a balance of five euros" 8 | def account = new Account(5.0) 9 | 10 | when: "two euros are withdrawn" 11 | account.withdraw(2.0) 12 | 13 | then: "three euros remain in the account" 14 | account.balance == 3.0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/groovy/coolstuff/OptimizeRunOrder.groovy: -------------------------------------------------------------------------------- 1 | package coolstuff 2 | 3 | import spock.lang.Specification 4 | 5 | class OptimizeRunOrder extends Specification { 6 | def "a slow test"() { 7 | expect: Thread.sleep(3000) 8 | } 9 | 10 | def "a fast test"() { 11 | expect: true 12 | } 13 | 14 | def "a sometimes bad test"() { 15 | //expect: System.currentTimeMillis() % 3 16 | } 17 | 18 | def "a bad test"() { 19 | //expect: false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/groovy/interaction/PublisherSubscriber2.groovy: -------------------------------------------------------------------------------- 1 | package interaction 2 | 3 | interface Subscriber2 { 4 | void receive(String message) 5 | boolean isActive() 6 | } 7 | 8 | class Publisher2 { 9 | List subscribers = [] 10 | 11 | void publish(String message) { 12 | for (subscriber in subscribers) { 13 | try { 14 | if (subscriber.active) { 15 | subscriber.receive(message) 16 | } 17 | } catch (ignored) {} 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/test/groovy/state/AccountTest.java: -------------------------------------------------------------------------------- 1 | package state; 2 | 3 | import java.math.BigDecimal; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | 9 | public class AccountTest { 10 | @Test 11 | public void withdrawSomeAmount() { 12 | // given 13 | Account account = new Account(BigDecimal.valueOf(5)); 14 | 15 | // when 16 | account.withdraw(BigDecimal.valueOf(2)); 17 | 18 | // then 19 | assertEquals(BigDecimal.valueOf(3), account.getBalance()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/groovy/interaction/PublisherSubscriberSpec.groovy: -------------------------------------------------------------------------------- 1 | package interaction 2 | 3 | import spock.lang.Specification 4 | 5 | class PublisherSubscriberSpec extends Specification { 6 | def pub = new Publisher() 7 | def sub1 = Mock(Subscriber) 8 | def sub2 = Mock(Subscriber) 9 | 10 | def setup() { 11 | pub.subscribers << sub2 << sub1 12 | } 13 | 14 | def "delivers messages to all subscribers"() { 15 | when: 16 | pub.publish("msg") 17 | 18 | then: 19 | 1 * sub1.receive("msg") 20 | 1 * sub2.receive("msg") 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/state/Account.java: -------------------------------------------------------------------------------- 1 | package state; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class Account { 6 | private BigDecimal balance = new BigDecimal(0); 7 | 8 | public Account(BigDecimal initial) { 9 | balance = initial; 10 | } 11 | 12 | public BigDecimal getBalance() { 13 | return balance; 14 | } 15 | 16 | public void withdraw(BigDecimal amount) { 17 | if (amount.compareTo(BigDecimal.ZERO) < 0) { 18 | throw new NegativeAmountWithdrawnException(amount); 19 | } 20 | balance = balance.subtract(amount); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This GitHub project contains slides and source code for the presentation "Smarter Testing With Spock". 2 | 3 | To build the project and execute all its Spock specifications, type: 4 | 5 | ./gradlew build (Windows: gradlew build) 6 | 7 | To create an Eclipse project for the project, type: 8 | 9 | ./gradlew eclipse (Windows: gradlew eclipse) 10 | 11 | To create an IDEA project for the project, type: 12 | 13 | ./gradlew idea (Windows: gradlew idea) 14 | 15 | If you have any further questions, the Spock mailing list is always here to help: http://forum.spockframework.org 16 | 17 | Enjoy! 18 | The Spock team 19 | -------------------------------------------------------------------------------- /src/test/groovy/interaction/PublisherSubscriberSpec2.groovy: -------------------------------------------------------------------------------- 1 | package interaction 2 | 3 | import spock.lang.Specification 4 | 5 | class PublisherSubscriberSpec2 extends Specification { 6 | def pub = new Publisher() 7 | def sub1 = Mock(Subscriber) 8 | def sub2 = Mock(Subscriber) 9 | 10 | def setup() { 11 | pub.subscribers << sub1 << sub2 12 | } 13 | 14 | def "can cope with misbehaving subscribers"() { 15 | when: 16 | pub.publish("msg") 17 | 18 | then: 19 | 1 * sub1.receive("msg") >> { throw new Exception() } 20 | 1 * sub2.receive("msg") 21 | } 22 | } -------------------------------------------------------------------------------- /src/test/groovy/interaction/PublisherSubscriberSpec3.groovy: -------------------------------------------------------------------------------- 1 | package interaction 2 | 3 | import spock.lang.Specification 4 | 5 | class PublisherSubscriberSpec3 extends Specification { 6 | def pub = new Publisher() 7 | def sub1 = Mock(Subscriber) 8 | 9 | def setup() { 10 | pub.subscribers << sub1 11 | } 12 | 13 | def "delivers messages in the order they are published"() { 14 | when: 15 | pub.publish("msg1") 16 | pub.publish("msg2") 17 | 18 | then: 19 | 1 * sub1.receive("msg1") 20 | 21 | then: 22 | 1 * sub1.receive("msg2") 23 | } 24 | } -------------------------------------------------------------------------------- /src/test/groovy/extension/builtin/JUnitRules.groovy: -------------------------------------------------------------------------------- 1 | package extension.builtin 2 | 3 | import spock.lang.Specification 4 | import org.junit.rules.TestName 5 | import org.junit.Rule 6 | import org.junit.rules.TemporaryFolder 7 | import spock.lang.Shared 8 | 9 | class JUnitRules extends Specification { 10 | @Rule TemporaryFolder tempFolder 11 | @Shared File file 12 | 13 | def "a file based test"() { 14 | when: 15 | file = tempFolder.newFile("foo.txt") 16 | 17 | then: 18 | file.exists() 19 | } 20 | 21 | def "by now the file has been deleted"() { 22 | expect: 23 | !file.exists() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/groovy/interaction/Publisher2SubscriberSpec.groovy: -------------------------------------------------------------------------------- 1 | package interaction 2 | 3 | import spock.lang.Specification 4 | 5 | class Publisher2SubscriberSpec extends Specification { 6 | def pub = new Publisher2() 7 | def sub1 = Mock(Subscriber2) 8 | def sub2 = Mock(Subscriber2) 9 | 10 | def setup() { 11 | pub.subscribers << sub1 << sub2 12 | } 13 | 14 | def "delivers messages to all active subscribers"() { 15 | sub1.active >> true 16 | sub2.active >> false 17 | 18 | when: 19 | pub.publish("msg") 20 | 21 | then: 22 | 1 * sub1.receive("msg") 23 | 0 * sub2.receive(_) 24 | } 25 | } -------------------------------------------------------------------------------- /src/test/groovy/datadriven/AccountSpecDataDriven.groovy: -------------------------------------------------------------------------------- 1 | package datadriven 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | import state.Account 6 | 7 | class AccountSpecDataDriven extends Specification { 8 | //@Unroll 9 | def "withdraw some amount"() { 10 | given: 11 | def account = new Account(balance) 12 | 13 | when: 14 | account.withdraw(withdrawn) 15 | 16 | then: 17 | account.balance == remaining 18 | 19 | where: 20 | balance | withdrawn || remaining 21 | 5.0 | 2.0 || 3.0 22 | 4.0 | 0.0 || 4.0 23 | 4.0 | 4.0 || 0.0 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/groovy/datadriven/AccountSpecDataDriven2.groovy: -------------------------------------------------------------------------------- 1 | package datadriven 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | import state.Account 6 | 7 | @Unroll 8 | class AccountSpecDataDriven2 extends Specification { 9 | def "withdrawing #withdrawn from account with balance #balance"() { 10 | given: 11 | def account = new Account(balance) 12 | 13 | when: 14 | account.withdraw(withdrawn) 15 | 16 | then: 17 | account.balance == old(account.balance) - withdrawn 18 | 19 | where: 20 | balance | withdrawn 21 | 5.0 | 2.0 22 | 4.0 | 1.0 23 | 4.0 | 4.0 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/groovy/state/AccountSpec4.groovy: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | import spock.lang.Specification 4 | 5 | class AccountSpec4 extends Specification { 6 | def "can't withdraw a negative amount"() { 7 | given: 8 | def account = new Account(5.0) 9 | 10 | when: 11 | account.withdraw(-1.0) 12 | 13 | then: 14 | NegativeAmountWithdrawnException e = thrown() 15 | e.amount == -1.0 16 | } 17 | 18 | def "withdrawing some amount decreases the balance by exactly that amount"() { 19 | def account = new Account(5.0) 20 | 21 | when: 22 | account.withdraw(2.0) 23 | 24 | then: 25 | account.balance == old(account.balance) - 2.0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/groovy/extension/custom/ReportExtension.groovy: -------------------------------------------------------------------------------- 1 | package extension.custom 2 | 3 | import org.spockframework.runtime.AbstractRunListener 4 | import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension 5 | import org.spockframework.runtime.model.SpecInfo 6 | import org.spockframework.runtime.model.FeatureInfo 7 | 8 | class ReportExtension extends AbstractAnnotationDrivenExtension { 9 | @Override 10 | void visitSpecAnnotation(Report annotation, SpecInfo spec) { 11 | spec.addListener(new AbstractRunListener() { 12 | @Override 13 | void afterFeature(FeatureInfo feature) { 14 | for (block in feature.blocks) { 15 | for (text in block.texts) { 16 | println text 17 | } 18 | } 19 | } 20 | }) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/groovy/exercise/CodeMakerSpec.groovy: -------------------------------------------------------------------------------- 1 | package exercise 2 | 3 | import spock.lang.Specification 4 | 5 | class CodeMakerSpec extends Specification { 6 | Workshop workshop = Mock() 7 | CodeMaker geek = new CodeMaker("Fred", workshop) 8 | 9 | def "tweets the correct message"() { 10 | } 11 | 12 | def "tweets only about himself"() { 13 | } 14 | 15 | def "stays calm when a tweet blows up"() { 16 | } 17 | 18 | def "registers a buddy when asked to invites him"() { 19 | } 20 | 21 | def "only registers a buddy if the workshop isn't booked out"() { 22 | } 23 | 24 | def "bulk-registers buddies in the order they get invited"() { 25 | } 26 | 27 | def "gets angry when asked to invite a buddy who's already registered"() { 28 | } 29 | 30 | def "honestly reports his score"() { 31 | } 32 | 33 | def "gets excited when he cracks the highscore"() { 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/groovy/datadriven/AccountSpecDatabaseDriven.groovy: -------------------------------------------------------------------------------- 1 | package datadriven 2 | 3 | import spock.lang.Specification 4 | 5 | import spock.lang.Shared 6 | import groovy.sql.Sql 7 | import spock.lang.Unroll 8 | import state.Account 9 | 10 | @Unroll 11 | class AccountSpecDatabaseDriven extends Specification { 12 | @Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver") 13 | 14 | def setupSpec() { 15 | sql.execute("create table accountdata (id int primary key, balance decimal, withdrawn decimal, remaining decimal)") 16 | sql.execute("insert into accountdata values (1, 5.0, 2.0, 3.0), (2, 4.0, 0.0, 4.0), (3, 4.0, 4.0, 0.0)") 17 | } 18 | 19 | def "withdrawing #withdrawn from account with balance #balance leaves #remaining"() { 20 | given: 21 | def account = new Account(balance) 22 | 23 | when: 24 | account.withdraw(withdrawn) 25 | 26 | then: 27 | account.balance == remaining 28 | 29 | where: 30 | [balance, withdrawn, remaining] << sql.rows("""select balance, withdrawn, remaining from accountdata""") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/groovy/exercise/CodeMaker.groovy: -------------------------------------------------------------------------------- 1 | package exercise 2 | 3 | import groovy.transform.InheritConstructors 4 | 5 | class CodeMaker { 6 | String name 7 | Workshop workshop 8 | 9 | CodeMaker(String name, Workshop workshop) { 10 | this.name = name 11 | this.workshop = workshop 12 | } 13 | 14 | void tweetAboutWorkshop(String message) { 15 | try { 16 | workshop.tweet(this, message) 17 | } catch (TweetException ignored) {} 18 | } 19 | 20 | void invite(CodeMaker buddy) { 21 | if (workshop.isRegistered(buddy)) { 22 | throw new FoulBuddyException("Are you trying to play a double game on me?") 23 | } 24 | if (!workshop.fullyBooked) { 25 | workshop.register(buddy) 26 | } 27 | } 28 | 29 | void bulkInvite(List buddies) { 30 | buddies.each { invite(it) } 31 | } 32 | 33 | String submitExercise(String solution) { 34 | def id = workshop.submitExercise(this, solution) 35 | def highScores = workshop.highScores 36 | def myScore = highScores.find { it.id == id } 37 | if (myScore == workshop.highScores[0]) { 38 | return "I've cracked the highscore!" 39 | } else { 40 | return "I've scored $myScore.score points" 41 | } 42 | } 43 | } 44 | 45 | interface Workshop { 46 | boolean isFullyBooked() 47 | boolean isRegistered(CodeMaker geek) 48 | List getHighScores() 49 | 50 | void register(CodeMaker geek) 51 | String tweet(CodeMaker geek, String message) 52 | int submitExercise(CodeMaker geek, String solution) 53 | } 54 | 55 | class HighScore { 56 | int id 57 | CodeMaker geek 58 | int score 59 | } 60 | 61 | @InheritConstructors 62 | class FoulBuddyException extends RuntimeException {} 63 | 64 | class TweetException extends RuntimeException {} 65 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/test/groovy/state/Conditions.groovy: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | import spock.lang.Specification 4 | 5 | import static spock.util.matcher.HamcrestMatchers.closeTo 6 | import static spock.util.matcher.HamcrestSupport.that 7 | 8 | class Conditions extends Specification { 9 | def "when-then style"() { 10 | when: 11 | def x = Math.max(5, 9) 12 | 13 | then: 14 | x == 9 15 | } 16 | 17 | def "expect style"() { 18 | expect: 19 | Math.max(5, 9) == 9 20 | } 21 | 22 | def "more complex conditions"() { 23 | expect: 24 | getGermanCarBrands().any { it.size() >= 3 } 25 | } 26 | 27 | def "Hamcrest matchers"() { 28 | expect: 29 | that computeAnswerToTheUniverse(), closeTo(42, 0.01) 30 | } 31 | 32 | def "compare text"() { 33 | expect: 34 | generateLoremIpsum() == 35 | """ 36 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec pulvinar, 37 | nibh a ornare tampor, tortor nisl consequat nisi, non varius elit massa 38 | non ligula. Integer sit amet congue ligula. Nam lectus dui, rutrum a 39 | mattis id, imperdiet eget ipsum. Nullam sollicitudin nunc vitae urna 40 | fermentum pellentesque. In libero leo, m0llis posuere vulputate nec, 41 | sagittis at nulla. Nullam convallis nulla odio. Duis nisi mi, ultricies 42 | Id venenatis ac, condimentum tempus ligula. 43 | """ 44 | } 45 | 46 | def "compare sets"() { 47 | def set1 = ["Fred", "Wilma", "Dino"] as Set 48 | def set2 = ["Fred", "Wilma", "Dino"] as Set 49 | //def set2 = ["Wilma", "Dino", "Barney"] as Set 50 | 51 | expect: 52 | set1 == set2 53 | } 54 | 55 | def "compare POJOs"() { 56 | def person1 = new Person("Fred", 30, "Winstor Rd.") 57 | def person2 = new Person("Fred", 30, "Winstor Rd.") 58 | //def person2 = new Person("Frog", 30, "Windsor Rd.") 59 | 60 | expect: 61 | person1 == person2 62 | } 63 | 64 | def "with method"() { 65 | def person = new Person("Fred", 30, "Winstor Rd.") 66 | 67 | expect: 68 | with(person) { 69 | name == "Fred" 70 | age == 30 71 | address == "Winstor Rd." 72 | } 73 | } 74 | 75 | 76 | 77 | 78 | 79 | private getGermanCarBrands() { ["audi", "bmw", "porsche"] } 80 | 81 | private computeAnswerToTheUniverse() { 42.00387455 } 82 | 83 | private generateLoremIpsum() { 84 | """ 85 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec pulvinar, 86 | nibh a ornare tampor, tortor nisl consequat nisi, non varius elit massa 87 | non ligula. Integer sit amet congue ligula. Nam lectus dui, rutrum a 88 | mattis id, imperdiet eget ipsum. Nullam sollicitudin nunc vitae urna 89 | fermentum pellentesque. In libero leo, m0llis posuere vulputate nec, 90 | sagittis at nulla. Nullam convallis nulla odio. Duis nisi mi, ultricies 91 | Id venenatis ac, condimentum tempus ligula. 92 | """ 93 | } 94 | 95 | private static class Person { 96 | String name 97 | int age 98 | String address 99 | 100 | Person(String name, int age, String address) { 101 | this.name = name 102 | this.age = age 103 | this.address = address 104 | } 105 | 106 | boolean equals(Object other) { 107 | other instanceof Person && other.name == name && 108 | other.age == age && other.address == address; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /exercises.md: -------------------------------------------------------------------------------- 1 | Exercises 2 | ========= 3 | 4 | Setup (15 min) 5 | -------------- 6 | 7 | 0. Set up the "smarter-testing-with-spock" project 8 | a. Clone git@github.com:spockframework/smarter-testing-with-spock.git. Or, if you don't have Git installed, download and extract the zipball from https://github.com/spockframework/smarter-testing-with-spock (look out for the 'ZIP' button). 9 | b. Open a console and cd into the project directory. Execute `./gradlew build` (Windows: `gradlew build`). If the build passes, you are all set. If it fails, speak up. :-) 10 | c. (optional) Generate an IDEA project with `./gradlew idea` (Windows: `gradlew idea`). Open the project in IDEA. 11 | d. (optional) Generate an Eclipse project with `./gradlew eclipse` (Windows: `gradlew eclipse`). Import the project into Eclipse ("Import -> Existing Projects Into Workspace"). Note: For this to work, you need to have Groovy-Eclipse installed, including its Groovy 2.0 compiler support. For installation instructions, visit http://groovy.codehaus.org/Eclipse+Plugin. 12 | 13 | State Based Testing (15 min) 14 | ---------------------------- 15 | 16 | 1. Write a spec for the `java.util.Stack` class 17 | a. Name the spec `StackSpec.groovy` 18 | b. Experiment with different ways of setting up the Stack class (field initializer, `setup()` method, implicit/explicit `setup:` block) 19 | c. Use self-explaining method names 20 | d. Consider a few different initial states (empty stack, stack with single element, stack with multiple elements). If you like, use a separate spec per initial state. Note that you can have multiple specs in the same file. 21 | e. Describe a few error conditions. Use the `thrown` method to describe expected exceptions. 22 | f. Use the `old` method to describe how an operation changes the contents of the stack. 23 | g. Change one of the spec methods to use given-when-then style. Annotate the blocks with descriptions (e.g. `when: "an element is added"`). 24 | h. Experiment with a @Shared stack. 25 | 26 | Data Driven Testing (15 min) 27 | ---------------------------- 28 | 29 | 2. Enhance StackSpec by using data-driven testing for some of its methods 30 | a. Try to cover additional interesting cases that you didn't cover in 1. 31 | b. Use data pipes to provide the data. 32 | c. Refactor the data pipes to a data table. Use || to set apart inputs and outputs. 33 | d. Put @Unroll on a method/class to see which iteration(s) fail. 34 | e. Add an @Unroll naming pattern to see which inputs made the test fail. 35 | f. Write a test that generates data instead of hardcoding it in a data table. 36 | 37 | Interaction Based Testing (15 min) 38 | ---------------------------------- 39 | 40 | 3. CodeMaker Workshop 41 | 42 | a. Fill in the methods in `exercise/CodeMakerSpec.groovy` and describe the interactions between a `CodeMaker` (the class under specification) and a `Workshop` (the collaborator). The latter classes are declared in `exercise/CodeMaker.groovy`. 43 | b. Introduce a few bugs into how `CodeMaker` interacts with `Workshop`. Execute the spec and study the failure messages. 44 | 45 | Extensions (15 min) 46 | ------------------- 47 | 48 | 4. Experiment with some of Spock's built-in extensions 49 | a. Use @Ignore to ignore a single method/class. 50 | b. Use @IgnoreRest to ignore all but the annotated methods. 51 | c. Try @Ignore and @IgnoreRest together with spec inheritance (e.g. ignore a method in a base class). 52 | d. Write a test that sleeps for a while and give it a (shorter) `@Timeout`. Observe how the test gets stopped after the timeout has elapsed. Modify the test so that it swallows the first few `java.lang.InterruptedException`s and keeps sleeping. Observe how it eventually gets stopped. Verify that methods with a `@Timeout` annotation run in the same thread as methods without such an annotation. 53 | e. Write a class whose `close()` method throws an exception. Create two instance in the spec and annotate them with `@AutoCleanup`. Verify that `close()` gets called for both of them. Rename `close()` to `dispose()` and adapt `@AutoCleanup` accordingly. 54 | f. Write a spec whose methods simulate testing the individual parts of a workflow (e.g. "user logs in", "user adds item to shopping cart", etc.). It's good enough if the methods print out what their part is. Annotate the spec with @Stepwise. Observe that the methods gets exercised in the declared order. Try to run just a single method (don't choose the first one). 55 | 56 | Other Stuff (15 min) 57 | -------------------- 58 | 59 | 5. Experiment with Spock's configuration options 60 | a. Create `.spock/SpockConfig.groovy` in your user home directory 61 | b. Configure Spock *not* to filter stack traces 62 | c. Create a few annotations and tag some spec methods/classes with them. Try to include/exclude the tagged methods/classes. 63 | d. Write a spec containing methods that always pass, always fail, and sometimes fail. Some of the methods should take some time to complete. Enable `optimizeRunOrder` and observe how Spock varies the run order of methods over time. 64 | 65 | 6. Experiment with the spock-example project 66 | a. Clone git@github.com:spockframework/spock-example.git or download and extract its zipball from https://github.com/spockframework/spock-example (look out for the "ZIP" button) 67 | b. Take a look at the Gradle, Maven, and Ant builds and experiment with them 68 | 69 | 7. Get familiar with Spock documentation 70 | a. Browse the old (http://wiki.spockframework.org) and new (http://docs.spockframework.org/en/latest/) Spock documentation -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | --------------------------------------------------------------------------------