Contenu connexe
Similaire à Paulking groovy
Similaire à Paulking groovy (20)
Paulking groovy
- 1. © ASERT 2006-2010
Groovy Power Features!
Dr Paul King
paulk@asert.com.au
@paulk_asert
ASERT, Australia
- 2. Topics
Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
• Web Services
• Writing DSLs
© ASERT 2006-2010
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 2
- 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 = Java – boiler plate code
+ mostly dynamic typing
+ closures
+ domain specific languages
+ builders
+ metaprogramming
+ GDK library
QCON 2010 - 3
- 4. Groovy Goodies Overview
• Fully object oriented
• Closures: reusable
and assignable
pieces of code
• Operators can be • GPath: efficient
overloaded
© ASERT 2006-2010
object navigation
• Multimethods • GroovyBeans
• Literal declaration for • grep and switch
lists (arrays), maps,
ranges and regular • Templates, builder,
expressions swing, Ant, markup,
XML, SQL, XML-RPC,
Scriptom, Grails,
tests, Mocks QCON 2010 - 4
- 5. Growing Acceptance …
A slow and steady start but now gaining in
momentum, maturity and mindshare
Now free
- 7. … Growing Acceptance …
© ASERT 2006-2010
Groovy and Grails downloads:
70-90K per month and growing QCON 2010 - 7
- 8. … Growing Acceptance …
© ASERT 2006-2010
Source: http://www.micropoll.com/akira/mpresult/501697-116746
Source: http://www.grailspodcast.com/
QCON 2010 - 8
- 9. … Growing Acceptance …
© ASERT 2006-2010
http://www.jroller.com/scolebourne/entry/devoxx_2008_whiteboard_votes
http://www.java.net
QCON 2010 - 9
- 10. … Growing Acceptance …
What alternative JVM language are you using or intending to use
© ASERT 2006-2010
http://www.leonardoborges.com/writings
QCON 2010 - 10
- 11. … Growing Acceptance …
© ASERT 2006-2010
http://it-republik.de/jaxenter/quickvote/results/1/poll/44 (translated using http://babelfish.yahoo.com)
QCON 2010 - 11
- 12. … Growing Acceptance …
© ASERT 2006-2010
http://pollpigeon.com/jsf-grails-wicket/r/25665/
QCON 2010 - 12
- 14. The Landscape of JVM Languages
mostly
dynamic
typing
© ASERT 2006-2010
Dynamic features call
for dynamic types Java bytecode calls
for static types
The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ platform.
QCON 2010 - 14
- 15. Groovy Starter
System.out.println("Hello, World!"); // optional semicolon,
println 'Hello, World!' // System.out, brackets,
// main() method, class defn
def name = 'Guillaume' // dynamic typing
println "$name, I'll get the car." // GString
String longer = """${name}, the car
is in the next row.""" // multi-line string
© ASERT 2006-2010
// with static typing
assert 0.5 == 1/2 // BigDecimal equals()
def printSize(obj) { // optional 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 - 15
- 16. 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");
System.out.println(names);
Based on an
Erase e = new Erase(); example by
List shortNames = e.removeLongerThan(names, 3); Jim Weirich
System.out.println(shortNames.size());
for (int i = 0; i < shortNames.size(); i++) { & Ted Leung
String s = (String) shortNames.get(i);
System.out.println(s);
}
}
}
QCON 2010 - 16
- 17. ...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
- 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)
}
}
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
- 19. ...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
- 20. ...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
- 21. ...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
- 22. ...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
- 23. ...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
- 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
QCON 2010 - 24
- 25. ...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
- 26. Topics
• Groovy Intro
Leveraging the language
• Use and abuse of Design Patterns
• Web Services
• Writing DSLs
© ASERT 2006-2010
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 26
- 27. Better Control Structures: Switch
switch (10) {
case 0 : assert false ; break
case 0..9 : assert false ; break
case [8,9,11] : assert false ; break
case Float : assert false ; break
case {it%3 == 0} : assert false ; break
case ~/../ : assert true ; break
© ASERT 2006-2010
default : assert false ; break
}
• Extensible
– Implement your own isCase() method
QCON 2010 - 27
- 28. Better Control Structures: Switch – Custom isCase
WARNING:
enum Color { Advanced
yellow, orange, purple Topic!
def isCase(thing) { thing.color == this }
}
import static Color.*
class Banana { def color = yellow }
class Orange { def color = orange }
class Grape { def color = purple }
© ASERT 2006-2010
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 yellow
Found something orange
Unknown color
QCON 2010 - 28
- 29. 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) {
© ASERT 2006-2010
switch (f.color) {
case [yellow, orange]:
return "Found something $f.color"
default: return 'Unknown color'
}
}
Found something yellow
Found something orange
Unknown color
QCON 2010 - 29
- 30. Better Control Structures: Switch – Custom isCase
enum Color { WARNING:
yellow, orange, purple Advanced Topic!
def isCase(thing) {
thing?.hasProperty('color') && thing.color == this }
}
enum State {
peeled, unpeeled
def isCase(thing) {
try {
© ASERT 2006-2010
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 }
...
QCON 2010 - 30
- 31. Better Control Structures: Switch – Custom isCase
WARNING:
...
Advanced Topic!
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) {
© ASERT 2006-2010
case yellowAndPeeled:
return 'Possibly found a banana split'
case yellow:
return 'Found something yellow'
case peeled:
return 'Found something peeled'
default:
return 'No comment'
} Possibly found a banana split
} No comment
Found something peeled
QCON 2010 - 31
- 32. Better Control Structures: Switch Poker…
hand1 hand2
8C TS KC 9H 4S 7D 2S 5D 3S AC
suits = 'SHDC'
ranks = '23456789TJQKA'
suit = { String card -> suits.indexOf(card[1]) }
rank = { String card -> ranks.indexOf(card[0]) }
rankSizes = { List cards ->
© ASERT 2006-2010
cards.groupBy(rank).collect{ k, v -> v.size() }.sort() }
rankValues = { List cards ->
cards.collect{ rank(it) }.sort() }
// ...
println rankSizes(["7S", "7H", "2H", "7D", "AH"])
// => [1, 1, 3]
QCON 2010 - 32
- 33. …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) }
© ASERT 2006-2010
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 - 33
- 34. … Better Control Structures: Switch Poker
// ...
def rankHand(List cards) {
switch (cards) {
case straightFlush : return 9
case fourOfAKind : return 8
case fullHouse : return 7
case flush : return 6
© ASERT 2006-2010
case straight : return 5
case threeOfAKind : return 4
case twoPair : return 3
case pair : return 2
default : return 1
}
}
// ...
QCON 2010 - 34
- 35. Topics
• Groovy Intro
• Leveraging the language
Use and abuse of Design Patterns
• Web Services
• Writing DSLs
© ASERT 2006-2010
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 35
- 36. Grapes / Grab
// Google Collections example
import com.google.common.collect.HashBiMap
@Grab(group='com.google.collections',
module='google-collections',
version='1.0-rc2')
© ASERT 2006-2010
def getFruit() {
[ grape:'purple',
lemon:'yellow',
orange:'orange' ] as HashBiMap
}
assert fruit.lemon == 'yellow'
assert fruit.inverse().yellow == 'lemon'
QCON 2010 - 36
- 37. 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 - 37
- 38. ...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 - 38
- 40. Better Design Patterns: Singleton
class Calculator {
def total = 0
def add(a, b) { total++; a + b }
}
def INSTANCE = new Calculator()
Calculator.metaClass.constructor = { -> INSTANCE }
© ASERT 2006-2010
def c1 = new Calculator()
def c2 = new Calculator()
@Singleton(lazy=true)
assert c1.add(1, 2) == 3 class X {
assert c2.add(3, 4) == 7 def getHello () {
"Hello, World!"
assert c1.is(c2) }
assert [c1, c2].total == [2, 2] }
println X.instance.hello
QCON 2010 - 40
- 41. Better Design Patterns: Delegate…
public Date getWhen() {
import java.util.Date;
return when;
}
public class Event {
private String title;
public void setWhen(Date when) {
private String url;
this.when = when;
private Date when;
}
public String getUrl() {
public boolean before(Date other) {
return url;
return when.before(other);
}
© ASERT 2006-2010
}
public void setUrl(String url) {
public void setTime(long time) {
this.url = url;
when.setTime(time);
}
}
public String getTitle() {
public long getTime() {
return title;
return when.getTime();
}
}
public void setTitle(String title) {
public boolean after(Date other) {
this.title = title;
return when.after(other);
}
}
// ...
// ...
QCON 2010 - 41
- 42. …Better Design Patterns: Delegate…
public Date getWhen() {
import java.util.Date;
return when;
boilerplate }
public class Event {
private String title;
public void setWhen(Date when) {
private String url;
this.when = when;
private Date when;
}
public String getUrl() {
public boolean before(Date other) {
return url;
return when.before(other);
}
© ASERT 2006-2010
}
public void setUrl(String url) {
public void setTime(long time) {
this.url = url;
when.setTime(time);
}
}
public String getTitle() {
public long getTime() {
return title;
return when.getTime();
}
}
public void setTitle(String title) {
public boolean after(Date other) {
this.title = title;
return when.after(other);
}
}
// ...
// ...
QCON 2010 - 42
- 43. …Better Design Patterns: Delegate
class Event {
String title, url
@Delegate Date when
}
© ASERT 2006-2010
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)
QCON 2010 - 43
- 44. Topics
• Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
Web Services
• Writing DSLs
© ASERT 2006-2010
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 44
- 45. SOAP Client and Server
class MathService {
double add(double a, double b) {
a + b
}
double square(double c) {
c * c
}
}
© ASERT 2006-2010
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
QCON 2010 - 45
- 46. Better Testing: SoapUI
• Tool for testing Web Services has a built-
in Groovy editor for custom steps
© ASERT 2006-2010
QCON 2010 - 46
- 47. Topics
• Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
• Web Services
Writing DSLs
© ASERT 2006-2010
• Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 47
- 48. Groovy DSL Features
• Literal Syntax Conventions
• Scripts as well as Classes & Optional Syntax
• Optional Typing & Named Arguments
• Type Transformations
• Using With
© ASERT 2006-2010
• Operator Overloading
• Closures
• Runtime Metaprogramming
• Compile-time Metaprogramming (AST Macros)
• Builders
QCON 2010 - 48
- 49. Literal Syntax Conventions
• Lists
– Special syntax for list literals
– Additional common methods (operator overloading)
def list = [3, new Date(), 'Jan']
assert list + list == list * 2
• Maps
© ASERT 2006-2010
– Special syntax for map literals
– Additional common methods
def map = [a: 1, b: 2]
assert map['a'] == 1 && map.b == 2
• Ranges
– Special syntax for various kinds of ranges
def letters = 'a'..'z'
def numbers = 0..<10
QCON 2010 - 49
- 50. Scripts as well as Classes & Optional Syntax
• Java
public class HelloJava {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
© ASERT 2006-2010
• Groovy
class HelloGroovy {
static main(String[] args) {
System.out.println("Hello World");
}
}
System.out.println('Hello World');
println 'Hello World'
QCON 2010 - 50
- 51. Optional Typing & Named Params
• Java
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"));
© ASERT 2006-2010
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.first
• Groovy
Last name: $args.last"""
}
printDetails first:'John', last:'Smith'
QCON 2010 - 51
- 52. 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
© ASERT 2006-2010
around, combined in structured ways to form more
complex algorithms and data
doubleNum = { num -> num * 2 }
println doubleNum(3) // => 6
processThenPrint = { num, closure ->
num = closure(num); println "num is $num"
}
processThenPrint(3, doubleNum) // => num is 6
processThenPrint(10) { it / 2 } // => num is 5
QCON 2010 - 52
- 53. Static Imports
import groovy.swing.SwingXBuilder
© ASERT 2006-2010
import 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()
QCON 2010 - 53
- 54. Using '.with' Example
letters = ['a', 'b', 'c']
range = 'b'..'d'
letters.with {
add 'd'
remove 'a'
}
assert letters == range
© ASERT 2006-2010
map = [a:10, b:4, c:7]
map.with {
assert (a + b) / c == 2
}
QCON 2010 - 54
- 55. Coin example...
enum Coin {
penny(1), nickel(5), dime(10), quarter(25)
Coin(int value) { this.value = value }
int value
}
© ASERT 2006-2010
import static Coin.*
assert 2 * quarter.value +
1 * nickel.value +
2 * penny.value == 57
QCON 2010 - 55
- 56. ...Coin example...
class CoinMath {
static multiply(Integer self, Coin c) {
self * c.value
}
}
use (CoinMath) {
© ASERT 2006-2010
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
QCON 2010 - 56
- 57. ...Coin example
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) {
© ASERT 2006-2010
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 QCON 2010 - 57
- 58. Game example...
// 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) {
© ASERT 2006-2010
if (index == things.size() - 1) result += ' and '
else if (index < things.size() - 1) result += ', '
}
result += "${thing.toLowerCase()[0] in VOWELS ? 'an' : 'a'} $thin
}
result ?: 'nothing'
}
}
import static GameUtils.*
...
QCON 2010 - 58
- 59. ...Game example...
...
class Room {
def description
def contents = []
}
...
© ASERT 2006-2010
QCON 2010 - 59
- 60. ...Game example...
...
class Player {
def currentRoom
def inventory = []
void look() {
println "You are in ${currentRoom?.description?:'the void'} which contains ${listItems(currentRoom?.contents)}
}
void inv() {
© ASERT 2006-2010
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"
}
}
QCON 2010 - 60
- 61. ...Game example...
...
void drop(item) {
if (inventory?.remove(item)) {
currentRoom?.contents << item
println "You dropped the $item"
} else {
println "You don't have the $item"
}
© ASERT 2006-2010
}
def propertyMissing(String name) {
if (metaClass.respondsTo(this, name)) {
this."$name"()
}
name
}
}
...
QCON 2010 - 61
- 62. ...
...Game example...
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
© ASERT 2006-2010
look
take emerald
inv
look
take key
drop emerald
inv
look
}
assert player.inventory == ['dagger', 'key'] QCON 2010 - 62
- 63. ...Game example
...
// now try some error conditions
plainRoom.description = null
player.with {
drop gold
take gold
drop emerald
© ASERT 2006-2010
take emerald
take emerald
look
}
QCON 2010 - 63
- 64. Grails Criteria
// 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")
© ASERT 2006-2010
}
maxResults(10)
order("holderLastName", "desc")
}
// source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.2 Criteria
QCON 2010 - 64
- 65. Grails Criteria Example
// Book is a POJO in our domain/model
def book = Book.findByTitle("The Stand")
book = Book.findByTitleLike("Harry Pot%")
book = Book.findByReleaseDateBetween( firstDate, secondDate )
© ASERT 2006-2010
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
QCON 2010 - 65
- 66. Grails Bean Builder Example
bb.beans {
marge(Person) {
name = "marge"
husband = { Person p ->
name = "homer"
age = 45
props = [overweight:true, height:"1.8m"]
}
children = [bart, lisa]
© ASERT 2006-2010
}
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 QCON 2010 - 66
- 67. Groovy Lab Example
// 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
© ASERT 2006-2010
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))
QCON 2010 - 67
- 68. Operator Overloading Example
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
© ASERT 2006-2010
QCON 2010 - 68
- 69. Type Transformation Example
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) {
© ASERT 2006-2010
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() }
QCON 2010 - 69
- 70. AST Builder
• Numerous approaches, still evolving.
“From code” approach:
def result = new AstBuilder().buildFromCode {
println "Hello World"
}
• Produces:
BlockStatement
-> ReturnStatement
-> MethodCallExpression
-> VariableExpression("this")
-> ConstantExpression("println")
-> ArgumentListExpression
-> ConstantExpression("Hello World")
- 71. Topics
• Groovy Intro
• Leveraging the language
• Use and abuse of Design Patterns
• Web Services
• Writing DSLs
© ASERT 2006-2010
Groovy Testing
• Polyglot Groovy
• Parallel Processing
• Enterprise Groovy
• More Info
QCON 2010 - 71
- 72. Types of Testing
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 - 72
- 73. 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 - 73
- 74. Groovy and Testing Tool Spectrum*
Utilities Runners
AllPairs, Combinations Native Groovy, JUnit, TestNG, Spock, EasyB,
Polyglot languages JBehave, Cucumber, Robot Framework
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
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 - 74
- 75. 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 - 75
- 76. 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 - 76
- 77. ...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 - 77
- 78. WebTest testing Web Sites
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")
}
}
© ASERT 2006-2010
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}")
}
}
}
QCON 2010 - 78
- 79. WebTest testing Emails
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")
}
}
© ASERT 2006-2010
ant.testSpec(name:'Email Test'){
steps {
emailSetConfig(server:'localhost', password:'password',
username:'devteam@mycompany.org', 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')
}
}
QCON 2010 - 79
- 81. ...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 - 81
- 82. 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 - 82
- 83. ...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 - 83
- 84. 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 - 84
- 85. 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 - 85
- 86. ...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 - 86
- 87. ...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 - 87
- 89. ...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 - 89
- 90. 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 - 90
- 91. 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 - 91
- 93. ...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 - 93
- 94. 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 - 94
- 96. 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 - 96