groovy power features
DESCRIPTION
Leveraging the language, Use and abuse of Design Patterns, Web Services, Writing DSLs, Groovy Testing, Polyglot Groovy, Parallel Processing, Enterprise GroovyTRANSCRIPT
© A
SE
RT
2006-2
010
Dr Paul King
@paulk_asert
ASERT, Australia
Groovy Power Features!
Topics
Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
• Web Services
• Writing DSLs
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 2
© A
SE
RT
2006-2
010
QCON 2010 - 3
© A
SE
RT
2006-2
010
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.”
Groovy = Java – boiler plate code+ mostly dynamic typing+ closures+ domain specific languages+ builders+ metaprogramming+ GDK library
QCON 2010 - 4
© A
SE
RT
2006-2
010
Groovy Goodies Overview• Fully object oriented
• Closures: reusable
and assignable
pieces of code
• Operators can be
overloaded
• Multimethods
• Literal declaration for
lists (arrays), maps,
ranges and regular
expressions
• GPath: efficient
object navigation
• GroovyBeans
• grep and switch
• Templates, builder,
swing, Ant, markup,
XML, SQL, XML-RPC,
Scriptom, Grails,
tests, Mocks
Growing Acceptance …
A slow and steady start but now gaining in
momentum, maturity and mindshare
Now free
… Growing Acceptance …
© A
SE
RT
2006-2
010
QCON 2010 - 6
… Growing Acceptance …
© A
SE
RT
2006-2
010
QCON 2010 - 7
Groovy and Grails downloads:
70-90K per month and growing
… Growing Acceptance …
© A
SE
RT
2006-2
010
QCON 2010 - 8
Source: http://www.grailspodcast.com/
Source: http://www.micropoll.com/akira/mpresult/501697-116746
… Growing Acceptance …
© A
SE
RT
2006-2
010
QCON 2010 - 9
http://www.java.net
http://www.jroller.com/scolebourne/entry/devoxx_2008_whiteboard_votes
… Growing Acceptance …
© A
SE
RT
2006-2
010
QCON 2010 - 10http://www.leonardoborges.com/writings
What alternative JVM language are you using or intending to use
… Growing Acceptance …
© A
SE
RT
2006-2
010
QCON 2010 - 11
http://it-republik.de/jaxenter/quickvote/results/1/poll/44 (translated using http://babelfish.yahoo.com)
… Growing Acceptance …
© A
SE
RT
2006-2
010
QCON 2010 - 12
http://pollpigeon.com/jsf-grails-wicket/r/25665/
… Growing Acceptance
QCON 2010 - 13
© A
SE
RT
2006-2
010
QCON 2010 - 14
© A
SE
RT
2006-2
010
The Landscape of JVM Languages
Java bytecode calls
for static types
Dynamic features call
for dynamic types
mostly
dynamic
typing
The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ platform.
QCON 2010 - 15
© A
SE
RT
2006-2
010
Groovy StarterSystem.out.println("Hello, World!"); // optional semicolon,println 'Hello, World!' // System.out, brackets,
// main() method, class defn
def name = 'Guillaume' // dynamic typingprintln "$name, I'll get the car." // GString
String longer = """${name}, the caris in the next row.""" // multi-line string
// with static typing
assert 0.5 == 1/2 // BigDecimal equals()
def printSize(obj) { // optional duck typingprint obj?.size() // safe dereferencing
}
def pets = ['ant', 'bee', 'cat'] // native list syntaxpets.each { pet -> // closure support
assert pet < 'dog' // overloading '<' on String} // or: for (pet in pets)...
A Better Java...
QCON 2010 - 16
© A
SE
RT
2006-2
010
import java.util.List;import java.util.ArrayList;
class Erase {private List removeLongerThan(List strings, int length) {
List result = new ArrayList();for (int i = 0; i < strings.size(); i++) {
String s = (String) strings.get(i);if (s.length() <= length) {
result.add(s);}
}return result;
}public static void main(String[] args) {
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 (int i = 0; i < shortNames.size(); i++) {
String s = (String) shortNames.get(i);System.out.println(s);
}}
}
This code
is valid
Java and
valid Groovy
Based on an
example by
Jim Weirich
& Ted Leung
...A Better Java...
QCON 2010 - 17
© A
SE
RT
2006-2
010
import java.util.List;import java.util.ArrayList;
class Erase {private List removeLongerThan(List strings, int length) {
List result = new ArrayList();for (int i = 0; i < strings.size(); i++) {
String s = (String) strings.get(i);if (s.length() <= length) {
result.add(s);}
}return result;
}public static void main(String[] args) {
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 (int i = 0; i < shortNames.size(); i++) {
String s = (String) shortNames.get(i);System.out.println(s);
}}
}
Do the
semicolons
add anything?
And shouldn‟t
we us more
modern list
notation?
Why not
import common
libraries?
...A Better Java...
QCON 2010 - 18
© A
SE
RT
2006-2
010
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) {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)}
}}
...A Better Java...
QCON 2010 - 19
© A
SE
RT
2006-2
010
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) {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)}
}}
Do we need
the static types?
Must we always
have a main
method and
class definition?
How about
improved
consistency?
...A Better Java...
QCON 2010 - 20
© A
SE
RT
2006-2
010
def removeLongerThan(strings, length) {def result = new ArrayList()for (s in strings) {
if (s.size() <= length) {result.add(s)
}}return result
}
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)}
...A Better Java...
QCON 2010 - 21
© A
SE
RT
2006-2
010
def removeLongerThan(strings, length) {def result = new ArrayList()for (s in strings) {
if (s.size() <= length) {result.add(s)
}}return result
}
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)}
Shouldn‟t we
have special
notation for lists?
And special
facilities for
list processing?
Is „return‟
needed at end?
...A Better Java...
QCON 2010 - 22
© A
SE
RT
2006-2
010
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) }
...A Better Java...
QCON 2010 - 23
© A
SE
RT
2006-2
010
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) }
Is the method
now needed?
Easier ways to
use common
methods?
Are brackets
required here?
...A Better Java...
QCON 2010 - 24
© A
SE
RT
2006-2
010
names = ["Ted", "Fred", "Jed", "Ned"]println namesshortNames = names.findAll{ it.size() <= 3 }println shortNames.size()shortNames.each{ println it }
...A Better Java
QCON 2010 - 25
© A
SE
RT
2006-2
010
names = ["Ted", "Fred", "Jed", "Ned"]println namesshortNames = names.findAll{ it.size() <= 3 }println shortNames.size()shortNames.each{ println it }
[Ted, Fred, Jed, Ned]3TedJedNed
Topics
• Groovy Intro
Leveraging the language
• Use and abuse of Design Patterns
• Web Services
• Writing DSLs
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 26
© A
SE
RT
2006-2
010
Better Control Structures: Switch
• Extensible– Implement your own isCase() method
QCON 2010 - 27
© A
SE
RT
2006-2
010
switch (10) {case 0 : assert false ; breakcase 0..9 : assert false ; breakcase [8,9,11] : assert false ; breakcase Float : assert false ; breakcase {it%3 == 0} : assert false ; breakcase ~/../ : assert true ; breakdefault : assert false ; break
}
QCON 2010 - 28
© A
SE
RT
2006-2
010
Better Control Structures: Switch – Custom isCase
enum Color {
yellow, orange, purple
def isCase(thing) { thing.color == this }
}
import static Color.*
class Banana { def color = yellow }
class Orange { def color = orange }
class Grape { def color = purple }
fruits = [new Banana(), new Orange(), new Grape()]
fruits.each { println inspectFruit(it) }
def inspectFruit(f) {
switch (f) {
case yellow: return 'Found something yellow'
case orange: return 'Found something orange'
default: return 'Unknown color'
}
}Found something yellowFound something orangeUnknown color
WARNING:
Advanced
Topic!
QCON 2010 - 29
© A
SE
RT
2006-2
010
Better Control Structures: Switch – Custom isCase
enum Color { yellow, orange, purple }
import static Color.*
class Banana { def color = yellow }
class Orange { def color = orange }
class Grape { def color = purple }
fruits = [new Banana(), new Orange(), new Grape()]
fruits.each { println inspectFruit(it) }
def inspectFruit(f) {
switch (f.color) {
case [yellow, orange]:
return "Found something $f.color"
default: return 'Unknown color'
}
}
Found something yellowFound something orangeUnknown color
QCON 2010 - 30
© A
SE
RT
2006-2
010
Better Control Structures: Switch – Custom isCase
enum Color {yellow, orange, purpledef isCase(thing) {
thing?.hasProperty('color') && thing.color == this }}
enum State {peeled, unpeeleddef isCase(thing) {
try {return thing.color == this
} catch (MissingPropertyException) {return false
}}
import static Color.*import static State.*
class Banana { def color = yellow; def state = peeled }class Orange { def color = orange }class Grape { def state = peeled }...
WARNING:
Advanced Topic!
QCON 2010 - 31
© A
SE
RT
2006-2
010
Better Control Structures: Switch – Custom isCase
...fruits = [new Banana(), new Orange(), new Grape()]fruits.each { println inspectFruit(it) }
def inspectFruit(f) {def yellowAndPeeled = { yellow.isCase(it) &&
peeled.isCase(it) }switch (f) {
case yellowAndPeeled:return 'Possibly found a banana split'
case yellow:return 'Found something yellow'
case peeled:return 'Found something peeled'
default:return 'No comment'
}}
WARNING:
Advanced Topic!
Possibly found a banana splitNo commentFound something peeled
QCON 2010 - 32
© A
SE
RT
2006-2
010
Better Control Structures: Switch Poker…
suits = 'SHDC'ranks = '23456789TJQKA'suit = { String card -> suits.indexOf(card[1]) }rank = { String card -> ranks.indexOf(card[0]) }rankSizes = { List cards ->
cards.groupBy(rank).collect{ k, v -> v.size() }.sort() }rankValues = { List cards ->
cards.collect{ rank(it) }.sort() }// ...
8C TS KC 9H 4S 7D 2S 5D 3S AC
hand1 hand2
println rankSizes(["7S", "7H", "2H", "7D", "AH"])// => [1, 1, 3]
QCON 2010 - 33
© A
SE
RT
2006-2
010
…Better Control Structures: Switch Poker…
// ...flush = { List cards -> cards.groupBy(suit).size() == 1 }straight = { def v = rankValues(it); v == v[0]..v[0]+4 }straightFlush = { List cards -> straight(cards) && flush(cards) }fourOfAKind = { List cards -> rankSizes(cards) == [1, 4] }fullHouse = { List cards -> rankSizes(cards) == [2, 3] }threeOfAKind = { List cards -> rankSizes(cards) == [1, 1, 3] }twoPair = { List cards -> rankSizes(cards) == [1, 2, 2] }pair = { List cards -> rankSizes(cards) == [1, 1, 1, 2] }// ...
QCON 2010 - 34
© A
SE
RT
2006-2
010
… Better Control Structures: Switch Poker
// ...def rankHand(List cards) {
switch (cards) {case straightFlush : return 9case fourOfAKind : return 8case fullHouse : return 7case flush : return 6case straight : return 5case threeOfAKind : return 4case twoPair : return 3case pair : return 2default : return 1
}}// ...
Topics
• Groovy Intro
• Leveraging the language
Use and abuse of Design Patterns
• Web Services
• Writing DSLs
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 35
© A
SE
RT
2006-2
010
Grapes / Grab
QCON 2010 - 36
© A
SE
RT
2006-2
010
// Google Collections exampleimport com.google.common.collect.HashBiMap
@Grab(group='com.google.collections',module='google-collections',version='1.0-rc2')
def getFruit() {[ grape:'purple',
lemon:'yellow',orange:'orange' ] as HashBiMap
}
assert fruit.lemon == 'yellow'assert fruit.inverse().yellow == 'lemon'
Better Design Patterns: Immutable...
• Java Immutable Class– As per Joshua Bloch
Effective Java
QCON 2010 - 37
© A
SE
RT
2006-2
010
public final class Punter {private final String first;private final String last;
public String getFirst() {return first;
}
public String getLast() {return last;
}
@Overridepublic int hashCode() {
final int prime = 31;int result = 1;result = prime * result + ((first == null)
? 0 : first.hashCode());result = prime * result + ((last == null)
? 0 : last.hashCode());return result;
}
public Punter(String first, String last) {this.first = first;this.last = last;
}// ...
// ...@Overridepublic boolean equals(Object obj) {
if (this == obj)return true;
if (obj == null)return false;
if (getClass() != obj.getClass())return false;
Punter other = (Punter) obj;if (first == null) {
if (other.first != null)return false;
} else if (!first.equals(other.first))return false;
if (last == null) {if (other.last != null)
return false;} else if (!last.equals(other.last))
return false;return true;
}
@Overridepublic String toString() {
return "Punter(first:" + first+ ", last:" + last + ")";
}
}
...Better Design Patterns: Immutable...
QCON 2010 - 38
© A
SE
RT
2006-2
010
public final class Punter {private final String first;private final String last;
public String getFirst() {return first;
}
public String getLast() {return last;
}
@Overridepublic int hashCode() {
final int prime = 31;int result = 1;result = prime * result + ((first == null)
? 0 : first.hashCode());result = prime * result + ((last == null)
? 0 : last.hashCode());return result;
}
public Punter(String first, String last) {this.first = first;this.last = last;
}// ...
// ...@Overridepublic boolean equals(Object obj) {
if (this == obj)return true;
if (obj == null)return false;
if (getClass() != obj.getClass())return false;
Punter other = (Punter) obj;if (first == null) {
if (other.first != null)return false;
} else if (!first.equals(other.first))return false;
if (last == null) {if (other.last != null)
return false;} else if (!last.equals(other.last))
return false;return true;
}
@Overridepublic String toString() {
return "Punter(first:" + first+ ", last:" + last + ")";
}
}
• Java Immutable Class– As per Joshua Bloch
Effective Java
boilerplate
...Better Design Patterns: Immutable
QCON 2010 - 39
© A
SE
RT
2006-2
010
@Immutable class Punter {String first, last
}
QCON 2010 - 40
© A
SE
RT
2006-2
010
Better Design Patterns: Singleton
class Calculator {def total = 0def add(a, b) { total++; a + b }
}
def INSTANCE = new Calculator()Calculator.metaClass.constructor = { -> INSTANCE }
def c1 = new Calculator()def c2 = new Calculator()
assert c1.add(1, 2) == 3assert c2.add(3, 4) == 7
assert c1.is(c2)assert [c1, c2].total == [2, 2]
@Singleton(lazy=true)class X {
def getHello () {"Hello, World!"
}}
println X.instance.hello
QCON 2010 - 41
© A
SE
RT
2006-2
010
Better Design Patterns: Delegate…
import java.util.Date;
public class Event {private String title;private String url;private Date when;
public String getUrl() {return url;
}
public void setUrl(String url) {this.url = url;
}
public String getTitle() {return title;
}
public void setTitle(String title) {this.title = title;
}// ...
public Date getWhen() {return when;
}
public void setWhen(Date when) {this.when = when;
}
public boolean before(Date other) {return when.before(other);
}
public void setTime(long time) {when.setTime(time);
}
public long getTime() {return when.getTime();
}
public boolean after(Date other) {return when.after(other);
}// ...
QCON 2010 - 42
© A
SE
RT
2006-2
010
…Better Design Patterns: Delegate…
import java.util.Date;
public class Event {private String title;private String url;private Date when;
public String getUrl() {return url;
}
public void setUrl(String url) {this.url = url;
}
public String getTitle() {return title;
}
public void setTitle(String title) {this.title = title;
}// ...
public Date getWhen() {return when;
}
public void setWhen(Date when) {this.when = when;
}
public boolean before(Date other) {return when.before(other);
}
public void setTime(long time) {when.setTime(time);
}
public long getTime() {return when.getTime();
}
public boolean after(Date other) {return when.after(other);
}// ...
boilerplate
QCON 2010 - 43
© A
SE
RT
2006-2
010
…Better Design Patterns: Delegate
class Event {String title, url@Delegate Date when
}
def gr8conf = new Event(title: "GR8 Conference",url: "http://www.gr8conf.org",when: Date.parse("yyyy/MM/dd", "2009/05/18"))
def javaOne = new Event(title: "JavaOne",url: "http://java.sun.com/javaone/",when: Date.parse("yyyy/MM/dd", "2009/06/02"))
assert gr8conf.before(javaOne.when)
Topics
• Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
Web Services
• Writing DSLs
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 44
© A
SE
RT
2006-2
010
SOAP Client and Server
QCON 2010 - 45
© A
SE
RT
2006-2
010
class MathService {double add(double a, double b) {
a + b}double square(double c) {
c * c}
}
import groovy.net.soap.SoapServer
def server = new SoapServer('localhost', 6789)server.setNode('MathService')server.start()
import groovy.net.soap.SoapClient
def math = new SoapClient('http://localhost:6789/MathServiceInterface?wsdl')
assert math.add(1.0, 2.0) == 3.0
assert math.square(3.0) == 9.0
Better Testing: SoapUI
• Tool for testing Web Services has a built-
in Groovy editor for custom steps
QCON 2010 - 46
© A
SE
RT
2006-2
010
Topics
• Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
• Web Services
Writing DSLs
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 47
© A
SE
RT
2006-2
010
Groovy DSL Features
• Literal Syntax Conventions
• Scripts as well as Classes & Optional Syntax
• Optional Typing & Named Arguments
• Type Transformations
• Using With
• Operator Overloading
• Closures
• Runtime Metaprogramming
• Compile-time Metaprogramming (AST Macros)
• Builders
QCON 2010 - 48
© A
SE
RT
2006-2
010
Literal Syntax Conventions
• Lists– Special syntax for list literals
– Additional common methods (operator overloading)
• Maps– Special syntax for map literals
– Additional common methods
• Ranges– Special syntax for various kinds of ranges
QCON 2010 - 49
© A
SE
RT
2006-2
010
def list = [3, new Date(), 'Jan']assert list + list == list * 2
def map = [a: 1, b: 2]assert map['a'] == 1 && map.b == 2
def letters = 'a'..'z'def numbers = 0..<10
Scripts as well as Classes & Optional Syntax
• Java
• Groovy
QCON 2010 - 50
© A
SE
RT
2006-2
010
public class HelloJava {public static void main(String[] args) {
System.out.println("Hello World");}
}
System.out.println('Hello World');
println 'Hello World'
class HelloGroovy {static main(String[] args) {
System.out.println("Hello World");}
}
Optional Typing & Named Params
• Java
• Groovy
QCON 2010 - 51
© A
SE
RT
2006-2
010
import java.util.Date;import java.util.HashMap;import java.util.Map;
public class OptionalSyntax {public static void printDetails(Map<String, String> args) {
System.out.println("Details as at: " + new Date());String first = args.get("first");System.out.println("First name: " + (first != null ? first : "unknown"));String last = args.get("last");System.out.println("Last name: " + (last != null ? last : "unknown"));
}
public static void main(String[] args) {Map<String, String> details = new HashMap<String, String>();details.put("first", "John");details.put("last", "Smith");printDetails(details);
}} def printDetails(args) {
println """Details as at: ${new Date()}First name: $args.firstLast name: $args.last"""}printDetails first:'John', last:'Smith'
Closures
• Traditional mainstream languages– Data can be stored in variables, passed around,
combined in structured ways to form more complex
data; code stays put where it is defined
• Languages supporting closures– Data and code can be stored in variables, passed
around, combined in structured ways to form more
complex algorithms and data
QCON 2010 - 52
© A
SE
RT
2006-2
010
doubleNum = { num -> num * 2 }println doubleNum(3) // => 6
processThenPrint = { num, closure ->num = closure(num); println "num is $num"
}processThenPrint(3, doubleNum) // => num is 6processThenPrint(10) { it / 2 } // => num is 5
Static Imports
QCON 2010 - 53
© A
SE
RT
2006-2
010
import groovy.swing.SwingXBuilderimport static java.awt.Color.*import static java.lang.Math.*
def swing = new SwingXBuilder()def frame = swing.frame(size: [300, 300]) {
graph(plots: [[GREEN, {value -> sin(value)}],[BLUE, {value -> cos(value)}],[RED, {value -> tan(value)}]
])}.show()
Using '.with' Example
QCON 2010 - 54
© A
SE
RT
2006-2
010
letters = ['a', 'b', 'c']
range = 'b'..'d'
letters.with {
add 'd'
remove 'a'
}
assert letters == range
map = [a:10, b:4, c:7]
map.with {
assert (a + b) / c == 2
}
Coin example...
QCON 2010 - 55
© A
SE
RT
2006-2
010
enum Coin {
penny(1), nickel(5), dime(10), quarter(25)
Coin(int value) { this.value = value }
int value
}
import static Coin.*
assert 2 * quarter.value +
1 * nickel.value +
2 * penny.value == 57
...Coin example...
QCON 2010 - 56
© A
SE
RT
2006-2
010
class CoinMath {
static multiply(Integer self, Coin c) {
self * c.value
}
}
use (CoinMath) {
assert 2 * quarter +
1 * nickel +
2 * penny == 57
}
// EMC equivalent
Integer.metaClass.multiply = {
Coin c -> delegate * c.value
}
assert 2 * quarter + 1 * nickel + 2 * penny == 57
...Coin example
QCON 2010 - 57
© A
SE
RT
2006-2
010
class CoinValues {
static get(Integer self, String name) {
self * Coin."${singular(name)}".value
}
static singular(String val) {
val.endsWith('ies') ? val[0..-4] + 'y' : val.endsWith('s') ? val[0..-2] : val
}
}
use (CoinValues) {
assert 2.quarters + 1.nickel + 2.pennies == 57
}
// EMC equivalent
Integer.metaClass.getProperty = { String name ->
def mp = Integer.metaClass.getMetaProperty(name)
if (mp) return mp.getProperty(delegate)
def singular = name.endsWith('ies') ? name[0..-4] + 'y' :
name.endsWith('s') ? name[0..-2] : name
delegate * Coin."$singular".value
}
assert 2.quarters + 1.nickel + 2.pennies == 57
Game example...
QCON 2010 - 58
© A
SE
RT
2006-2
010
// Trying out the game DSL idea by Sten Anderson from:
// http://blogs.citytechinc.com/sanderson/?p=92
class GameUtils {
static VOWELS = ['a', 'e', 'i', 'o', 'u']
static listItems(things) {
def result = ''
things.eachWithIndex{ thing, index ->
if (index > 0) {
if (index == things.size() - 1) result += ' and '
else if (index < things.size() - 1) result += ', '
}
result += "${thing.toLowerCase()[0] in VOWELS ? 'an' : 'a'} $thing"
}
result ?: 'nothing'
}
}
import static GameUtils.*
...
...Game example...
QCON 2010 - 59
© A
SE
RT
2006-2
010
...
class Room {
def description
def contents = []
}
...
...Game example...
QCON 2010 - 60
© A
SE
RT
2006-2
010
...
class Player {
def currentRoom
def inventory = []
void look() {
println "You are in ${currentRoom?.description?:'the void'} which contains ${listItems(currentRoom?.contents)}"
}
void inv() {
println "You are holding ${listItems(inventory)}"
}
void take(item) {
if (currentRoom?.contents?.remove(item)) {
inventory << item
println "You took the $item"
} else {
println "I see no $item here"
}
}
...
...Game example...
QCON 2010 - 61
© A
SE
RT
2006-2
010
...
void drop(item) {
if (inventory?.remove(item)) {
currentRoom?.contents << item
println "You dropped the $item"
} else {
println "You don't have the $item"
}
}
def propertyMissing(String name) {
if (metaClass.respondsTo(this, name)) {
this."$name"()
}
name
}
}
...
...Game example...
QCON 2010 - 62
© A
SE
RT
2006-2
010
...
Room plainRoom = new Room(description:'a plain white room',
contents:['dagger', 'emerald', 'key'])
Player player = new Player(currentRoom:plainRoom)
player.with{
inv
look
take dagger
inv
look
take emerald
inv
look
take key
drop emerald
inv
look
}
assert player.inventory == ['dagger', 'key']
...
...Game example
QCON 2010 - 63
© A
SE
RT
2006-2
010
...
// now try some error conditions
plainRoom.description = null
player.with {
drop gold
take gold
drop emerald
take emerald
take emerald
look
}
Grails Criteria
QCON 2010 - 64
© A
SE
RT
2006-2
010
// Account is a POJO in our domain/model
def c = Account.createCriteria()
def results = c {
like("holderFirstName", "Fred%")
and {
between("balance", 500, 1000)
eq("branch", "London")
}
maxResults(10)
order("holderLastName", "desc")
}// source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.2 Criteria
Grails Criteria Example
QCON 2010 - 65
© A
SE
RT
2006-2
010
// Book is a POJO in our domain/model
def book = Book.findByTitle("The Stand")
book = Book.findByTitleLike("Harry Pot%")
book = Book.findByReleaseDateBetween( firstDate, secondDate )
book = Book.findByReleaseDateGreaterThan( someDate )
book = Book.findByTitleLikeOrReleaseDateLessThan(
"%Something%", someDate )
books = Book.findAllByTitleLikeAndReleaseDateGreaterThan(
"%Java%", new Date()-30)
// source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.1 Dynamic Finders
Grails Bean Builder Example
QCON 2010 - 66
© A
SE
RT
2006-2
010
bb.beans {
marge(Person) {
name = "marge"
husband = { Person p ->
name = "homer"
age = 45
props = [overweight:true, height:"1.8m"]
}
children = [bart, lisa]
}
bart(Person) {
name = "Bart"
age = 11
}
lisa(Person) {
name = "Lisa"
age = 9
}
}
// source: 14. Grails and Spring: 14.3 Runtime Spring with the Beans DSL
Groovy Lab Example
QCON 2010 - 67
© A
SE
RT
2006-2
010
// require GroovyLab
import static org.math.array.Matrix.*
import static org.math.plot.Plot.*
def A = rand(10,3) // random Matrix of 10 rows and 3 columns
def B = fill(10,3,1.0) // one Matrix of 10 rows and 3 columns
def C = A + B // support for matrix addition with "+" or "-"
def D = A - 2.0 // support for number addition with "+" or "-"
def E = A * B // support for matrix multiplication or division
def F = rand(3,3)
def G = F**(-1) // support for matrix power (with integers only)
println A // display Matrix content
plot("A",A,"SCATTER") // plot Matrix values as ScatterPlot
def M = rand(5,5) + id(5) //Eigenvalues decomposition
println "M=\n" + M
println "V=\n" + V(M)
println "D=\n" + D(M)
println "M~\n" + (V(M) * D(M) * V(M)**(-1))
Operator Overloading Example
QCON 2010 - 68
© A
SE
RT
2006-2
010
BigDecimal a = new BigDecimal(3.5d);
BigDecimal b = new BigDecimal(4.0d);
assert a.multiply(b).compareTo(new BigDecimal(14.0d)) == 0;
assert a.multiply(b).equals(new BigDecimal(14.0d).setScale(1));
def c = 3.5, d = 4.0
assert c * d == 14.0
Type Transformation Example
QCON 2010 - 69
© A
SE
RT
2006-2
010
class InventoryItem {
def weight, name
InventoryItem(Map m) {
this.weight = m.weight; this.name = m.name
}
InventoryItem(weight, name) {
this.weight = weight; this.name = name
}
InventoryItem(String s) {
s.find(/weight=(\d*)/) { all, w -> this.weight = w }
s.find(/name=(.*)/) { all, n -> this.name = n }
}
}
def room = [:]
def gold = [weight:50, name:'Gold'] as InventoryItem
def emerald = [10, 'Emerald'] as InventoryItem
def dagger = ['weight=5, name=Dagger'] as InventoryItem
room.contents = [gold, emerald, dagger]
room.contents.each{ println it.dump() }
AST Builder
• Numerous approaches, still evolving.
“From code” approach:
• Produces:
def result = new AstBuilder().buildFromCode {println "Hello World"
}
BlockStatement-> ReturnStatement
-> MethodCallExpression-> VariableExpression("this")-> ConstantExpression("println")-> ArgumentListExpression
-> ConstantExpression("Hello World")
Topics
• Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
• Web Services
• Writing DSLs
Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 71
© A
SE
RT
2006-2
010
Types of Testing
QCON 2010 - 72
© A
SE
RT
2006-2
010
Unit Testing
Mock/interaction testing
State-based testing
Integration Testing
Acceptance Testing
Web drivers
Non-web drivers
Test runners
Techniques
Testing DSLs
ATDD/BDD
Data-driven
Logic-driven
Model-driven
Performance
testing
All-pairs &
combinations
Gpars
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
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 - 73
© A
SE
RT
2006-2
010
Groovy and Testing Tool Spectrum*
QCON 2010 - 74
© A
SE
RT
2006-2
010
Database
Drivers
DbUnit
DataSets
SqlUnit
groovy.sql
JPA
JDO
BigTable
JDBC
SOAP /
REST
Drivers
GroovyWS
XML-RPC
CXF
Axis2
JAX-WS
JAX-RS
Utilities
AllPairs, Combinations
Polyglot languages
Logic programming
Threads, Parallel /
Concurrency libraries
Data-driven libraries
Networking libraries
XML Processing
Read/write files /
Excel / Word / CSV
Reporting, Logging
Other
Drivers
FEST
FTP
AntUnit
Telnet
SSH
ExecTools
iTest2, SoapUI, Twist,
IDEs, JMeter, Text
editors, Recorders,
Sahi, Build Tools, CI
Web
Drivers
WebTest
WebDriver
JWebUnit
Tellurium
Selenium
HtmlUnit
Watij
HttpBuilder
Cyberneko
Runners
Native Groovy, JUnit, TestNG, Spock, EasyB,
JBehave, Cucumber, Robot Framework
* Tools/libraries/frameworks don't always neatly fall into one category – still useful conceptually
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)
– 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 - 75
© A
SE
RT
2006-2
010
HtmlUnit: Testing New Blog Post...
QCON 2010 - 76
© A
SE
RT
2006-2
010
@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 titleassert 'Welcome to SimpBlog' == page.titleText
// fill in blog entry and post itdef 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()
...
...HtmlUnit: Testing New Blog Post
QCON 2010 - 77
© A
SE
RT
2006-2
010
...
// check blog post detailsassert 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></table>def cell = result.getByXPath('//TABLE//TR/TD')[0]def para = cell.getFirstChild()assert para.textContent == 'Cowabunga Dude!'
QCON 2010 - 78
© A
SE
RT
2006-2
010
WebTest testing Web Sitesdef ant = new AntBuilder()
def webtest_home = System.properties.'webtest.home'
ant.taskdef(resource:'webtest.taskdef') {
classpath {
pathelement(location:"$webtest_home/lib")
fileset(dir:"$webtest_home/lib", includes:"**/*.jar")
}
}
def config_map = [:]
['protocol','host','port','basepath','resultfile',
'resultpath', 'summary', 'saveresponse','defaultpropertytype'].each {
config_map[it] = System.properties['webtest.'+it]
}
ant.testSpec(name:'groovy: Test Groovy Scripting at creation time') {
config(config_map)
steps {
invoke(url:'linkpage.html')
for (i in 1..10) {
verifyText(description:"verify number ${i} is on pages", text:"${i}")
}
}
}
WebTest testing Emails
QCON 2010 - 79
© A
SE
RT
2006-2
010
def ant = new AntBuilder()
def webtest_home = System.properties.'webtest.home'
ant.taskdef(resource:'webtest.taskdef'){
classpath(){
pathelement(location:"$webtest_home/lib")
fileset(dir:"$webtest_home/lib", includes:"**/*.jar")
}
}
ant.testSpec(name:'Email Test'){
steps {
emailSetConfig(server:'localhost', password:'password',
username:'[email protected]', type:'pop3')
emailStoreMessageId(subject:'/Build notification/',
property:'msg')
emailStoreHeader(property:'subject',
messageId:'#{msg}', headerName:'Subject')
groovy('''def subject = step.webtestProperties.subject
assert subject.startsWith('Build notification')''')
emailMessageContentFilter(messageId:'#{msg}')
verifyText(text:'Failed build')
}
}
Spock Testing Framework...
QCON 2010 - 80
© A
SE
RT
2006-2
010
...Spock Testing Framework
QCON 2010 - 81
© A
SE
RT
2006-2
010
• Testing framework for Java and Groovy
• Highly expressive specification language– No assertion API
– No record &
replay
mocking API
– No
superfluous
annotations
– Meaningful
assert error
messages
– Extensible
– Compatible
with JUnit
reportingwise
@Speck@RunWith(Sputnik)class PublisherSubscriberSpeck {def "events are received by all subscribers"() {def pub = new Publisher()def sub1 = Mock(Subscriber)def sub2 = Mock(Subscriber)pub.subscribers << sub1 << sub2
when:pub.send("event")
then:1 * sub1.receive("event")1 * sub2.receive("event")
}}
Spock Example...
QCON 2010 - 82
© A
SE
RT
2006-2
010
import com.gargoylesoftware.htmlunit.WebClientimport 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 should succeed"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 was Spock)"form.getSelectByName('category').getOptions().find { it.text == category }.form.getSelectByName('author').getOptions().find { it.text == author }.setSelectedform.getTextAreaByName('content').setText(content)result = form.getInputByName('btnPost').click()subheadings = result.getElementsByTagName('h3')para = result.getByXPath('//TABLE//TR/TD/P')[0]
...
...Spock Example
QCON 2010 - 83
© A
SE
RT
2006-2
010
...then:page.titleText == 'Welcome to SimpBlog'result.getElementsByTagName('h1').item(0).textContent.matches("Post.*: $author was here.*"subheadings.item(1).textContent == "Category: $category"subheadings.item(2).textContent == "Author: $author"
and:para.textContent == content
where:author << ['Bart', 'Homer', 'Lisa']category << ['Home', 'Work', 'Food']content << ['foo', 'bar', 'baz']
}}
// Optional use of 'and:'
QCON 2010 - 84
© A
SE
RT
2006-2
010
EasyB
• Description: BDD, Rspec-like testing librarynarrative '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'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}
}
EasyB Example ...
• When run will be marked as pending– perfect for ATDD
QCON 2010 - 85
© A
SE
RT
2006-2
010
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"and "I have selected 'Bart' as the author"and "I click the 'Create Post' button"then "I expect the entry to be posted"
}
...EasyB Example...
QCON 2010 - 86
© A
SE
RT
2006-2
010
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", {
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)')}
...
...EasyB Example...
QCON 2010 - 87
© A
SE
RT
2006-2
010
...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(true
}
and "I click the 'Create Post' button", {result = form.getInputByName('btnPost').click()
}
then "I expect the entry to be posted", {// check blog post detailsassert result.getElementsByTagName('h1').item(0).textContent.matches('Post.*: Bart was here.*'def h3headings = result.getElementsByTagName('h3')assert h3headings.item(1).textContent == 'Category: Home' // traditional styleh3headings.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.firstChildassert para.textContent == 'Cowabunga Dude!'// para.shouldHave textContent: 'Cowabunga Dude!'
}}
...EasyB Example...
QCON 2010 - 88
© A
SE
RT
2006-2
010
...EasyB Example
QCON 2010 - 89
© A
SE
RT
2006-2
010
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
Post Blog Entry Feature
for feature
As a Blogger
I want to be able to post a blog
So that I can keep others informed
given we are on the create blog entry page
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
easyb is preparing to process 2 file(s)
Running simp blog initial story (SimpBlogInitialStory.groovy)
Scenarios run: 1, Failures: 0, Pending: 1, Time elapsed: 1.049 sec
Running simp blog story (SimpBlogStory.groovy)
Scenarios run: 1, Failures: 0, Pending: 0, Time elapsed: 1.356 sec
2 total behaviors ran (including 1 pending behavior) with no failures
easyb execution passed
Cucumber• Description
– Loose coupling
between text spec
and step defns
QCON 2010 - 90
© A
SE
RT
2006-2
010
# language: enFeature: AdditionIn order to avoid silly mistakesAs a math idiot I want to be told the sum of two numbers
Scenario Outline: Add two numbersGiven I have entered <input_1> into the calculatorAnd I have entered <input_2> into the calculatorWhen I press <button>Then the stored result should be <output>
Examples:| input_1 | input_2 | button | output || 20 | 30 | add | 50 || 2 | 5 | add | 7 || 0 | 40 | add | 40 |
# language: enFeature: DivisionIn order to avoid silly mistakesCashiers must be able to calculate a fraction
Scenario: Regular numbersGiven I have entered 3 into the calculatorAnd I have entered 2 into the calculatorWhen I press divideThen the stored result should be 1.5
Cucumber Example...
QCON 2010 - 91
© A
SE
RT
2006-2
010
# 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: 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.*"
...Cucumber Example...
QCON 2010 - 92
© A
SE
RT
2006-2
010
...Cucumber Example
QCON 2010 - 93
© A
SE
RT
2006-2
010
import com.gargoylesoftware.htmlunit.WebClientthis.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 ->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)}
Cucumber Data Driven Example...
QCON 2010 - 94
© A
SE
RT
2006-2
010
# language: en@newpostFeature: New Blog PostIn order to create a new blog entryBloggers should be able to select their name and category and enter text
Scenario Outline: New PostingGiven we are on the create blog entry pageWhen I have entered "<title>" as the titleAnd I have entered "<content>" as the contentAnd I have selected "<category>" from the "category" dropdownAnd I have selected "<author>" from the "author" dropdownAnd I click the 'Create Post' buttonThen 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 |
...Cucumber Data Driven Example
QCON 2010 - 95
© A
SE
RT
2006-2
010
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
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 clausesQCON 2010 - 96
© A
SE
RT
2006-2
010
JBehave Example...
QCON 2010 - 97
© A
SE
RT
2006-2
010
Given we are on the create blog entry pageWhen I have entered "Bart was here" as the titleAnd I have entered "Cowabunga Dude!" as the contentAnd I have selected "Home" from the "category" dropdownAnd I have selected "Bart" from the "author" dropdownAnd I click the 'Create Post' buttonThen I should see a heading message matching "Post.*: Bart was here.*"
import org.jbehave.scenario.Scenarioimport 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.*"
...JBehave Example
QCON 2010 - 98
© A
SE
RT
2006-2
010
import org.jbehave.scenario.steps.Stepsimport 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')}
@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
Topics
• Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
• Web Services
• Writing DSLs
• Groovy Testing
Polyglot GroovyOther languages, other styles
• Parallel Processing
• Enterprise Groovy
• More InfoQCON 2010 - 99
© A
SE
RT
2006-2
010
Polyglot Programming...
QCON 2010 - 100
© A
SE
RT
2006-2
010
@Grab('org.clojure:clojure:1.0.0')import clojure.lang.Compilerimport clojure.lang.RT
def src = new File('temp.clj')src.text = '''(ns groovy)(defn factorial [n]
(if (< n 2)1(* n (factorial (- n 1))))
'''
src.withReader { reader ->Compiler.load reader
}
def fac = RT.var('groovy', 'factorial')println fac.invoke(4)
...def jy = getEngine("jython")jy?.eval('''def factorial(n):
i=fact=1while i <= n:
fact=fact*ii=i+1
return fact
result = factorial(4)''')println jy?.result
...Polyglot Programming
• But so what?– I can use Groovy for Scripting my
environment and or leveraging its runners
and other testing capabilities
– I can call out to other languages when needed
• Cucumber via JRuby for more native control
• Watir instead of Watij
• ScalaCheck for test data generation
• Jython for Robot Framework for more native
control
• Rhino for JavaScript testing
• Rules engine integration
QCON 2010 - 101
© A
SE
RT
2006-2
010
Functional Programming in Groovy
• Java functional programming goodness– Normal OO methods
– Ability to have immutable types
– Some concurrency building blocks
• Closures for greater flexibility– Enabler for concurrency
• Lazy evaluation with Closures– Built in to some libraries, e.g. data access
• Metaprogramming to enhance existing
libraries
• Third party Java libraries– Functional Java, FunctionalJ, Commons Functor, Scala
QCON 2010 - 102
© A
SE
RT
2006-2
010
QCON 2010 - 103
© A
SE
RT
2006-2
010
Functional Programming…
def fac(n) { n == 0 ? 1 : n * fac(n - 1) }assert 24 == fac(4)
// define and use infinite streamsdef integers(n) { cons(n, { integers(n+1) }) }def naturalnumbers = integers(1)assert '1 2 3 4 5 6 7 8 9 10' ==
naturalnumbers.take(10).join(' ')def evennumbers =
naturalnumbers.filter{ it % 2 == 0 }assert '2 4 6 8 10 12 14 16 18 20' ==
evennumbers.take(10).join(' ')
QCON 2010 - 104
© A
SE
RT
2006-2
010
…Functional Programming…
import fj.data.Stream
//some metaprogramming to make fj mesh well with GroovyStream.metaClass.filter = { Closure c ->
delegate.filter(c as fj.F) }Stream.metaClass.getAt = { n ->
delegate.index(n) }Stream.metaClass.getAt = { Range r ->
r.collect{ delegate.index(it)}}Stream.metaClass.asList = { ->
delegate.toCollection().asList() }
def evens = Stream.range(0).filter{ it % 2 == 0 }assert evens.take(5).asList() == [0, 2, 4, 6, 8]assert (8..12).collect{ evens[it] } == [16, 18, 20, 22, 24]assert evens[3..6] == [6, 8, 10, 12]
QCON 2010 - 105
© A
SE
RT
2006-2
010
…Functional Programming
import fj.data.Streamimport static fj.data.Stream.cons
// some metaprogramming to make Closures mesh well with// fj functions and productsStream.metaClass.filterTail = { Closure c ->
delegate.tail()._1().filter(c as fj.F) }Stream.metaClass.static.cons = { h, Closure c ->
delegate.cons(h, ['_1':c] as fj.P1) }
def sieve(nums) {cons nums.head(), {
sieve nums.filterTail{ n -> n % nums.head() != 0 }}
}
println sieve(Stream.range(2)).index(100) // => 547
Enabling a functional style …
• Consider the Maximum Segment Sum
(MSS) problem– Take a list of integers; the MSS is the maximum of the sums of
any number of adjacent integers
• Imperative solution:
QCON 2010 - 106
© A
SE
RT
2006-2
010
def numbers = [31,-41,59,26,-53,58,97,-93,-23,84]
def size = numbers.size()def max = null(0..<size).each { from ->(from..<size).each { to ->def sum = numbers[from..to].sum()if (max == null || sum > max) max = sum
}}
println "Maximum Segment Sum of $numbers is $max"
... Enabling a functional style …
• A first attempt at a more functional style:
QCON 2010 - 107
© A
SE
RT
2006-2
010
def numbers = [31,-41,59,26,-53,58,97,-93,-23,84]
def size = numbers.size()def max = [0..<size, 0..<size].combinations().collect{numbers[it[0]..it[1]].sum()
}.max()
println "Maximum Segment Sum of $numbers is $max"
... Enabling a functional style …
• An even more functional style– A known solution using functional composition:
mss = max º sum* º (flatten º tails* º inits)
– Where inits and tails are defined as follows:
QCON 2010 - 108
© A
SE
RT
2006-2
010
assert letters.inits() == [['a'],['a', 'b'],['a', 'b', 'c'],['a', 'b', 'c', 'd']
]
letters = ['a', 'b', 'c', 'd']
assert letters.tails() == [['d'],
['c', 'd'],['b', 'c', 'd'],
['a', 'b', 'c', 'd']]
... Enabling a functional style …
• An even more functional stylemss = max º sum* º (flatten º tails* º inits)
Notes:
– sum() is one-level flatten in Groovy, flatten() is recursive
– Metaprogramming allowed us to enhance all Lists
QCON 2010 - 109
© A
SE
RT
2006-2
010
List.metaClass {inits{ (0..<delegate.size()).collect{ delegate[0..it] } }tails{ delegate.reverse().inits() }
}
def segs = { it.inits()*.tails().sum() }
def solve = { segs(it)*.sum().max() }
def numbers = [31,-41,59,26,-53,58,97,-93,-23,84]
println "Maximum Segment Sum of $numbers is ${solve numbers}"
Source: http://hamletdarcy.blogspot.com/2008/07/groovy-vs-f-showdown-side-by-side.html
...Constraint/Logic Programming...
QCON 2010 - 110
© A
SE
RT
2006-2
010
Source: http://xkcd.com/287/
...Constraint/Logic Programming...
QCON 2010 - 111
© A
SE
RT
2006-2
010
// 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 = ['Mixed fruit' : 215,'French fries' : 275,'Side salad' : 335,'Hot wings' : 355,'Mozzarella sticks' : 420,'Sampler plate' : 580
]def numOrdered = new IntegerVariable[menu.size()]def priceEach = new int[menu.size()]def sum = 1505...
Found a solution:7 * Mixed fruit
Found a solution:1 * Mixed fruit2 * Hot wings1 * Sampler plate
...Constraint/Logic Programming
QCON 2010 - 112
© A
SE
RT
2006-2
010
...menu.eachWithIndex { name, price, i ->
// number ordered >= 0// number ordered * price <= sumnumOrdered[i] = makeIntVar(name, 0, sum.intdiv(price))priceEach[i] = price
}m.addConstraint(eq(scalar(numOrdered, priceEach), sum))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()
}
ModelJUnit...
QCON 2010 - 113
© A
SE
RT
2006-2
010
// require modeljunit.jarimport nz.ac.waikato.modeljunit.coverage.*import nz.ac.waikato.modeljunit.*
class VendingMachineModel implements FsmModel {def state = 0 // 0,25,50,75,100void reset(boolean testing) {state = 0}
boolean vendGuard() {state == 100}@Action void vend() {state = 0}
boolean coin25Guard() {state <= 75}@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()
...ModelJUnit...
QCON 2010 - 114
© A
SE
RT
2006-2
010
...
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)done (0, coin50, 50)done (50, coin50, 100)done (100, vend, 0)done (0, coin25, 25)done (25, coin25, 50)done Random reset(true)done (0, coin50, 50)done (50, coin25, 75)done (75, coin25, 100)done (100, vend, 0)done (0, coin50, 50)done (50, coin25, 75)...
...
Metrics Summary:action coverage: 3/3state coverage: 5/5transition coverage: 7/8transition-pair coverage: 8/12...
...ModelJUnit: SimpBlog Case Study
QCON 2010 - 115
© A
SE
RT
2006-2
010
ScalaCheck
• Description– Tool for testing Scala, Java and Groovy programs
– Based on property specifications and automatic
random test data generation
QCON 2010 - 116
© A
SE
RT
2006-2
010
> scala -classpath ScalaCheck-1.5.jarWelcome to Scala version 2.7.6.final
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")
ScalaCheck: SimpBlog Case Study...
QCON 2010 - 117
© A
SE
RT
2006-2
010
class SimpBlogChecker {static postAndCheck = nullstatic 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.WebClientdef page = new WebClient().getPage('http://localhost:8080/postForm')def form = page.getFormByName('post')form.getInputByName('title').setValueAttribute(title)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).textContentdef h3headings = result.getElementsByTagName('h3')def categoryResult = h3headings.item(1).textContentdef authorResult = h3headings.item(2).textContentdef para = result.getByXPath('//TABLE//TR/TD/P')[0]def contentResult = para.textContentreturn new ResultHolder(titleResult, contentResult, authorResult, categoryResult)''')...
...ScalaCheck: SimpBlog Case Study...
QCON 2010 - 118
© A
SE
RT
2006-2
010
...def binding = new Binding()binding.setVariable('title', title)binding.setVariable('content', clean(content))binding.setVariable('author', author)binding.setVariable('category', category)postAndCheck.binding = bindingpostAndCheck.run()
}}
class ResultHolder {String title, content, author, categoryResultHolder(String title, String content,
String author, String category) {this.title = titlethis.content = contentthis.author = authorthis.category = category
}}
> groovyc SimpBlogChecker.groovy
...ScalaCheck: SimpBlog Case Study...
QCON 2010 - 119
© A
SE
RT
2006-2
010
//CheckSimpBlog.scala
import org.scalacheck.Prop._import org.scalacheck.ConsoleReporter.testStatsEximport org.scalacheck.Test.checkimport org.scalacheck.Arbitrary._import org.scalacheck.Gen._
object CheckSimpBlog {
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
...ScalaCheck: SimpBlog Case Study...
QCON 2010 - 120
© A
SE
RT
2006-2
010
...val enterFieldsAcceptedAndEchoedProperty = forAll(fieldsGen)(
fields =>{
val (title, content, author, category) = fieldsval result = SimpBlogChecker.postAndReturn(title, content, author, category)result.getTitle contains titleresult.getContent contains contentresult.getAuthor contains authorresult.getCategory contains category
})
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.
...ScalaCheck: SimpBlog Case Study
QCON 2010 - 121
© A
SE
RT
2006-2
010
Topics
• Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
• Web Services
• Writing DSLs
• Groovy Testing
• Polyglot Groovy
Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 122
© A
SE
RT
2006-2
010
Concurrent Programming in Groovy• Java concurrent programming goodness
– Normal OO methods
– Ability to have immutable types
– Some concurrency building blocks
• Process Enhancements– AntBuilder and GDK methods
• Closures for greater flexibility– Enabler for concurrency
– Closure is Runnable and Callable
• Third party Java libraries– Functional Java (Actors)
– Cascading.groovy subproject for Hadoop clusters
– Jetlang
– GridGain
– Groovy actorsQCON 2010 - 123
© A
SE
RT
2006-2
010
QCON 2010 - 124
© A
SE
RT
2006-2
010
Fibonacci…
START = 8END = 16fib = {n -> n < 2 ? n : fib(n - 1) + fib(n - 2) }(START..END).each {num ->
println "n:$num => ${fib(num)}"}
QCON 2010 - 125
© A
SE
RT
2006-2
010
…Fibonacci…
import java.util.concurrent.*
CUTOFF = 12 // not worth parallelizing for small nTHREADS = 100
println "Calculating Fibonacci sequence in parallel..."serialFib = {n -> (n < 2) ? n : serialFib(n - 1) + serialFib(n - 2) }pool = Executors.newFixedThreadPool(THREADS)defer = {c -> pool.submit(c as Callable) }fib = {n ->
if (n < CUTOFF) return serialFib(n)def left = defer { fib(n - 1) }def right = defer { fib(n - 2) }left.get() + right.get()
}
(8..16).each {n -> println "n=$n => ${fib(n)}" }pool.shutdown()
gpars (formerly GParallelizer)• http://gpars.codehaus.org/
• Library classes and DSL sugar providing intuitive ways
for Groovy developers to handle tasks concurrently.
Logical parts:
– Actors provide a Groovy implementation of Scala-like
actors including "remote" actors on other machines
– Dataflow Concurrency supports natural shared-memory
concurrency model, using 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
– Safe a non-blocking mt-safe reference to mutable state
that is inspired by "agents" in ClojureQCON 2010 - 126
© A
SE
RT
2006-2
010
gpars/GParallelizer...
QCON 2010 - 127
© A
SE
RT
2006-2
010
// run multiple closures in parallelAsynchronizer.withAsynchronizer {
assert [10, 20] == AsyncInvokerUtil.doInParallel({calculateA()},{calculateB()}
)}
// multiply numbers asynchronouslyParallelizer.withParallelizer(5) {
def result = [1, 2, 3, 4, 5].collectAsync {it * 2}assert result == [2, 4, 6, 8, 10]
}
...gpars/GParallelizer...
QCON 2010 - 128
© A
SE
RT
2006-2
010
// support for dataflow to avoid doing synchronisationimport static org.gparallelizer.dataflow.DataFlow.thread
final def x = new DataFlowVariable()final def y = new DataFlowVariable()final def z = new DataFlowVariable()
thread {z << x.val + y.valprintln "Result: ${z.val}"
}
thread { x << 10 }
thread { y << 5 }
...gpars/GParallelizer...
QCON 2010 - 129
© A
SE
RT
2006-2
010
// actor supportimport static org.gparallelizer.actors.pooledActors.PooledActors.*
def me = actor {friend.send('Hi')react(10.seconds) {
// continue conversation}
}me.metaClass.onTimeout = {->friend.send('I see, busy as usual. Never mind.')}me.start()
// safe supportimport org.gparallelizer.actors.pooledActors.SafeVariable
def jugMembers = new SafeVariable<List>(['Me']) //add Me
jugMembers.send {it.add 'James'} //add James
final Thread t1 = Thread.start {
jugMembers.send {it.add 'Joe'} //add Joe
}
final Thread t2 = Thread.start {
jugMembers << {it.add 'Dave'} //add Dave
jugMembers << {it.add 'Alice'} //add Alice
}
[t1, t2]*.join()
println jugMembers.val
jugMembers.valAsync {println "Current members: $it"}
System.in.read()
jugMembers.stop()
...gpars/GParallelizer
QCON 2010 - 130
© A
SE
RT
2006-2
010
gpars for testing
QCON 2010 - 131
© A
SE
RT
2006-2
010
@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'],['Food', 'Lisa', 'Content 4']
]
withParallelizer(3) {testCases.eachAsync{ category, author, content ->
postAndCheck category, author, content}
}
private postAndCheck(category, author, content) {...
Topics
• Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
• Web Services
• Writing DSLs
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
Enterprise Groovy
• More Info
QCON 2010 - 132
© A
SE
RT
2006-2
010
Integration with Spring...
QCON 2010 - 133
© A
SE
RT
2006-2
010
// traditional approach using a beans xml file
import org.springframework.context.support.ClassPathXmlApplicationContext
def ctx = new ClassPathXmlApplicationContext('calcbeans.xml')
def calc = ctx.getBean('calcBean')
println calc.doAdd(3, 4) // => 7
// using BeanBuilder
def bb = new grails.spring.BeanBuilder()
bb.beans {
adder(AdderImpl)
calcBean(CalcImpl2) { adder = adder }
}
def ctx = bb.createApplicationContext()
def calc = ctx.getBean('calcBean')
println calc.doAdd(3, 4) // => 7
...Integration with Spring
QCON 2010 - 134
© A
SE
RT
2006-2
010
// using annotations
import org.springframework.stereotype.Component
@Component class AdderImpl {
def add(x, y) { x + y }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
@Component class CalcImpl3 {
@Autowired private AdderImpl adder
def doAdd(x, y) { adder.add(x, y) }
}
import org.springframework.context.support.GenericApplicationContext
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner
def ctx = new GenericApplicationContext()
new ClassPathBeanDefinitionScanner(ctx).scan('') // scan root package for components
ctx.refresh()
def calc = ctx.getBean('calcImpl3')
println calc.doAdd(3, 4) // => 7
Dependency Injection: Guice
QCON 2010 - 135
© A
SE
RT
2006-2
010
import com.google.inject.*
@ImplementedBy(CalculatorImpl)interface Calculator {
def add(a, b)}
@Singletonclass CalculatorImpl implements Calculator {
private total = 0def add(a, b) { total++; a + b }def getTotalCalculations() { 'Total Calculations: ' + total }String toString() { 'Calc: ' + hashCode()}
}
class Client {@Inject Calculator calc// ...
}
def injector = Guice.createInjector()// ...
Topics
• Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
• Web Services
• Writing DSLs
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
More Info
QCON 2010 - 136
© A
SE
RT
2006-2
010
More Information: on the web
• 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)
• Mailing list for users– [email protected]
• Information portals– http://www.aboutgroovy.org
– http://www.groovyblogs.org
• Documentation (1000+ pages)– Getting Started Guide, User Guide, Developer Guide, Testing
Guide, Cookbook Examples, Advanced Usage Guide
• Books– Several to choose from ...
QCON 2010 - 137
© A
SE
RT
2006-2
010
...More Information
138
© A
SE
RT
2006-2
010