Make Your Testing Groovy
Upcoming SlideShare
Loading in...5
×
 

Make Your Testing Groovy

on

  • 8,966 views

Using the Groovy dynamic language for primarily functional / acceptance / customer / BDD testing with a forward looking perspective. Also considers polyglot options. The techniques and lessons learned ...

Using the Groovy dynamic language for primarily functional / acceptance / customer / BDD testing with a forward looking perspective. Also considers polyglot options. The techniques and lessons learned can be applied to other kinds of testing and are also applicable to similar languages. Drivers and Runners discussed include: Native Groovy, HttpBuilder, HtmlUnit, WebTest, Watij, Selenium, WebDriver , Tellurium, JWebUnit, JUnit, TestNG, Spock, EasyB, JBehave, Cucumber, Robot Framework and FitNesse/Slim. Also looks at JMeter, ScalaCheck, Choco, AllPairs and ModelJUnit

Statistics

Views

Total Views
8,966
Views on SlideShare
8,937
Embed Views
29

Actions

Likes
18
Downloads
375
Comments
1

6 Embeds 29

http://www.slideshare.net 17
https://apps.fujisan.co.jp 8
http://confluence.sebnoumea.gotdns.com 1
http://apps.fujisan.co.jp 1
http://www.aquaazon.com 1
https://twitter.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • Really good presentation on modern QA approaches n general.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Make Your Testing Groovy Make Your Testing Groovy Presentation Transcript

  • Make your Testing Groovy © ASERT 2006-2010 Dr Paul King, ASERT, Australia paulk@asert.com.au, paulk_asert Full: http://www.slideshare.net/paulk_asert/make-tests-groovy
  • What is Groovy? • “Groovy is like a super version of Java. It can leverage Java's enterprise capabilities but also has cool productivity features like closures, DSL support, builders and dynamic typing.” © ASERT 2006-2010 Groovy = Java – boiler plate code + mostly dynamic typing + closures + domain specific languages + builders + metaprogramming + GDK library QCON 2010 - 3
  • What is Groovy? • “Groovy is like a super version of Java. It can leverage Java's enterprise capabilities but also has cool productivity features like closures, DSL support, builders and dynamic typing.” © ASERT 2006-2010 Groovy = Productivity features of Ruby But Java-like syntax (very low learning curve) Leverage enterprise Java features Suitable for non-core developers QCON 2010 - 4
  • What is Groovy? • “Groovy is like a super version of Java. It can leverage Java's enterprise capabilities but also has cool productivity features like closures, DSL support, builders and dynamic typing.” © ASERT 2006-2010 Groovy = Productivity features of Ruby But Java-like syntax (very low learning curve) Leverage enterprise Java features Suitable for non-core developers QCON 2010 - 5
  • What is Groovy? • “Groovy is like a super version of Java. It can leverage Java's enterprise capabilities but also has cool productivity features like closures, DSL support, builders and dynamic typing.” © ASERT 2006-2010 Groovy = Productivity features of Ruby But Java-like syntax (very low learning curve) Leverage enterprise Java features Suitable for non-core developers QCON 2010 - 6
  • What is Groovy? • “Groovy is like a super version of Java. It can leverage Java's enterprise capabilities but also has cool productivity features like closures, DSL support, builders and dynamic typing.” © ASERT 2006-2010 Groovy = Productivity features of Ruby But Java-like syntax (very low learning curve) Leverage enterprise Java features Suitable for non-core developers QCON 2010 - 7
  • Growing Acceptance … A slow and steady start but now gaining in momentum, maturity and mindshare Now free
  • Growing Acceptance … A slow and steady start but now gaining in momentum, maturity and mindshare Now free
  • … Growing Acceptance … What alternative JVM language are you using or intending to use © ASERT 2006-2010 http://www.leonardoborges.com/writings QCON 2010 - 10
  • … Growing Acceptance © ASERT 2006-2010 http://pollpigeon.com/jsf-grails-wicket/r/25665/ QCON 2010 - 11
  • … Growing Acceptance … © ASERT 2006-2010 QCON 2010 - 12
  • The Landscape of JVM* Languages mostly dynamic typing © ASERT 2006-2010 Dynamic features call for dynamic types Java bytecode calls for static types *Java Virtual Machine QCON 2010 - 13
  • Groovy Starter System.out.println("Hello, World!"); // supports Java syntax println 'Hello, World!' // but can remove some syntax String name = 'Guillaume' // Explicit typing/awareness println "$name, I'll get the car." // Gstring (interpolation) def longer = """${name}, the car is in the next row.""" // multi-line, implicit type © ASERT 2006-2010 assert 0.5 == 1/2 // BigDecimal equals() assert 0.1 + 0.2 == 0.3 // and arithmetic def printSize(obj) { // implicit/duck typing print obj?.size() // safe dereferencing } def pets = ['ant', 'bee', 'cat'] // native list syntax pets.each { pet -> // closure support assert pet < 'dog' // overloading '<' on String } // or: for (pet in pets)... QCON 2010 - 14
  • A Better Java... import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { This code List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { is valid String s = (String) strings.get(i); Java and if (s.length() <= length) { result.add(s); valid Groovy } } © ASERT 2006-2010 return result; } public static void main(String[] args) { List names = new ArrayList(); names.add("Ted"); names.add("Fred"); names.add("Jed"); names.add("Ned"); Based on an System.out.println(names); example by Erase e = new Erase(); List shortNames = e.removeLongerThan(names, 3); Jim Weirich System.out.println(shortNames.size()); & Ted Leung for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } } QCON 2010 - 15
  • A Better Java... import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { This code List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { is valid String s = (String) strings.get(i); Java and if (s.length() <= length) { result.add(s); valid Groovy } } © ASERT 2006-2010 return result; } public static void main(String[] args) { List names = new ArrayList(); names.add("Ted"); names.add("Fred"); names.add("Jed"); names.add("Ned"); Based on an System.out.println(names); example by Erase e = new Erase(); List shortNames = e.removeLongerThan(names, 3); Jim Weirich System.out.println(shortNames.size()); & Ted Leung for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } } QCON 2010 - 16
  • ...A Better Java... import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { Do the List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { semicolons String s = (String) strings.get(i); add anything? if (s.length() <= length) { result.add(s); And shouldn‟t } } we us more © ASERT 2006-2010 } return result; modern list public static void main(String[] args) { notation? List names = new ArrayList(); names.add("Ted"); names.add("Fred"); Why not names.add("Jed"); names.add("Ned"); System.out.println(names); import common Erase e = new Erase(); libraries? List shortNames = e.removeLongerThan(names, 3); System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } } QCON 2010 - 17
  • ...A Better Java... class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList() for (String s in strings) { if (s.length() <= length) { result.add(s) } } return result } public static void main(String[] args) { © ASERT 2006-2010 List names = new ArrayList() names.add("Ted"); names.add("Fred") names.add("Jed"); names.add("Ned") System.out.println(names) Erase e = new Erase() List shortNames = e.removeLongerThan(names, 3) System.out.println(shortNames.size()) for (String s in shortNames) { System.out.println(s) } } } QCON 2010 - 18
  • ...A Better Java... class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList() for (String s in strings) { if (s.length() <= length) { result.add(s) Do we need } the static types? } return result Must we always } have a main public static void main(String[] args) { method and © ASERT 2006-2010 List names = new ArrayList() names.add("Ted"); names.add("Fred") class definition? names.add("Jed"); names.add("Ned") System.out.println(names) How about Erase e = new Erase() List shortNames = e.removeLongerThan(names, 3) improved System.out.println(shortNames.size()) consistency? for (String s in shortNames) { System.out.println(s) } } } QCON 2010 - 19
  • ...A Better Java... def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) } } return result } © ASERT 2006-2010 names = new ArrayList() names.add("Ted") names.add("Fred") names.add("Jed") names.add("Ned") System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) for (s in shortNames) { System.out.println(s) } QCON 2010 - 20
  • ...A Better Java... def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) Shouldn‟t we } } have special return result notation for lists? } And special © ASERT 2006-2010 names = new ArrayList() facilities for names.add("Ted") names.add("Fred") list processing? names.add("Jed") Is „return‟ names.add("Ned") System.out.println(names) needed at end? shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) for (s in shortNames) { System.out.println(s) } QCON 2010 - 21
  • ...A Better Java... def removeLongerThan(strings, length) { strings.findAll{ it.size() <= length } } names = ["Ted", "Fred", "Jed", "Ned"] System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) shortNames.each{ System.out.println(s) } © ASERT 2006-2010 QCON 2010 - 22
  • ...A Better Java... def removeLongerThan(strings, length) { strings.findAll{ it.size() <= length } } Is the method names = ["Ted", "Fred", "Jed", "Ned"] now needed? System.out.println(names) shortNames = removeLongerThan(names, 3) Easier ways to System.out.println(shortNames.size()) use common shortNames.each{ System.out.println(s) } methods? © ASERT 2006-2010 Are brackets required here? QCON 2010 - 23
  • ...A Better Java... names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it } © ASERT 2006-2010 QCON 2010 - 24
  • ...A Better Java names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it } © ASERT 2006-2010 [Ted, Fred, Jed, Ned] 3 Ted Jed Ned QCON 2010 - 25
  • Grapes / Grab // Google Collections example @Grab('com.google.collections:google-collections:1.0') import com.google.common.collect.HashBiMap HashBiMap fruit = [grape:'purple', lemon:'yellow', lime:'green'] © ASERT 2006-2010 assert fruit.lemon == 'yellow' assert fruit.inverse().yellow == 'lemon' QCON 2010 - 26
  • Better Design Patterns: Immutable... • Java Immutable Class – As per Joshua Bloch // ... @Override Effective Java public boolean equals(Object obj) { if (this == obj) public final class Punter { return true; private final String first; if (obj == null) private final String last; return false; if (getClass() != obj.getClass()) public String getFirst() { return false; return first; Punter other = (Punter) obj; } if (first == null) { © ASERT 2006-2010 if (other.first != null) public String getLast() { return false; return last; } else if (!first.equals(other.first)) } return false; if (last == null) { @Override if (other.last != null) public int hashCode() { return false; final int prime = 31; } else if (!last.equals(other.last)) int result = 1; return false; result = prime * result + ((first == null) return true; ? 0 : first.hashCode()); } result = prime * result + ((last == null) ? 0 : last.hashCode()); @Override return result; public String toString() { } return "Punter(first:" + first + ", last:" + last + ")"; public Punter(String first, String last) { } this.first = first; this.last = last; } } // ... QCON 2010 - 27
  • ...Better Design Patterns: Immutable... • Java Immutable Class boilerplate – As per Joshua Bloch // ... @Override Effective Java public boolean equals(Object obj) { if (this == obj) public final class Punter { return true; private final String first; if (obj == null) private final String last; return false; if (getClass() != obj.getClass()) public String getFirst() { return false; return first; Punter other = (Punter) obj; } if (first == null) { © ASERT 2006-2010 if (other.first != null) public String getLast() { return false; return last; } else if (!first.equals(other.first)) } return false; if (last == null) { @Override if (other.last != null) public int hashCode() { return false; final int prime = 31; } else if (!last.equals(other.last)) int result = 1; return false; result = prime * result + ((first == null) return true; ? 0 : first.hashCode()); } result = prime * result + ((last == null) ? 0 : last.hashCode()); @Override return result; public String toString() { } return "Punter(first:" + first + ", last:" + last + ")"; public Punter(String first, String last) { } this.first = first; this.last = last; } } // ... QCON 2010 - 28
  • ...Better Design Patterns: Immutable @Immutable class Punter { String first, last © ASERT 2006-2010 } QCON 2010 - 29
  • What we will cover Unit Testing Mock/interaction testing Techniques State-based testing Testing DSLs ATDD/BDD Data-driven Logic-driven © ASERT 2006-2010 Integration Testing Model-driven Performance testing Acceptance Testing All-pairs & Web drivers combinations Non-web drivers Gpars Test runners QCON 2010 - 30
  • Groovy's Value Add for Testing • Unit testing – Built-in asserts, support for JUnit 3&4 and TestNG, GroovyTestCase with shouldFail and other methods – Built-in mocking and compatible with Java mocking • Integration testing – Metaprogramming allows various kinds of IOC like © ASERT 2006-2010 intercepting and hooking up of components – Wealth of GDK methods for Ant, Processes, Files, Threads, etc. make the automating part much simpler • Acceptance Testing and Generally – Allows creation of English-like testing DSLs using Closures, builders, metaprogramming – Simpler syntax great for non hard-core testers – Grapes make tests easier to share QCON 2010 - 31
  • Groovy‟s Power Assert def pets = ['dog', 'cat', 'koala', 'goldfish'] assert pets.findAll{ it.size() > 3 }[0] == 'goldfish' Assertion failed: assert pets.findAll{ it.size() > 3 }[0] == 'goldfish' | | | | | [koala, goldfish] | false [dog, cat, koala, goldfish] koala © ASERT 2006-2010 def pets = ['犬', '猫', 'コアラ', '金魚'] assert pets.findAll{ it.size() > 1 }[0] == '金魚' Assertion failed: assert pets.findAll{ it.size() > 1 }[0] == '金魚' | | | | | [コアラ, 金魚] | false [犬, 猫, コアラ, 金魚] コアラ QCON 2010 - 33
  • Don't Forget: Key Testing Practices • Use testing DSL’s – Use Acceptance Test/Behavior Driven Development (ATDD/BDD) • Look to move up the testing stack – It used to be all about the driver – Now the driver is hidden in the framework or tool stack • Apply good testing practices © ASERT 2006-2010 – Pareto analysis, bug clusters, mutation testing, test early – All pairs/equivalence partitions/orthogonal array testing – Risk-based test selection, coding for testability, use CI – Boundary value analysis, defensive programming • Plug and play testing tools – Run different drivers with different runners and different tools • Complement automated tests with exploration • Expand testing scope – Test environment readiness, test deployments QCON 2010 - 34
  • Groovy and Testing Tool Spectrum* Utilities Runners AllPairs, Combinations Native Groovy, JUnit, TestNG, Spock, EasyB, Polyglot languages JBehave, Cucumber, Robot Framework, SLIM Logic programming Threads, Parallel / Web Database SOAP / Other Concurrency libraries Drivers Drivers REST Drivers Data-driven libraries Drivers Networking libraries WebTest DbUnit FEST © ASERT 2006-2010 XML Processing GroovyWS WebDriver DataSets Email Read/write files / JWebUnit SqlUnit XML-RPC FTP Excel / Word / CSV Reporting, Logging Tellurium groovy.sql CXF AntUnit Selenium JPA Axis2 Telnet HtmlUnit JDO JAX-WS SSH Tools Watij BigTable JAX-RS Exec iTest2, SoapUI, Twist, HttpBuilder JDBC WindowLicker IDEs, JMeter, Text Cyberneko editors, Recorders, Sahi, Build Tools, CI * Tools/libraries/frameworks don't always neatly fall into one category – still useful conceptually QCON 2010 - 35
  • Concept Manual HTTP Request / Response Web Server © ASERT 2006-2010 Automated Driver Runner <webtest name="myTest"> <steps> <invoke description="get Login Page" url="login" /> <verifyTitle description="we should see the login title" text="Login Page" /> </steps> </webtest> Read Script HTTP Request / Response QCON 2010 - 37
  • Driver Category • Real browser invoker • Browser Emulators – Runs on platform – Can simulate multiple supported by real browsers browser – Less platform – May need multiple restrictions platforms, e.g. IE6/IE7 – Good for CI © ASERT 2006-2010 – Uses actual JavaScript – Easier to not download engine images, resources – Can be easier to use – Ability to optimise with test recorders JavaScript interactions – Automation – More extensible capabilities differ – Ability to disable across browsers JavaScript – Can typically get to all – Scope for parallelism aspects of browser QCON 2010 - 38
  • Application under Test © ASERT 2006-2010 QCON 2010 - 39
  • Native Groovy... • Access URLs • Built-in XML parsing • Built-in friendly regular expression syntax • Even for advanced cases, there is friendly access to low-level things: © ASERT 2006-2010 – Sockets, Processes – Databases and other things – Files • Huge range of Java libraries – PDF – Reading, writing Excel QCON 2010 - 40
  • ...Native Groovy... • Useful URL methods def html = new URL('http://localhost:8080').text assert html.contains('<title>Welcome to SimpBlog</title>') © ASERT 2006-2010 html.find(~'<title>(.*)</title>') { all, title -> assert title == 'Welcome to SimpBlog' } • Simple enough for GAE – For public sites – Can share test scripts easily – No setup required QCON 2010 - 41
  • ...Native Groovy... • Built-in XML Parsing def page = new XmlSlurper().parse('http://localhost:8080/viewPost?id=1') assert page.body.h1.text().contains('Tis the season') © ASERT 2006-2010 assert page.body.h3[1].text() == 'Category: Home' assert page.body.h3[2].text() == 'Author: Bart' assert page.body.table.tr.td.p.text() == "Aren't we forgeting the true meaning of Christmas? You know, the birth of Santa." QCON 2010 - 42
  • ...Native Groovy • Easy access to Java libraries @Grab('nekohtml:nekohtml:1.9.6.2') import org.cyberneko.html.parsers.SAXParser def parser = new XmlSlurper(new SAXParser()) def page = parser.parse('http://localhost:8080/viewPost?id=1') © ASERT 2006-2010 assert page.BODY.H1.text().contains('Tis the season') assert page.BODY.H3[1].text() == 'Category: Home' assert page.BODY.H3[2].text() == 'Author: Bart' assert page.BODY.TABLE.TR.TD.P.text() == "Aren't we forgeting the true meaning of Christmas? You know, the birth of Santa." QCON 2010 - 43
  • HttpBuilder • Builder for Http interactions – Flexible: bogus posts, response codes, JSON, non- HTML @Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.5.0-RC1') import groovyx.net.http.* import static groovyx.net.http.ContentType.URLENC © ASERT 2006-2010 def http = new HTTPBuilder('http://localhost:8080') def postBody = [title:'Bart was here (and so was HttpBuilder)', content:'Cowabunga Dude!', author:'1', category:'3'] http.post(path:'/addPost', body: postBody, requestContentType: URLENC) { resp, html -> assert resp.contentType == 'text/html' assert resp.status == 200 assert html.BODY.H1.text().matches('Post.*: Bart was here.*') assert html.BODY.H3[1].text() == 'Category: Home' assert html.BODY.H3[2].text() == 'Author: Bart' assert html.BODY.TABLE.TR.TD.P.text() == 'Cowabunga Dude!' } QCON 2010 - 44
  • HtmlUnit • 100% Java-based headless browser emulator – Can test any Web site: Java, .Net, PHP, Rails, ... • Open Source – Apache 2 license – Hosted at SourceForge – 7 committers (3 very active) – © ASERT 2006-2010 Very mature • Useful for: – Integration and acceptance testing – Screen scraping, deployment automation, ... • Used by other drivers: – Canoo WebTest , JWebUnit , WebDriver , JSFUnit , Celerity • Special features: – Easy ajax mode, emulation of multiple browsers QCON 2010 - 45
  • HtmlUnit Features... • Support for the HTTP and HTTPS protocols • Support for cookies • Ability to specify whether failing responses from the server should throw exceptions or should be returned as "error" pages • Support for submit methods POST and GET © ASERT 2006-2010 – As well as HEAD, DELETE, ... • Ability to customize the request headers being sent to the server • Support for HTML responses – Wrapper for HTML pages that provides easy access to all information contained inside them – Support for submitting forms and clicking links – Support for walking the DOM model of HTML documents QCON 2010 - 46
  • ...HtmlUnit Features • Proxy server support • Support for basic and NTLM authentication • Excellent JavaScript support – jQuery 1.2.6: Full support – MochiKit 1.4.1: Full support – GWT 1.6.4: Full support © ASERT 2006-2010 – Sarissa 0.9.9.3: Full support – MooTools 1.2.1: Full support – Prototype 1.6.0: Very good support – Ext JS 2.2: Very good support – Dojo 1.0.2: Good support – YUI 2.3.0: Good support QCON 2010 - 47
  • HtmlUnit: Testing New Blog Post... @Grab('net.sourceforge.htmlunit:htmlunit:2.6') import com.gargoylesoftware.htmlunit.WebClient def client = new WebClient() def page = client.getPage('http://localhost:8080/postForm') // check page title assert 'Welcome to SimpBlog' == page.titleText © ASERT 2006-2010 // fill in blog entry and post it def form = page.getFormByName('post') form.getInputByName('title'). setValueAttribute('Bart was here (and so was HtmlUnit)') form.getSelectByName('category').getOptions().find{ it.text == 'Home' }.setSelected(true) form.getTextAreaByName('content').setText('Cowabunga Dude!') def result = form.getInputByName('btnPost').click() ... QCON 2010 - 48
  • ...HtmlUnit: Testing New Blog Post ... // check blog post details assert result.getElementsByTagName('h1').item(0). textContent.matches('Post.*: Bart was here.*') def h3headings = result.getElementsByTagName('h3') © ASERT 2006-2010 assert h3headings.item(1).textContent == 'Category: Home' assert h3headings.item(2).textContent == 'Author: Bart' // expecting: // <table><tr><td><p>Cowabunga Dude!</p></td></tr></table> def cell = result.getByXPath('//TABLE//TR/TD')[0] def para = cell.getFirstChild() assert para.textContent == 'Cowabunga Dude!' QCON 2010 - 49
  • Canoo WebTest • Description – Open source tool for automated testing of web applications – Declarative approach in XML or testing DSL in Groovy – Has Test Recorder – Excellent reporting options – Ant-based under the covers <target name="login" > <testSpec name="normal" > © ASERT 2006-2010 &config; <steps> <invoke stepid="get Login Page" url="login.jsp" /> <verifytitle stepid="we should see the login title" text="Login Page" /> <setinputfield stepid="set user name" name="username" value="scott" /> <setinputfield stepid="set password" name="password" value="tiger" /> <clickbutton stepid="Click the submit button" label="let me in" /> <verifytitle stepid="Home Page follows if login ok" text="Home Page" /> </steps> </testSpec> </target> QCON 2010 - 50
  • Canoo WebTest Features • Strongly encourages declarative testing – Supports testing DSLs, test structuring and reuse through macrodefs and imports for XML flavor & methods and closures for Groovy flavor • Extensive support for HTML pages – Including JavaScript • Also supports other MIME types © ASERT 2006-2010 – Generically as binary streams – Special support for PDF, Excel, Emails • Ant heritage provides easy IDE/CI hooks • Excellent Documentation • Excellent Community • Eats own dog food – high quality codebase QCON 2010 - 51
  • WebTest: Testing New Blog Post... <webtest name="Testing Posting a new Blog Entry"> <invoke url="http://localhost:8080/" description="Home Page"/> <verifyTitle text="Welcome to SimpBlog"/> <group description="Post New Blog Entry"> <clickLink label="New Blog Entry"/> © ASERT 2006-2010 <setInputField name="title" value="Bart was here (and so was WebTest)"/> <setSelectField name="category" text="School"/> <setInputField name="content" value="Cowabunga Dude!"/> <clickButton name="btnPost"/> </group> ... QCON 2010 - 56
  • ...WebTest: Testing New Blog Post... ant.webtest(name: 'Test SimpBlog') { invoke url: "http://localhost:8080/", description: "Home Page" verifyTitle text: "Welcome to SimpBlog" group description: "Post New Blog Entry", { clickLink label: "New Blog Entry" setInputField name: "title", value: "Bart was here (and so was WebTest with Groovy)" setSelectField name: "category", text: "School" © ASERT 2006-2010 setInputField name: "content", value: "Cowabunga Dude!" clickButton name: "btnPost" } group description: "Check Blog Post", { verifyElementText type: "h1", regex: "true", text: "Post.*: Bart was here.*" verifyXPath xpath: "//h3[2]/text()", text: "Category: School" verifyXPath xpath: "//h3[3]/text()", text: "Author: Bart" verifyElementText type: "p", text: "Cowabunga Dude!" } } QCON 2010 - 58
  • ...WebTest: Testing New Blog Post © ASERT 2006-2010 QCON 2010 - 60
  • Firefox Recorder © ASERT 2006-2010 QCON 2010 - 61
  • Watij • Description – Java API that provides control and automation of Internet Explorer – Supports actions like navigating, clicking links, filling out forms, etc. – Also supports more complex actions like file © ASERT 2006-2010 downloading and uploading, popup windows and dialogs, and screen captures • Special Features – Ability to work with IE interactively – Can attach to an existing browser session – Special browser commands, e.g. ie.fullScreen(true) – Handles child browsers and popup dialogs QCON 2010 - 62
  • ...Watij: Testing New Blog Post... ... // check entered post is being displayed assert ie.htmlElement(tag, 'H1').text(). matches('Post.*: Bart was here.*') def h3headers = ie.htmlElements(tag, 'H3') © ASERT 2006-2010 assert h3headers.get(1).text() == 'Category: Home' assert h3headers.get(2).text() == 'Author: Bart' // try a more advanced finder // content is at: //TABLE/TBODY/TR/TD/P def row = ie.htmlElement(xpath('//TABLE/TBODY/TR')) assert row.cell(0).htmlElement(tag, 'P').text() == 'Cowabunga dude!' ie.close() QCON 2010 - 65
  • Selenium... • Description – Tools to help automate testing for web-based applications – Support for © ASERT 2006-2010 running tests on multiple browser platforms • Components – Selenium Core – Selenium IDE  Selenium RC Our focus – Selenium Grid Source: http://seleniumhq.org/projects/remote-control/ QCON 2010 - 67
  • ...Selenium © ASERT 2006-2010 Source: http://seleniumhq.org/docs/01_introducing_selenium.html QCON 2010 - 68
  • ...Selenium: Testing New Blog Post ... // post blog selenium.open "/postForm" selenium.type "title", "Bart was here (and so was Selenium)" selenium.select "category", "Home" selenium.type "content", "Cowabunga Dude!" selenium.click "btnPost" © ASERT 2006-2010 selenium.waitForPageToLoad "5000" // checks assert selenium.isTextPresent('regex:Post.*: Bart was here') assert selenium.isElementPresent('//h3[text()="Author: Bart"]') assert selenium.isElementPresent('//h3[text()="Category: Home"]') assert selenium.isElementPresent( '//table//tr/td/p[text()="Cowabunga Dude!"]') selenium.stop() server.stop() QCON 2010 - 70
  • Selenium IDE... Features: • Easy record and playback © ASERT 2006-2010 • Intelligent field selection will use IDs, names, or XPath as needed • Autocomplete for all common Selenium commands • Walk through tests • Debug and set breakpoints • Save tests as HTML, Ruby scripts, or any other format • Support for Selenium user- extensions.js file • Option to automatically assert the title of every page QCON 2010 - 71
  • ...Selenium Other Tools © ASERT 2006-2010 Source: http://selenium-grid.seleniumhq.org/how_it_works.html QCON 2010 - 75
  • WebDriver • Description – Simple API to drive both real browsers • for testing javascript heavy apps – and a pure 'in memory' emulator solution • for faster testing of simpler apps • uses HtmlUnit under the covers in emulator mode © ASERT 2006-2010 – Represents next generation of Selenium RC • though merging into Selenium may happen under the covers if you are a Selenium user – Roadmap has plans to leverage some advanced Selenium like features • RemoteWebDriver • FarmedWebDriver (think Selenium Grid) QCON 2010 - 76
  • ...WebDriver: Testing New Blog Post ... assert driver.findElement(By.tagName("h1")).text. matches('Post.*: Bart was here.*') def h3headers = driver.findElements(By.tagName("h3")) assert h3headers[1].text == 'Category: Home' © ASERT 2006-2010 assert h3headers[2].text == 'Author: Bart' // try a more advanced finder // content is at: //TABLE/TBODY/TR/TD/P def row = driver.findElement(By.xpath("//table/tbody/tr")) def col = row.findElement(By.tagName("td")) def para = col.findElement(By.tagName("p")) assert para.text == 'Cowabunga dude!' QCON 2010 - 78
  • Tellurium • Description – built on top of Selenium but tries to solve several shortcomings – "record and reply" style, difficult to refactor and maintain, so instead define UI components declaratively then write tests in terms of UI © ASERT 2006-2010 – Provides many predefined UI objects for you to use directly, such as Button, CheckBox, InputBox, Selector, TextBox, and Table but also ability to write your own custom UI objects – Supports advanced locating mechanisms: composite locator, "group locating" – Supports testing DSL – Supports data-driven tests QCON 2010 - 79
  • Architecture © ASERT 2006-2010 Source: http://code.google.com/p/aost/wiki/Introduction QCON 2010 - 80
  • Tellurium Example • Selenium: selenium.type("//input[@title='Google Search']", input) selenium.click("//input[@name='btnG' and @type='submit']") • Tellurium UI: ui.Container(uid: "google_start_page", © ASERT 2006-2010 clocator: [tag: "td"], group: "true") { InputBox(uid: "searchbox", clocator: [title: "Google Search"]) SubmitButton(uid: "googlesearch", clocator: [name: "btnG", value: "Google Search"]) } • Tellurium DSL Test: type "google_start_page.searchbox", input click "google_start_page.googlesearch" QCON 2010 - 81
  • Tellurium: Testing New Blog Post... ui.UrlLink(uid: "create_new_post", clocator: [tag:'a', text: "New Blog Entry"]) ui.Form(uid: "blogform", clocator: [tag: 'form', name:'post'], group: "true") { InputBox(uid: "title", clocator: [name: "title"]) InputBox(uid: "content", clocator: [tag:'textarea', name: "content"]) Selector(uid: "category", clocator: [name: "category"]) Selector(uid: "author", clocator: [name: "author"]) © ASERT 2006-2010 SubmitButton(uid: "post_button", clocator: [name: 'btnPost', value: "Create Post"]) } ui.TextBox(uid: 'main_header', clocator: [tag: 'h1']) ui.TextBox(uid: 'category_header', clocator: [tag: 'h3', position: '2']) ui.TextBox(uid: 'author_header', clocator: [tag: 'h3', position: '3']) ui.Container(uid: 'table', clocator: [tag: 'table']) { ui.TextBox(uid: 'content_para', locator: '//tr/td/p') } QCON 2010 - 82
  • ...Tellurium: Testing New Blog Post openUrl "http://localhost:8080/" click "create_new_post" waitForPageToLoad 5000 assert title == 'Welcome to SimpBlog' // post blog type "blogform.title", "Bart was here (and so was Tellurium)" selectByLabel "blogform.category", "Home" © ASERT 2006-2010 selectByLabel "blogform.author", "Bart" type "blogform.content", "Cowabunga Dude!" click "blogform.post_button" waitForPageToLoad 5000 // check contents assert getText('main_header').matches('Post.*: Bart was here.*') assert getText('category_header') == 'Category: Home' assert getText('author_header') == 'Author: Bart' assert getText('table.content_para') == 'Cowabunga Dude!' shutDown QCON 2010 - 83
  • TrUMP IDE © ASERT 2006-2010 QCON 2010 - 84
  • ...Built-in JUnit 3... ... def form = page.getFormByName('post') form.getInputByName('title').setValueAttribute( 'Bart was here (and so was HtmlUnit)') form.getSelectByName('category').getOptions().find { it.text == 'Home' }.setSelected(true) form.getTextAreaByName('content').setText('Cowabunga Dude!') def result = form.getInputByName('btnPost').click() // check blog post details © ASERT 2006-2010 assert result.getElementsByTagName('h1').item(0). textContent.matches('Post.*: Bart was here.*') def h3headings = result.getElementsByTagName('h3') assert h3headings.item(1).textContent == 'Category: Home' assert h3headings.item(2).textContent == 'Author: Bart' // expecting: <table><tr><td><p>Cowabunga Dude!</p></td></tr></tab def cell = result.getByXPath('//TABLE//TR/TD')[0] def para = cell.getFirstChild() assert para.textContent == 'Cowabunga Dude!' } } QCON 2010 - 93
  • JUnit 4.X • Groovy distributions from 1.7 include JUnit 4 • Automatically invokes text runner if needed • Example uses HtmlUnit driver (not shown) import org.junit.* class TestSimpBlogJUnit4 { © ASERT 2006-2010 def page @Before void setUp() { // ... } @Test void bartWasHere() { // ... QCON 2010 - 96
  • JUnit 4.X Parameterized Tests @RunWith(Parameterized) class TestSimpBlogJUnit4DD { def page, author, title, category, content TestSimpBlogJUnit4DD(author, title, category, content) { this.author = author this.title = title © ASERT 2006-2010 this.category = category this.content = content } @Parameters static data() { return [ ['Bart', 'Title 1', 'Home', 'Content 1'], ['Homer', 'Title 2', 'Work', 'Content 2'], ['Marge', 'Title 3', 'Food', 'Content 3'] ].collect{ it as String[] } } ... QCON 2010 - 97
  • Spock Testing Framework... © ASERT 2006-2010 QCON 2010 - 100
  • ...Spock Testing Framework • Testing framework for Java and Groovy • Highly expressive specification language – No assertion API – No record & replay @Speck mocking API @RunWith(Sputnik) – No class PublisherSubscriberSpeck { superfluous def "events are received by all subscribers"() { © ASERT 2006-2010 annotations def pub = new Publisher() – Meaningful def sub1 = Mock(Subscriber) assert error def sub2 = Mock(Subscriber) messages pub.subscribers << sub1 << sub2 when: pub.send("event") then: – Extensible 1 * sub1.receive("event") – Compatible 1 * sub2.receive("event") with JUnit } reportingwise } QCON 2010 - 101
  • Spock Example... import com.gargoylesoftware.htmlunit.WebClient import spock.lang.* import org.junit.runner.RunWith @Speck () @RunWith (Sputnik) class TestSimpBlogSpock { def page, subheadings, para, form, result @Unroll("When #author posts a #category blog with content '#content' it shoul © ASERT 2006-2010 def "when creating a new blog entry"() { given: page = new WebClient().getPage('http://localhost:8080/postForm') form = page.getFormByName('post') when: form.getInputByName('title').setValueAttribute("$author was here (and so form.getSelectByName('category').getOptions().find { it.text == category form.getSelectByName('author').getOptions().find { it.text == author }.se form.getTextAreaByName('content').setText(content) result = form.getInputByName('btnPost').click() subheadings = result.getElementsByTagName('h3') para = result.getByXPath('//TABLE//TR/TD/P')[0] ... QCON 2010 - 102
  • ...Spock Example ... then: page.titleText == 'Welcome to SimpBlog' result.getElementsByTagName('h1').item(0).textContent.matches("Post.*: $auth subheadings.item(1).textContent == "Category: $category" subheadings.item(2).textContent == "Author: $author" and: // Optional use of 'and:' para.textContent == content © ASERT 2006-2010 where: author << ['Bart', 'Homer', 'Lisa'] category << ['Home', 'Work', 'Food'] content << ['foo', 'bar', 'baz'] } } QCON 2010 - 103
  • EasyB • Description: BDD, Rspec-like testing library narrative 'segment flown', { as_a 'frequent flyer' i_want 'to accrue rewards points for every segment I fly' so_that 'I can receive free flights for my dedication to the airline' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points' © ASERT 2006-2010 when 'that flyer completes a segment worth 500 points' then 'that flyer has a new rewards balance of 2000 points' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points', { flyer = new FrequentFlyer(1500) } when 'that flyer completes a segment worth 500 points', { flyer.fly(new Segment(500)) } then 'that flyer has a new rewards balance of 2000 points', { flyer.pointsBalance.shouldBe 2000 } } QCON 2010 - 104
  • EasyB Example ... • When run will be marked as pending – perfect for ATDD scenario "Bart posts a new blog entry", { given "we are on the create blog entry page" when "I have entered 'Bart was here' as the title" and "I have entered 'Cowabunga Dude!' into the content" and "I have selected 'Home' as the category" © ASERT 2006-2010 and "I have selected 'Bart' as the author" and "I click the 'Create Post' button" then "I expect the entry to be posted" } QCON 2010 - 105
  • ...EasyB Example... description "Post Blog Entry Feature" narrative "for feature", { as_a "Blogger" i_want "to be able to post a blog" so_that "I can keep others informed" } before "posting blog", { given "we are on the create blog entry page", { © ASERT 2006-2010 webClient = new com.gargoylesoftware.htmlunit.WebClient() page = webClient.getPage('http://localhost:8080/postForm') } } scenario "Bart was here blog", { when "I have entered 'Bart was here' as the title", { form = page.getFormByName('post') form.getInputByName('title').setValueAttribute( 'Bart was here (and so was EasyB)') } ... QCON 2010 - 106
  • ...EasyB Example... ... and "I have entered 'Cowabunga Dude!' into the content", { form.getTextAreaByName('content').setText('Cowabunga Dude!') } and "I have selected 'Home' as the category", { form.getSelectByName('category').getOptions().find { it.text == 'Home' }.setSelected( } and "I click the 'Create Post' button", { result = form.getInputByName('btnPost').click() } © ASERT 2006-2010 then "I expect the entry to be posted", { // check blog post details assert result.getElementsByTagName('h1').item(0).textContent.matches('Post.*: Bart wa def h3headings = result.getElementsByTagName('h3') assert h3headings.item(1).textContent == 'Category: Home' // traditional style h3headings.item(2).textContent.shouldBe 'Author: Bart' // BDD style // expecting: <table><tr><td><p>Cowabunga Dude!</p></td></tr></table> def cell = result.getByXPath('//TABLE//TR/TD')[0] def para = cell.firstChild assert para.textContent == 'Cowabunga Dude!' // para.shouldHave textContent: 'Cowabunga Dude!' } } QCON 2010 - 107
  • ...EasyB Example... © ASERT 2006-2010 QCON 2010 - 108
  • ...EasyB Example 2 scenarios (including 1 pending) executed successfully. Story: simp blog initial scenario Bart posts a new blog entry [PENDING] given we are on the create blog entry page when I have entered 'Bart was here' as the title when I have entered 'Cowabunga Dude!' into the content [PENDING] when I have selected 'Home' as the category [PENDING] when I have selected 'Bart' as the author [PENDING] when I click the 'Create Post' button [PENDING] then I expect the entry to be posted [PENDING] Story: simp blog easyb is preparing to process 2 file(s) © ASERT 2006-2010 Post Blog Entry Feature Running simp blog initial story (SimpBlogInitialStory.groovy) for feature Scenarios run: 1, Failures: 0, Pending: 1, Time elapsed: 1.049 sec As a Blogger Running simp blog story (SimpBlogStory.groovy) I want to be able to post a blog Scenarios run: 1, Failures: 0, Pending: 0, Time elapsed: 1.356 sec So that I can keep others informed given we are on the create blog entry page 2 total behaviors ran (including 1 pending behavior) with no failures easyb execution passed scenario Bart was here blog when I have entered 'Bart was here' as the title when I have entered 'Cowabunga Dude!' into the content when I have selected 'Home' as the category when I click the 'Create Post' button then I expect the entry to be posted QCON 2010 - 109
  • Cucumber # language: en • Description Feature: Addition In order to avoid silly mistakes – Loose coupling As a math idiot between text spec I want to be told the sum of two numbers and step defns Scenario Outline: Add two numbers Given I have entered <input_1> into the calculator And I have entered <input_2> into the calculator When I press <button> Then the stored result should be <output> Examples: © ASERT 2006-2010 | input_1 | input_2 | button | output | | 20 | 30 | add | 50 | | 2 | 5 | add | 7 | | 0 | 40 | add | 40 | # language: en Feature: Division In order to avoid silly mistakes Cashiers must be able to calculate a fraction Scenario: Regular numbers Given I have entered 3 into the calculator And I have entered 2 into the calculator When I press divide Then the stored result should be 1.5 QCON 2010 - 110
  • Cucumber Example... # language: en @newpost Feature: New Blog Post In order to create a new blog entry Bloggers should be able to select their name and category and enter text © ASERT 2006-2010 Scenario: New Posting Given we are on the create blog entry page When I have entered "Bart was here" as the title And I have entered "Cowabunga Dude!" as the content And I have selected "Home" from the "category" dropdown And I have selected "Bart" from the "author" dropdown And I click the 'Create Post' button Then I should see a heading message matching "Post.*: Bart was here.*" QCON 2010 - 111
  • ...Cucumber Example... © ASERT 2006-2010 QCON 2010 - 112
  • ...Cucumber Example import com.gargoylesoftware.htmlunit.WebClient this.metaClass.mixin(cuke4duke.GroovyDsl) Given ~/we are on the create blog entry page/, { -> page = new WebClient().getPage('http://localhost:8080/postForm') } When(~/I have entered "(.*)" as the title/) {String title -> form = page.getFormByName('post') form.getInputByName('title').setValueAttribute(title + ' (and so was Cucumber)') } When(~'I have entered "(.*)" as the content') {String content -> © ASERT 2006-2010 form.getTextAreaByName('content').setText(content) } When(~'I have selected "(.*)" from the "(.*)" dropdown') {String option, String name -> form.getSelectByName(name).getOptions().find { it.text == option }.setSelected(true) } When(~"I click the 'Create Post' button") { -> result = form.getInputByName('btnPost').click() } Then(~'I should see a heading message matching "(.*)"') {String pattern -> // ensureThat result.getElementsByTagName('h1').item(0).textContent.matches(pattern) assert result.getElementsByTagName('h1').item(0).textContent.matches(pattern) } QCON 2010 - 113
  • Cucumber Data Driven Example... # language: en @newpost Feature: New Blog Post In order to create a new blog entry Bloggers should be able to select their name and category and enter text Scenario Outline: New Posting Given we are on the create blog entry page © ASERT 2006-2010 When I have entered "<title>" as the title And I have entered "<content>" as the content And I have selected "<category>" from the "category" dropdown And I have selected "<author>" from the "author" dropdown And I click the 'Create Post' button Then I should see a heading message matching "Post.*: <title>.*" Examples: | title | content | category | author | | Title 1 | Content 1 | Home | Bart | | Title 2 | Content 2 | Work | Homer | | Title 3 | Content 3 | Food | Marge | QCON 2010 - 114
  • ...Cucumber Data Driven Example © ASERT 2006-2010 QCON 2010 - 115
  • JBehave • Description – Behaviour-driven development in Java • Also works out of the box for Groovy – Behavior scenarios written in text • Use the words Given, When, Then and And. – Mapped using regular expressions and annotations © ASERT 2006-2010 to step methods – Web Runner available for non-technical users to easily run tests – Hooks to Selenium available in JBehave Web • Other Java libraries (e.g. HtmlUnit) easy to use too – Supports parameter converters • Getting 'String' parameters into appropriate Object values – Supports a 'StepDoc' function • For listing available scenario clauses QCON 2010 - 116
  • JBehave Example... Given we are on the create blog entry page When I have entered "Bart was here" as the title And I have entered "Cowabunga Dude!" as the content And I have selected "Home" from the "category" dropdown And I have selected "Bart" from the "author" dropdown And I click the 'Create Post' button Then I should see a heading message matching "Post.*: Bart was here.*" import org.jbehave.scenario.Scenario © ASERT 2006-2010 import org.jbehave.scenario.steps.Steps class NewPostScenario extends Scenario { NewPostScenario() { super([new CreateBlogSteps()] as Steps[]) } } Scenario: Given we are on the create blog entry page When I have entered "Bart was here" as the title And I have entered "Cowabunga Dude!" as the content And I have selected "Home" from the "category" dropdown And I have selected "Bart" from the "author" dropdown And I click the 'Create Post' button Then I should see a heading message matching "Post.*: Bart was here.*" QCON 2010 - 117
  • ...JBehave Example... import org.jbehave.scenario.steps.Steps import org.jbehave.scenario.annotations.* import com.gargoylesoftware.htmlunit.WebClient class CreateBlogSteps extends Steps { def page, form, result @Given("we are on the create blog entry page") void gotoEntryPage() { page = new WebClient().getPage('http://localhost:8080/postForm') } © ASERT 2006-2010 @When('I have entered "$title" as the title') void enterTitle(String title) { form = page.getFormByName('post') form.getInputByName('title'). setValueAttribute(title + ' (and so was JBehave)') } @When('I have entered "$content" as the content') void enterContent(String content) { form.getTextAreaByName('content').setText(content) } ... Example uses HtmlUnit which must be added to CLASSPATH QCON 2010 - 118
  • ...JBehave Example ... @When('I have selected "$option" from the "$name" dropdown') void selectOption(String option, String name) { form.getSelectByName(name).getOptions().find { it.text == option }.setSelected(true) } @When("I click the 'Create Post' button") void clickPostButton() { result = form.getInputByName('btnPost').click() © ASERT 2006-2010 } @Then('I should see a heading message matching "$message"') void checkPost(String pattern) { assert result.getElementsByTagName('h1').item(0). textContent.matches(pattern) } } Choose either traditional style or BDD style ... void checkPost(String pattern) { ensureThat result.getElementsByTagName('h1').item(0). textContent.matches(pattern) } ... QCON 2010 - 119
  • JBehave Web Runner... © ASERT 2006-2010 QCON 2010 - 120
  • ...JBehave Web Runner © ASERT 2006-2010 QCON 2010 - 121
  • Hacked* "JBehave Aware" GroovyConsole © ASERT 2006-2010 * Not currently publically available QCON 2010 - 122
  • Robot Framework... • Description – Keyword-driven test automation framework for acceptance level testing and acceptance test-driven development (ATDD) – Easy to use tabular syntax for creating test – Easy to use test libraries implemented either with © ASERT 2006-2010 Python or Java – Open source, Apache License 2.0 – Supports creating data-driven test cases. – Provides tagging to categorize and select test cases to be executed – Provides easy-to-read reports and logs in HTML format – XML-RPC interface for remote testing QCON 2010 - 123
  • FitNesse/SLIM • Description – Tool for enhancing collaboration in software development – Allows customers, developers and testers to easily © ASERT 2006-2010 create and run tests to compare expected with actual results – Tests are captured in wiki format and mapped into code using fixtures Source: http://fitnesse.org/ QCON 2010 - 134
  • SLIM with Groovy... © ASERT 2006-2010 QCON 2010 - 135
  • ...SLIM with Groovy package simpblog import com.gargoylesoftware.htmlunit.WebClient class NewBlogPost { String author, title, content, category private result def execute() { def client = new WebClient() def page = client.getPage('http://localhost:8080/postForm') assert 'Welcome to SimpBlog' == page.titleText def form = page.getFormByName('post') © ASERT 2006-2010 form.getInputByName('title'). setValueAttribute("$title (entered with Robot Framework)") form.getSelectByName('author').getOptions().find{ it.text == author }.setSelected(true) form.getSelectByName('category').getOptions().find{ it.text == category }.setSelected(true) form.getTextAreaByName('content').setText(content) result = form.getInputByName('btnPost').click() } def mainHeading() { def m = result.getElementsByTagName('h1').item(0).textContent =~ /Post .*: (.*) ([^)]*)/ m[0][1] } } Example uses HtmlUnit to call SimpBlog web site but those details aren't important here QCON 2010 - 136
  • All Combinations • Description test('MacOS', '4G', '250G') – Don't have a bunch test('Linux', '4G', '250G') of hard-coded, hard test('Vista', '4G', '250G') to maintain manual test('MacOS', '8G', '500G') test data or even test('Linux', '8G', '500G') manually generated test('Vista', '8G', '500G') // 30 more rows CSV file © ASERT 2006-2010 – Much better to generate test cases [ from succinct ['MacOS', 'Linux', 'Vista'], ['2G', '4G', '6G', '8G'], expressions of ['250G', '350G', '500G'] what you are trying ].combinations().each{ to achieve os, mem, disk -> test(os, mem, disk) } QCON 2010 - 158
  • All Combinations Case Study import com.gargoylesoftware.htmlunit.WebClient def combos = [["Bart", "Homer", "Marge", "Lisa", "Maggie"], ["Work", "School", "Home", "Travel", "Food"], ["foo", "bar", "baz"]].combinations() println "Found ${combos.size()} combos" combos.each { author, category, content -> postAndCheck category, author, content } © ASERT 2006-2010 def postAndCheck(String category, String author, String content) { // ... // details not shown (ran with HtmlUnit) // ... } Found 75 combos QCON 2010 - 159
  • All Pairs • Description – Sometimes called pairwise testing or orthogonal array testing © ASERT 2006-2010 – Technique to limit the explosion of test cases by identifying samples of important classes of test cases (equivalence classes) • providing maximum coverage with minimum testing – Instead of all combinations, systematically use pair- wise combinations of interactions between objects • as most faults result from adverse two-way interactions QCON 2010 - 160
  • All Pairs Case Study... class AllPairs { private initialResults, results, rest Details here not important: Given here for completeness as this private buildPairs(Map partialCombinations, inputsLeft) { library is not publically available yet def first = getFirstEntry(inputsLeft) def partialResults = [] first.value.each { def next = [(first.key): it] ... getFirstEntry(next) private getFirstEntry(Map map) { next.putAll(partialCombinations) return map.entrySet().toList().get(0) partialResults << next } } if (inputsLeft.size() == 1) { private getAllPairsFromMap(map) { initialResults.addAll(partialResults) if (!map || map.size() <= 1) return null © ASERT 2006-2010 } else { def allPairs = new HashSet() partialResults.each { def first = getFirstEntry(map) def rest = inputsLeft.clone() def restMap = map.clone() rest.remove(first.key) restMap.remove(first.key) buildPairs(it, rest) restMap.each { } def nextPair = new HashSet() } nextPair << first } nextPair << it allPairs << nextPair private adjustPairs() { } results = initialResults.clone() def restPairs = getAllPairsFromMap(rest) initialResults.each { if (restPairs != null) { def restResults = results.clone() allPairs.addAll(restPairs) restResults.remove(it) } if (allPairsCovered(it, restResults)) { return allPairs results.remove(it) } } ... } } ... QCON 2010 - 161
  • ...All Pairs Case Study... ... private boolean allPairsCovered(candidate, remaining) { def totalCount = 0 def pairCombos = getAllPairsFromMap(candidate) pairCombos.each {candidatePair -> def pairFound = false def pairs = candidatePair.toList() for (it in remaining) { def entries = it.entrySet() if (!pairFound && entries.contains(pairs[0]) && entries.contains(pairs[1])) { pairFound = true totalCount++ } © ASERT 2006-2010 } } return (totalCount == pairCombos.size()) } private updateUsedPairs(map) { getAllPairsFromMap(map).each { usedPairs << it } } def generate(configurations) { initialResults = new HashSet() results = new HashSet() buildPairs([:], configurations) adjustPairs() results Details here not important: } Given here for completeness as this } library is not publically available yet QCON 2010 - 162
  • ...All Pairs Case Study... import com.gargoylesoftware.htmlunit.WebClient def pairs = new AllPairs().generate( author: ["Bart", "Homer", "Marge", "Lisa", "Maggie"], category: ["Work", "School", "Home", "Travel", "Food"], content: ["foo", "bar", "baz"]) println "Found ${pairs.size()} pairs" pairs.each { println it // just for debugging purposes © ASERT 2006-2010 postAndCheck it.category, it.author, it.content } def postAndCheck(String category, String author, String content) { // ... // details not shown (ran with HtmlUnit) // ... } QCON 2010 - 163
  • ...All Pairs Case Study Found 18 pairs [content:bar, category:Food, author:Bart] [content:bar, category:School, author:Homer] [content:foo, category:Work, author:Bart] [content:baz, category:School, author:Homer] [content:bar, category:Home, author:Maggie] [content:foo, category:School, author:Marge] [content:bar, category:Work, author:Bart] © ASERT 2006-2010 [content:baz, category:Travel, author:Bart] [content:foo, category:Home, author:Homer] [content:bar, category:Travel, author:Marge] [content:baz, category:Work, author:Homer] [content:bar, category:Travel, author:Lisa] [content:baz, category:Travel, author:Maggie] [content:baz, category:Home, author:Marge] [content:baz, category:Food, author:Homer] [content:baz, category:Travel, author:Lisa] [content:foo, category:Food, author:Maggie] [content:foo, category:Travel, author:Lisa] QCON 2010 - 164
  • gpars... • Description – Library classes and DSL sugar providing intuitive ways for Groovy developers to handle tasks concurrently. Four logical parts: • Actors provide a Groovy implementation of Scala-like actors, both thread-bound actors and thread pool- © ASERT 2006-2010 bound (event-driven) ones • Dataflow Concurrency allows for very natural shared- memory concurrency model, based on single- assignment variables • Asynchronizer extends the Java 1.5 built-in support for executor services to enable multi-threaded collection and closure processing • Parallelizer uses JSR-166y Parallel Arrays to enable multi-threaded collection processing QCON 2010 - 165
  • ...gpars... // run multiple closures in parallel Asynchronizer.withAsynchronizer { assert [10, 20] == AsyncInvokerUtil.doInParallel( {calculateA()}, {calculateB()} ) © ASERT 2006-2010 } // multiply numbers asynchronously Parallelizer.withParallelizer(5) { final List result = [1, 2, 3, 4, 5].collectAsync {it * 2} assert ([2, 4, 6, 8, 10].equals(result)) } QCON 2010 - 166
  • ...gpars... // support for dataflow to avoid doing synchronisation import static org.gparallelizer.dataflow.DataFlow.thread final def x = new DataFlowVariable() final def y = new DataFlowVariable() final def z = new DataFlowVariable() © ASERT 2006-2010 thread { z << x.val + y.val println "Result: ${z.val}" } thread { x << 10 } thread { y << 5 } QCON 2010 - 167
  • ...gpars // actor support import static org.gparallelizer.actors.pooledActors.PooledActors.* def me = actor { friend.send('Hi') react(10.seconds) { © ASERT 2006-2010 // continue conversation } } me.metaClass.onTimeout = {-> friend.send('I see, busy as usual. Never mind.')} me.start() QCON 2010 - 168
  • gpars and SimpBlog @Grab('net.sourceforge.htmlunit:htmlunit:2.6') import com.gargoylesoftware.htmlunit.WebClient @Grab('org.gparallelizer:GParallelizer:0.8.3') import static org.gparallelizer.Parallelizer.* def testCases = [ ['Home', 'Bart', 'Content 1'], ['Work', 'Homer', 'Content 2'], ['Travel', 'Marge', 'Content 3'], © ASERT 2006-2010 ['Food', 'Lisa', 'Content 4'] ] withParallelizer(3) { Note: Testing testCases.eachAsync{ category, author, content -> DSL makes postAndCheck category, author, content tests more readable } } private postAndCheck(category, author, content) { ... QCON 2010 - 169
  • Native Groovy Versions also possible @Grab('net.sourceforge.htmlunit:htmlunit:2.6') import com.gargoylesoftware.htmlunit.WebClient Thread.start { postAndCheck 'Home', 'Bart', 'Content 1' Or use: } ant.parallel { Thread.start { //... postAndCheck 'Work', 'Homer', 'Content 2' } © ASERT 2006-2010 } Thread.start { postAndCheck 'Travel', 'Marge', 'Content 3' } Thread.start { postAndCheck 'Food', 'Lisa', 'Content 4' } Or use: "command".execute() private postAndCheck(category, author, content) { ... QCON 2010 - 170
  • Constraint/Logic Programming... • Description – Style of programming where relations between variables are stated in the form of constraints – First made popular by logic programming languages such as Prolog but the style is now also used outside logic programming specific languages © ASERT 2006-2010 – Constraints differ from the common primitives of other programming languages in that they do not specify one or more steps to execute but rather the properties of a solution to be found – Popular libraries used with Groovy supporting constraint programming include Gecode/J, Choco and tuProlog – We'll look at Choco as an example QCON 2010 - 171
  • ...Constraint/Logic Programming... © ASERT 2006-2010 Source: http://xkcd.com/287/ QCON 2010 - 172
  • ...Constraint/Logic Programming... // requires choco 2.1.0-basic.jar from http://choco.emn.fr/ import static choco.Choco.* import choco.kernel.model.variables.integer.IntegerVariable def m = new choco.cp.model.CPModel() def s = new choco.cp.solver.CPSolver() def menu = [ Found a solution: © ASERT 2006-2010 'Mixed fruit' : 215, 7 * Mixed fruit 'French fries' : 275, Found a solution: 'Side salad' : 335, 1 * Mixed fruit 'Hot wings' : 355, 2 * Hot wings 'Mozzarella sticks' : 420, 1 * Sampler plate 'Sampler plate' : 580 ] def numOrdered = new IntegerVariable[menu.size()] def priceEach = new int[menu.size()] def sum = 1505 ... QCON 2010 - 173
  • ...Constraint/Logic Programming ... menu.eachWithIndex { name, price, i -> // number ordered >= 0 // number ordered * price <= sum numOrdered[i] = makeIntVar(name, 0, sum.intdiv(price)) priceEach[i] = price } m.addConstraint(eq(scalar(numOrdered, priceEach), sum)) © ASERT 2006-2010 s.read(m) def more = s.solve() while (more) { println "Found a solution:" numOrdered.each { def v = s.getVar(it) if (v.val) println " $v.val * $v.name" } more = s.nextSolution() } QCON 2010 - 174
  • SimpBlog Case Study... • You have been asked to set up some test cases representing the Simpsons weekly blogging habits • After some careful study you observe the following strange behavior © ASERT 2006-2010 – They never blog on the same day – Marge blogs only on a Saturday or Sunday – Maggie blogs only on a Tuesday or Thursday – Lisa blogs only on a Monday, Wednesday or Friday – Bart blogs only on the day after Lisa – Homer only blogs if noone else blogged the previous day and doesn't allow anyone to blog the next day QCON 2010 - 175
  • ...SimpBlog Case Study... // requires choco 2.1.0-basic.jar from http://choco.emn.fr/ import static choco.Choco.* def m = new choco.cp.model.CPModel() def s = new choco.cp.solver.CPSolver() daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", © ASERT 2006-2010 "Thursday", "Friday", "Saturday"] def bart = makeIntVar('Bart', 0, 6) def homer = makeIntVar('Homer', 0, 6) def marge = makeIntVar('Marge', 0, 6) def lisa = makeIntVar('Lisa', 0, 6) def maggie = makeIntVar('Maggie', 0, 6) def simpsons = [bart, homer, marge, lisa, maggie] ... QCON 2010 - 176
  • ...SimpBlog Case Study... ... // They never blog on the same day for (i in 0..<simpsons.size()) for (j in 0..<i) m.addConstraint(neq(simpsons[i], simpsons[j])) // Marge blogs only on a Saturday or Sunday m.addConstraint(or(eq(marge, 0), eq(marge, 6))) // Maggie blogs only on a Tuesday or Thursday m.addConstraint(or(eq(maggie, 2), eq(maggie, 4))) © ASERT 2006-2010 // Lisa blogs only on a Monday, Wednesday or Friday m.addConstraint(or(eq(lisa, 1), eq(lisa, 3), eq(lisa, 5))) // Bart blogs only on the day after Lisa m.addConstraint(eq(plus(lisa, 1), bart)) // Homer only blogs if noone else blogged the previous // day and doesn't allow anyone to blog the next day m.addConstraint(and(distanceNEQ(homer, marge, 1), distanceNEQ(homer, bart, 1), distanceNEQ(homer, maggie, 1), distanceNEQ(homer, lisa, 1))) ... QCON 2010 - 177
  • ...SimpBlog Case Study ... s.read(m) def more = s.solve() if (!more) println "No Solutions Found" else println pad("Solutions:") + simpsons.collect{ pad(it.name) }.join() while (more) { print pad("") println simpsons.collect { © ASERT 2006-2010 def v = s.getVar(it) pad(daysOfWeek[v.val]) }.join() more = s.nextSolution() } def pad(s) { s.padRight(12) } Solutions: Bart Homer Marge Lisa Maggie Thursday Saturday Sunday Wednesday Tuesday Tuesday Saturday Sunday Monday Thursday Saturday Tuesday Sunday Friday Thursday Thursday Sunday Saturday Wednesday Tuesday QCON 2010 - 178
  • ModelJUnit... • Description – Supports model-based testing – Allows you to write simple finite state machine (FSM) models or extended finite state machine (EFSM) models in Java or Groovy © ASERT 2006-2010 – You can then generate tests from those models and measure various model coverage metrics QCON 2010 - 181
  • ...ModelJUnit... // require modeljunit.jar import nz.ac.waikato.modeljunit.coverage.* import nz.ac.waikato.modeljunit.* class VendingMachineModel implements FsmModel { def state = 0 // 0,25,50,75,100 void reset(boolean testing) {state = 0} boolean vendGuard() {state == 100} @Action void vend() {state = 0} boolean coin25Guard() {state <= 75} © ASERT 2006-2010 @Action void coin25() {state += 25} boolean coin50Guard() {state <= 50} @Action void coin50() {state += 50} } def tester = new RandomTester(new VendingMachineModel()) tester.buildGraph() def metrics = [new ActionCoverage(), new StateCoverage(), new TransitionCoverage(), new TransitionPairCoverage()] metrics.each { tester.addCoverageMetric it } tester.addListener "verbose" tester.generate 20 println 'nMetrics Summary:' tester.printCoverage() QCON 2010 - 182
  • ...ModelJUnit... ... done (0, coin50, 50) done (50, coin25, 75) done (75, coin25, 100) done Random reset(true) done (0, coin50, 50) done (50, coin25, 75) done (75, coin25, 100) done (100, vend, 0) ... © ASERT 2006-2010 done (0, coin50, 50) done (50, coin50, 100) Metrics Summary: done (100, vend, 0) action coverage: 3/3 done (0, coin25, 25) state coverage: 5/5 done (25, coin25, 50) transition coverage: 7/8 done Random reset(true) transition-pair coverage: 8/12 ... done (0, coin50, 50) done (50, coin25, 75) done (75, coin25, 100) done (100, vend, 0) done (0, coin50, 50) done (50, coin25, 75) ... QCON 2010 - 183
  • ModelJUnit: SimpBlog Case Study... • Does the order in which form information is entered affect the application? – Could AJAX effects be causing unexpected results? ... void reset(boolean testing) { authorSelected = false // require modeljunit.jar, htmlunit.jar categorySelected = false © ASERT 2006-2010 import nz.ac.waikato.modeljunit.coverage.* titleEntered = false import nz.ac.waikato.modeljunit.* contentEntered = false import com.gargoylesoftware.htmlunit.WebClient client = new WebClient() page = client.getPage('http://localhost:8080/postForm') class SimpBlogModel implements FsmModel { assert 'Welcome to SimpBlog' == page.titleText boolean authorSelected = false form = page.getFormByName('post') boolean categorySelected = false } ... boolean titleEntered = false boolean contentEntered = false int count = 0 def client, page, form // Special known method, allows equivalence class definition // example states: __ __ __ __, AU __ __ __, AU CA TI CO def getState() { "${authorSelected ? ' AU ' : ' __ '}${categorySelected ? ' CA ' : ' __ '}" + "${titleEntered ? ' TI ' : ' __ '}${contentEntered ? ' CO ' : ' __ '}" } ... QCON 2010 - 184
  • ...ModelJUnit: SimpBlog Case Study... ... boolean "enter title Guard"() { !titleEntered } @Action void "enter title "() { titleEntered = true form.getInputByName('title').setValueAttribute("Title ${count++}") } boolean enterContentGuard() { !contentEntered } @Action void enterContent() { contentEntered = true form.getTextAreaByName('content').setText("Content ${count++}") } boolean chooseAuthorGuard() { !authorSelected } @Action void chooseAuthor() { authorSelected = true // simple version just Lisa © ASERT 2006-2010 form.getSelectByName('author').getOptions().find{ it.text == 'Lisa' }.setSelected(true) } boolean pickCategoryGuard() { !categorySelected } @Action void pickCategory() { categorySelected = true // simple version just Home form.getSelectByName('category').getOptions().find{ it.text == 'Home' }.setSelected(true) } boolean "submit post Guard"() { categorySelected && authorSelected && titleEntered && contentEntered } @Action void "submit post "() { def result = form.getInputByName('btnPost').click() assert result.getElementsByTagName('h1').item(0).textContent.matches('Post.*: Title .*') // could do more asserts here reset(true) } } // end of SimpBlogModel class definition ... QCON 2010 - 185
  • ...ModelJUnit: SimpBlog Case Study... def tester = new RandomTester(new SimpBlogModel()) tester.buildGraph() def metrics = [ new ActionCoverage(), new StateCoverage(), new TransitionCoverage(), new TransitionPairCoverage() ] metrics.each { © ASERT 2006-2010 tester.addCoverageMetric it } tester.addListener "verbose" tester.generate 50 println 'nMetrics Summary:' tester.printCoverage() def graphListener = tester.model.getListener("graph") graphListener.printGraphDot "simpblog.dot" println "nGraph contains " + graphListener.graph.numVertices() + " states and " + graphListener.graph.numEdges() + " transitions." QCON 2010 - 186
  • ...ModelJUnit: SimpBlog Case Study... done ( __ __ __ __ , pickCategory, __ CA __ __ ) ... done ( __ CA __ __ , enterContent, __ CA __ CO ) done ( __ __ __ __ , pickCategory, __ CA __ __ ) done ( __ CA __ CO , enter title , __ CA TI CO ) done ( __ CA __ __ , enterContent, __ CA __ CO ) done ( __ CA TI CO , chooseAuthor, AU CA TI CO ) done ( __ CA __ CO , chooseAuthor, AU CA __ CO ) done ( AU CA TI CO , submit post , __ __ __ __ ) done ( AU CA __ CO , enter title , AU CA TI CO ) done ( __ __ __ __ , pickCategory, __ CA __ __ ) done ( AU CA TI CO , submit post , __ __ __ __ ) done ( __ CA __ __ , chooseAuthor, AU CA __ __ ) done ( __ __ __ __ , chooseAuthor, AU __ __ __ ) done ( AU CA __ __ , enter title , AU CA TI __ ) done ( AU __ __ __ , pickCategory, AU CA __ __ ) done ( AU CA TI __ , enterContent, AU CA TI CO ) done ( AU CA __ __ , enterContent, AU CA __ CO ) done ( AU CA TI CO , submit post , __ __ __ __ ) done ( AU CA __ CO , enter title , AU CA TI CO ) done ( __ __ __ __ , chooseAuthor, AU __ __ __ ) done ( AU CA TI CO , submit post , __ __ __ __ ) done ( AU __ __ __ , pickCategory, AU CA __ __ ) done ( __ __ __ __ , chooseAuthor, AU __ __ __ ) done ( AU CA __ __ , enter title , AU CA TI __ ) done ( AU __ __ __ , enter title , AU __ TI __ ) done ( AU CA TI __ , enterContent, AU CA TI CO ) done ( AU __ TI __ , pickCategory, AU CA TI __ ) done ( AU CA TI CO , submit post , __ __ __ __ ) done ( AU CA TI __ , enterContent, AU CA TI CO ) © ASERT 2006-2010 done ( __ __ __ __ , enterContent, __ __ __ CO ) done ( AU CA TI CO , submit post , __ __ __ __ ) done ( __ __ __ CO , pickCategory, __ CA __ CO ) done Random reset(true) done ( __ CA __ CO , chooseAuthor, AU CA __ CO ) done ( __ __ __ __ , pickCategory, __ CA __ __ ) done ( AU CA __ CO , enter title , AU CA TI CO ) done ( __ CA __ __ , enterContent, __ CA __ CO ) done ( AU CA TI CO , submit post , __ __ __ __ ) done ( __ CA __ CO , enter title , __ CA TI CO ) done ( __ __ __ __ , pickCategory, __ CA __ __ ) done ( __ CA TI CO , chooseAuthor, AU CA TI CO ) done ( __ CA __ __ , enter title , __ CA TI __ ) done ( __ CA TI __ , chooseAuthor, AU CA TI __ ) Metrics Summary: done ( AU CA TI __ , enterContent, AU CA TI CO ) action coverage: 5/5 done ( AU CA TI CO , submit post , __ __ __ __ ) state coverage: 12/16 done ( __ __ __ __ , chooseAuthor, AU __ __ __ ) transition coverage: 19/33 done ( AU __ __ __ , pickCategory, AU CA __ __ ) transition-pair coverage: 26/56 done ( AU CA __ __ , enter title , AU CA TI __ ) done ( AU CA TI __ , enterContent, AU CA TI CO ) Graph contains 16 states and 33 transitions. done ( AU CA TI CO , submit post , __ __ __ __ ) ... QCON 2010 - 187
  • ...ModelJUnit: SimpBlog Case Study... © ASERT 2006-2010 Advanced Simplified version version (just Lisa) QCON 2010 - 188
  • ...ModelJUnit: SimpBlog Case Study © ASERT 2006-2010 QCON 2010 - 189
  • ScalaCheck • Description – Tool for testing Scala, Java and Groovy programs – Based on property specifications and automatic random test data generation > scala -classpath ScalaCheck-1.5.jar Welcome to Scala version 2.7.6.final © ASERT 2006-2010 scala> import org.scalacheck.Prop.forAll scala> val propConcatLists = forAll { (l1: List[Int], l2: List[Int]) => | l1.size + l2.size == (l1 ::: l2).size } propConcatLists: org.scalacheck.Prop = Prop scala> propConcatLists.check + OK, passed 100 tests. scala> val propSqrt = forAll { (n: Int) => scala.Math.sqrt(n*n) == n } propSqrt: org.scalacheck.Prop = Prop scala> propSqrt.check ! Falsified after 2 passed tests. > ARG_0: "-1" (1 shrinks, original arg: "-2") QCON 2010 - 190
  • ScalaCheck: SimpBlog Case Study... class SimpBlogChecker { static postAndCheck = null static clean(s) { s.replace('', '').replace('n', 'n') } static ResultHolder postAndReturn(String title, String content, String author, String category) { if (!postAndCheck) postAndCheck = new GroovyShell().parse(''' @Grab('net.sourceforge.htmlunit:htmlunit:2.5') import com.gargoylesoftware.htmlunit.WebClient def page = new WebClient().getPage('http://localhost:8080/postForm') def form = page.getFormByName('post') form.getInputByName('title').setValueAttribute(title) © ASERT 2006-2010 form.getSelectByName('category').getOptions().find { it.text == category }.setSelected(true) form.getSelectByName('author').getOptions().find { it.text == author }.setSelected(true) form.getTextAreaByName('content').setText(content) def result = form.getInputByName('btnPost').click() def titleResult = result.getElementsByTagName('h1').item(0).textContent def h3headings = result.getElementsByTagName('h3') def categoryResult = h3headings.item(1).textContent def authorResult = h3headings.item(2).textContent def para = result.getByXPath('//TABLE//TR/TD/P')[0] def contentResult = para.textContent return new ResultHolder(titleResult, contentResult, authorResult, categoryResult) ''') ... QCON 2010 - 191
  • ...ScalaCheck: SimpBlog Case Study... ... def binding = new Binding() binding.setVariable('title', title) binding.setVariable('content', clean(content)) binding.setVariable('author', author) binding.setVariable('category', category) postAndCheck.binding = binding postAndCheck.run() } } © ASERT 2006-2010 class ResultHolder { String title, content, author, category ResultHolder(String title, String content, String author, String category) { this.title = title this.content = content this.author = author this.category = category } } > groovyc SimpBlogChecker.groovy QCON 2010 - 192
  • ...ScalaCheck: SimpBlog Case Study... //CheckSimpBlog.scala import org.scalacheck.Prop._ import org.scalacheck.ConsoleReporter.testStatsEx import org.scalacheck.Test.check import org.scalacheck.Arbitrary._ import org.scalacheck.Gen._ object CheckSimpBlog { © ASERT 2006-2010 val fieldsGen = for { title <- elements("Title 1", "Title 2") content <- arbitrary[String] author <- elements("Bart", "Homer", "Lisa", "Marge", "Maggie") category <- elements("Home", "Work", "Food", "Travel") } yield (title, content, author, category) ... > scalac -classpath .;../lib/ScalaCheck-1.5.jar; ../../groovy-1.7-beta-2-SNAPSHOT/embeddable/groovy-all- 1.7-beta-2-SNAPSHOT.jar CheckSimpBlog.scala QCON 2010 - 193
  • ...ScalaCheck: SimpBlog Case Study... ... val enterFieldsAcceptedAndEchoedProperty = forAll(fieldsGen)( fields => { val (title, content, author, category) = fields val result = SimpBlogChecker.postAndReturn(title, content, author, category) result.getTitle contains title result.getContent contains content result.getAuthor contains author result.getCategory contains category } ) © ASERT 2006-2010 val tests = scala.List( ("enterFieldsAcceptedAndEchoedProperty", enterFieldsAcceptedAndEchoedProperty) ) def main(args: scala.Array[String]) = tests foreach { case (name, p) => testStatsEx(name, check(p)) } } > scala -classpath .;../lib/ScalaCheck-1.5.jar;../../groovy-1.7-beta -2-SNAPSHOT/embeddable/groovy-all-1.7-beta-2-SNAPSHOT.jar;../../ groovy-1.7-beta-2-SNAPSHOT/lib/ivy-2.1.0-rc2.jar CheckSimpBlog + OK, passed 100 tests. QCON 2010 - 194
  • ...ScalaCheck: SimpBlog Case Study © ASERT 2006-2010 QCON 2010 - 195
  • More Information... • Yet more tools – http://grails.org/plugin/perf4j – http://naleid.com/blog/2009/04/14/grails-build-test-data-01-plugin- released/ – http://www.concordion.org/ – http://maxheapsize.com/2009/10/13/concordion-vs-cucumber-and- java-based-acceptance-testing/ © ASERT 2006-2010 – http://www.pushtotest.com/ – http://maxq.tigris.org/, http://sonar.codehaus.org/ – http://celerity.rubyforge.org/, http://jcrawler.sourceforge.net/ – http://jameleon.sourceforge.net/ – http://databene.org/databene-benerator – http://grinder.sourceforge.net/ 199
  • ...More Information... • Testing Web sites – http://www.testingreflections.com/ – http://testobsessed.com/ – http://www.agiletester.ca/ – http://www.testingspot.net/ – http://opensourcetesting.org/ © ASERT 2006-2010 200
  • ...More Information... • Groovy Web sites – http://groovy.codehaus.org – http://grails.codehaus.org – http://pleac.sourceforge.net/pleac_groovy (many examples) – http://www.asert.com.au/training/java/GV110.htm (workshop) • Groovy User Mailing list © ASERT 2006-2010 – user@groovy.codehaus.org • Groovy Information portals – http://www.aboutgroovy.org – http://www.groovyblogs.org • Groovy Documentation (1000+ pages) – Getting Started Guide, User Guide, Developer Guide, Testing Guide, Cookbook Examples, Advanced Usage Guide 201
  • ...More Information © ASERT 2006-2010 202