Ian Kelly kellizer github comkellizer Testing With Spock
Ian Kelly @kellizer github. com/kellizer Testing++ { With Spock & Geb
Spock The Logical Testing Framework • Spock – A logical way to test • Groovy based framework • Follows Behaviour Driven Design (BDD) Paradigms • (Tests are live documents) • Works with IDEs/CI servers Out of the Box • & live with existing tests • (And Bring Fun to Testing) We are working with: Spock 0. 7 Geb 0. 9 Groovy 2. 0
Anatomy of a Spock Test Fixture Specification Collaborator Feature System Under Specification Collaborator
Our First Specification /** * System Under Specification */ public class Dog { public String speak() { return "WOOF"; } } All Specifications extend spock. lang. Specification class Dog. Specification extends Specification { def "Dogs should bark when told to speak"() { setup: def chip = new Dog() expect: chip. speak() == 'WOOF' } Test Execution
Feature Specifications Hold 1 or more Feature(s) class Dog. Specification extends Specification { def "Dogs should bark when told to speak"() { setup: def chip = new Dog() expect: chip. speak() == 'WOOF' } Feature Method (The Test)
Lets Run The Test Dogs Should Say bark when told to speak Test Passed
Lets Run The Test What happens if chip speaks like a duck? Test Failed
Feature Methods Feature Method Contain Blocks class Dog. Specification extends Specification { def "Dogs should bark when told to speak"() { setup: def chip = new Dog() expect: chip. speak() == 'WOOF' } • • • setup: or given: - to setup the test expect: - to test when there are no side effects when: /then: – apply the behaviour then validate the state cleanup: – called at the end of the feature execution where: – provides datasets to a feature Spock Recognise Feature Methods and executes them
Feature Methods - Blocks setup: (given: ) • Initialization of the feature • Sits at the top of the method • Can be termed as ‘given: ’ • Optional & non-repeatable
Feature Methods - Blocks when/then: • when: stimulates the SUS • then: validates the SUS • Expressions are assertions def "Check that Customer can have a zero balance"() { setup: def bank. Account. With. Out. Overdraft = new Bank. Account() when: "test a number of deposits and withdrawals" bank. Account. With. Out. Overdraft. deposit. Funds(100) bank. Account. With. Out. Overdraft. withdraw. Amount(100) then: "The account should be zero and no exception thrown" bank. Account. is. Active. Account() bank. Account. balance == 0 }
Feature Methods - Blocks expect: • Combines a stimulus and response • More limited than a then: block • Use with pure functions def "2 should be larger than 1"() { expect: Math. max(1, 2) == 2 } Stimulus Expected Response
Feature Methods - Blocks cleanup: Use to free resources • called Even if expectation is thrown • Comparable to the java finally block def "Test with a tmp file"() { setup: def file = new File("/new/file/tmp. out") //Do stuff with the FS file cleanup: file. delete() }
Feature Methods - Blocks then: with old: def bank. Account = new Bank. Account() def "Check that the bank account accepts a deposit"() { when: "we deposit 100 zl into the account" bank. Account. deposit. Funds(100) then: "expect the old bal. to be 0 and new bal. to be 100" old(bank. Account. balance) == 0 bank. Account. balance == 100 } Captures the account balance before applying stimulus
Feature Methods - Blocks Descriptions on blocks: def bank. Account = new Bank. Account() def "Check that the bank account accepts a deposit"() { when: "we deposit 100 zl into the account" bank. Account. deposit. Funds(100) then: "expect the old bal. to be 0 and new bal. to be 100" old(bank. Account. balance) == 0 bank. Account. balance == 100 }
Feature Methods - Blocks then/and def "Check that Authorised Overdrafts work as expected"() { setup: def bank. Account. With. Overdraft = new Bank. Account() bank. Account. With. Overdraft. set. Allowed. Over. Draft(500) when: "test a number of deposits and withdrawals" bank. Account. With. Overdraft. deposit. Funds(100) and: "Note the and label" bank. Account. With. Overdraft. withdraw. Amount(50) and: "Balance should be 50 at this point" bank. Account. With. Overdraft. withdraw. Amount(30) and: "Balance should be 20 at this point" bank. Account. With. Overdraft. withdraw. Amount(100) then: "We should now be left with a balance of 80 overdrawn" bank. Account. With. Overdraft. is. In. Overdraft() bank. Account. With. Overdraft. balance == -80 }
Feature Methods Thrown() within then: block def "Check that Customer cannot go over their allowed amount"() { setup: def bank. Account. With. Out. Overdraft = new Bank. Account() when: "test a number of deposits and withdrawals" bank. Account. With. Out. Overdraft. deposit. Funds(100) bank. Account. With. Out. Overdraft. withdraw. Amount(110) then: thrown(Insufficient. Funds. Exception) } Expected outcome to be a thrown Insufficient. Funds. Exception
Feature Methods not. Thrown() Expected outcome to be a thrown Insufficient. Funds. Exception
Driving Tests with Data
Feature Methods - Blocks where: • Same Logic, Different Data • Allows for Data Driven Feature Methods • Last in Method – Cannot be repeated • Data can be fed from external sources • Support multiple formats • | && || pipes to delimit values (Data Tables)
Feature Methods - Blocks Where def "Validate Age Calculation From DOB"() { when: Date date = new Simple. Date. Format("dd-mm-yyyy", Locale. ENGLISH). parse(dob) def calculated. Age = date. Service. age. From. DOB(date) calculated. Age == age where: Dob <<["01 -02 -1980”, "01 -01 -1990", "12 -12 -1958"] age << [33, 23, 55] } then:
Feature Methods - Blocks Where – Data Tables def "Validate Age Calculation From DOB"() { when: Date date = new Simple. Date. Format("dd-mm-yyyy", Locale. ENGLISH). parse(dob) def calculated. Age = date. Service. age. From. DOB(date) then: calculated. Age == age where: dob || age "01 -02 -1980" || 33 "01 -01 -1990" || 23 "12 -12 -1958" || 55 }
Feature Methods - Blocks Where – Database Driven @Unroll //see all the tests. def "maximum of two numbers, a=#a, b=#b so max would be #c"() { expect: Math. max(a, b) == c where: [a, b, c] << sql. rows("select a, b, c from maxdata") }
Feature Methods - Blocks where: Wrong Stimulus Test Output
@Unroll • Reports Feature Executions independently • Has no affect on the execution, just reporting
Mocking
Feature Methods - Mocking Object being mocked Cardinality Method Argument Return Object
Feature Methods - Mocking Cardinality Description None Optional (n. . _) At least n times n* Exactly n times (_. . n) Up to n times
Feature Methods - Mocking Argument Constraint Description value Argument Equals value !value Argument Not Equal *_ Any number of arguments _ Any argument !null Non-null Argument
Feature Methods - Mocking Return Values Description >> single return value, repeated indefinitely >>> multiple return values, last one repeated indefinitely >> { <<ACTION>> } custom action
Feature Methods - Mocking • Order of interaction not defined • Extra then: guarantees ordering def "example showing the ordered interaction"() { given: def subscriber 1 = Mock(Subscriber) Subscriber subscriber 2 = Mock() Subscriber subscriber 3 = Mock() Publisher publisher = new Standard. Publisher(subscribers: [subscriber 1, subscriber 2, subscriber 3]); Event event = Mock() when: publisher. send(event) then: 1 * subscriber 1. receive(event) 1 * subscriber 2. receive(event) // order of interaction within same then-block is not defined; // hence, subscriber 1 might be notified either before or after subscriber 2 then: // must come after all interactions in previous then-blocks; // hence, subscriber 3 must be notified after both subscriber 1 and subscriber 2 1 * subscriber 3. receive(event) }
Feature Methods - Mocking def "load customer and apply airmiles but customer doesn't exist"() { when: air. Miles. Processor. apply. Air. Miles(332211, 4000) then: 1 * customer. Repository. find. By. Id(332211) >> null thrown(Illegal. State. Exception) } Output Expected 2 invocations, (only 1 registered)
More Goodness
Fields Created per feature method or shared • New Instance Per Feature Method @Shared Annotation • Sharing Fields between Feature Methods class Database. Driven. Specification extends Specification { @Shared sql = Sql. new. Instance("jdbc: h 2: mem: ", "org. h 2. Driver") // normally an external database would be used, // and the test data wouldn't have to be inserted here def setup. Spec() { sql. execute("create table maxdata (id int primary key, a int, b int, c int)") sql. execute("insert into maxdata values (1, 3, 7, 7), (2, 5, 4, 5), (3, 9, 9, 9)") }
Fixture Methods Invoke setup. Spec() Invoke setup() Invoke feature method() Invoke cleaup() Invoke cleanup. Spec()
Extensions @Ignore. If • Ignores if a certain condition is true @Requires • Inverse of @Ignore. If – only runs when condition is true
Extensions @Ignore • Will ignore test(s) • Can be place at feature or specification level @Ignore. Rest • Ignores all other methods in the specification @Ignore. If • Ignores if a certain condition is true
@Timeout • Allows you to timeout your test if not completed in a predetermined time @Timeout(value = 5) def "timeout feature method if not completed after 5 seconds"() { def person = new Customer(name: "Fred", age: 22) when: person. age = 42 Thread. sleep(7000) then: person. age == 42 } • Result
@Auto. Cleanup • Will invoke close() on field (can be any method) • Exception will be reported but not classed as a failure • Preferred to cleanup()/cleanup. Spec( @Auto. Cleanup(quiet = true) def input = new File. Input. Stream("myfile. txt") def "some input stream tests"() { // test uses file input stream }
Feature Methods Extended assert def "Check that Customer can have a balance of total plus 1"() { setup: def bank. Account. With. Out. Overdraft = new Bank. Account() when: bank. Account. With. Out. Overdraft. deposit. Funds(100) then: assert bank. Account. With. Out. Overdraft. balance == 100, “bank account total should be 100" } Test Output
More Gems - @Stepwise • Run in Defined Order • If 1 Test fails, the remainder get skipped @Stepwise class Stepwise. Specification extends Specification { def "step 1"() { println("step 1") expect: true } def "step 2"() { expect: true println("step 2") } def "step 3"() { expect: true println("step 3") } }
More Gems - @Stepwise class Stepwise. Specification extends Specification { def "step 1"() { println("step 1") expect: false } def "step 2"() { expect: true println("step 2") } def "step 3"() { expect: true println("step 3") } }
More Gems - @ Include/Exclude class Include. Extension. Spec extends Specification { static { //System. set. Property "spock. configuration", "Include. Fast. Config. groovy" // Alternatively, try this: System. set. Property "spock. configuration", "Exclude. Slow. Config. groovy" } @Fast def "a fast method"() { expect: true } @Slow def "a slow method"() { expect: true } def "a neither fast nor slow method"() { expect: true } } runner { exclude Slow } Exclude. Slow. Config. groovy
Geb
Geb Awesome Browser Automation • Introduces the power of Web. Driver to your tests • Complements Spock but is Independent Project • Uses Expressiveness of Groovy • Has a j. Query like API (is not j. Query) • Has Domain Modelling Support via Pages/Modules
Geb – Supported Browsers Web. Driver • First Class Support • Firefox • Internet Explorer • Google Chrome • Mobile (Ipad/Iphone/Android) • Remote Browsers • Headless Browser • Phantom. JS • W 3 C Standard • http: //www. w 3. org/TR/webdriver/
Geb abstracts Web. Driver Interactions
Spock & Geb Spock • Our Test Specification Geb/Spock Adapter • Geb Support for Spock Geb DSL • The Geb DSL Webdriver • Browser API support Browser • FF • Chrome • IE etc. • What Web we are Application testing
Spock Test (Using Geb) class Login. Story. Specification extends Geb. Reporting. Spec. With. Pause { def "Login with an correct password"() { given: go "/login. html" $("form"). with { username = 'admin' password = 'password' remember = true } when: $("button"). click() then: title == "Authenticated User" }
Geb – Page Object Pattern • Model web pages for re-useable and maintainable code • Reduces duplication • Abstract the HTML implementation structure • Changes happen • Simply change in a single location def "Login to The Secure Admin Server"() { when: to Login. Page login("admin", "password") then: at Authenticated. Admin. Page }
Geb – Page Object Pattern class Authenticated. Admin. Page extends Page { static content = { //optional content administrators. Name(required: false) { $("h 2") } //stacked users(wait: true) { $("li. span 5. clearfix") } user { i -> users[i] } user. Name { i -> users[i]. find("h 4", 0) } } } • Simply change elements in a single location
Geb – j. Query(ish) API • Copying j. Query has many benefits • CSS Content Lookup • Selecting content on a page • Traversing to/around content • Methods for retrieving relative content • Fluent API • Geb (like j. Query) uses a “$” function Geb != j. Query
Geb – j. Query(ish) API Selecting content – DOM Searching //match all ‘div’ elements on the page $("div”) //match all ‘div’ with a title attribute value of ‘section’ $("div", title: "section") //match the first ‘div’ with the class ‘main’ $("div. main", 0) //match all 'div' elements with the class ‘main’ $("div. main”) Navigator Object
Geb – j. Query(ish) API Selecting content – CSS Selector $("div. some-class p: first[title='something']")
Geb – j. Query(ish) API Selecting content – Index & Ranges <p>a</p> <p>b</p> <p>c</p> $("p", 0). text() == "a” $("p", 2). text() == "c” $("p", 0. . 1)*. text() = ["a", "b"] $("p", 1. . 2)*. text() = ["b", "c"]
Geb – j. Query(ish) API Index & Ranges <p attr 1="a" attr 2="b">p 1</p> <p attr 1="a" attr 2="c">p 2</p> $("p", attr 1: "a", attr 2: "b"). size() == 1 $("p", attr 1: "a"). size() == 2 $("p", text: "p 1", attr 1: "a"). size() == 1 $("p", text: ~/p. /). size() == 2 $("p", text: starts. With("p")). size() == 2 $("p", text: ends. With("2")). size() == 1 $("p", text: ~/p. /). size() == 2
Geb – j. Query(ish) API Pattern Matching Keyword Description starts. With Matches values that start with the given value contains Matches values that contain the given value anywhere ends. With Matches values that end with the given value contains. Word Matches values that contain the given value surrounded by either whitespace or the beginning or end of the value not. Starts. With Matches values that DO NOT start with the given value not. Contains Matches values that DO NOT contain the given value anywhere not. Ends. With Matches values that DO NOT end with the given value not. Contains. Word Matches values that DO NOT contain the given value surrounded by either whitespace or the beginning or end of the value
Geb – j. Query(ish) API Traversing content - Finding & Filtering <div class="a"> <p class="b">geb</p> </div> <div class="b"> <input type="text"/> </div> $("div"). find(". b") //p. b $("div"). filter(". b") //div. b $(". b"). not("p") //div. b $("div"). has("p") //div containing p //div containing the input with a type attribute of “text” $("div"). has("input", type: "text")
Geb – j. Query(ish) API Traversing content $("p. d"). previous() // 'p. c' $("p. e"). prev. All() // 'p. c' & 'p. d' $("p. d"). next() // 'p. e' $("p. c"). next. All() // 'p. d' & 'p. e' $("p. d"). parent() // 'div. b' $("p. c"). siblings() // 'p. d' & 'p. e' $("div. a"). children() // 'div. b' & 'div. f' <div class="a"> <div class="b"> <p class="c"></p> <p class="d"></p> <p class="e"></p> </div> <div class="f"></div>
Geb – Screenshots/HTML Reports Automatically captures the State • As a screenshot of the page • Also captures the HTML content • Optional (extend Geb. Reporting. Spec)
Geb – Configuration & Driver Management Looks for Geb. Config class or Geb. Config. groovy file on classpath Manages and optimises webdriver instances & clearing cookie state
Geb – Direct Downloading Browser. drive { go "http: //myapp. com/login" // login username = "me" password = "secret" login(). click() // now find the pdf download link def download. Link = $("a. pdf-download-link") // now get the pdf bytes def bytes = download. Bytes(download. Link. @href) Lots of work behind the scenes
Geb – CI Test Reports
Bring Gradle to party
Ian Kelly @kellizer github. com/kellizer Spock – http: //docs. spockframework. org Geb – http: //www. gebish. org Sauce Labs – http: //www. saucelabs. com Gradle – http: //www. gradle. org/ Thank You
- Slides: 65