Share
Embed
What makes Groovy groovy?
JAX 2014 presentation on the cool aspects of what makes Groovy a pretty groovy language to use
Guillaume Laforge
May 15, 2014
Transcript
265 slides
-
© 2014 Guillaume Laforge. All rights reserved. Do not distribute
without permission. Guillaume Laforge @glaforge What makes Groovy groovy? -
@glaforge Pivotal related talks • Wednesday – 5:15pm - 6:15pm:
Spring XD: Tackling Big Data Complexity – 7:30pm - 8:00pm: JAX Innovation Awards - Spring Boot is nominated! • Thursday – 8:30am - 9:45am: Going reactive: High Performance JVM Code with Reactor – 10:30am -11:30am: Functional Groovy – 10:30am - 11:30am: Spring Data Solr – 2:15pm - 3:15pm: Advanced Web Development Techniques with Grails 2 – 2:15pm - 3:15pm: Making the Eclipse IDE fun again • Friday – 9:00am - 5:00pm: From the database into the web - End-to-end REST web services with Spring 4 -
Groovy… But also supports static type checking &
static compilation -
class MathSpec extends Specification { def
"maximum of two numbers"() { expect: Math.max(a, b) == c ! where: a | b || c 1 | 3 || 3 7 | 4 || 7 0 | 0 || 0 } } -
new MarkupBuilder().html { head {
title "The Script Bowl" } ! body { div(class: "banner") { p "Groovy rocks!" } } } -
@RestController class App { @RequestMapping("/")
String home() { "Hello World!" } } Speaking of conciseness... A full Spring app in the span of a tweet! -
Great for scripting Fit for Domain- Specific Languages Most seamless
integration & interoperability wih java! -
Great for scripting Fit for Domain- Specific Languages Most seamless
integration & interoperability wih java! Full-blown reactive applications too! -
Any Java developer is a Groovy developer! Most Java code
is also valid Groovy code! -
@glaforge Scripts versus Classes 22 public class Main {
public static void main(String[] args) { System.out.println("Hello"); } } vs -
@glaforge Scripts versus Classes 22 public class Main {
public static void main(String[] args) { System.out.println("Hello"); } } println "Hello" vs -
@glaforge Optional... 24 public class Greeter {
private String owner; ! public String getOwner() { return owner; } ! public void setOwner(String owner) { this.owner = owner; } ! public String greet(String name) { return "Hello " + name + ", I am " + owner; } } ! Greeter greeter = new Greeter(); greeter.setOwner("Guillaume"); ! System.out.println(greeter.greet("Marion")); -
@glaforge Optional... 24 public class Greeter {
private String owner; ! public String getOwner() { return owner; } ! public void setOwner(String owner) { this.owner = owner; } ! public String greet(String name) { return "Hello " + name + ", I am " + owner; } } ! Greeter greeter = new Greeter(); greeter.setOwner("Guillaume"); ! System.out.println(greeter.greet("Marion")); Semicolons -
@glaforge Optional... 25 public class Greeter {
private String owner ! public String getOwner() { return owner } ! public void setOwner(String owner) { this.owner = owner } ! public String greet(String name) { return "Hello " + name + ", I am " + owner } } ! Greeter greeter = new Greeter() greeter.setOwner("Guillaume") ! System.out.println(greeter.greet("Marion")) -
@glaforge Optional... 26 public class Greeter {
private String owner ! public String getOwner() { return owner } ! public void setOwner(String owner) { this.owner = owner } ! public String greet(String name) { return "Hello " + name + ", I am " + owner } } ! Greeter greeter = new Greeter() greeter.setOwner("Guillaume") ! System.out.println(greeter.greet("Marion")) -
@glaforge Optional... 26 public class Greeter {
private String owner ! public String getOwner() { return owner } ! public void setOwner(String owner) { this.owner = owner } ! public String greet(String name) { return "Hello " + name + ", I am " + owner } } ! Greeter greeter = new Greeter() greeter.setOwner("Guillaume") ! System.out.println(greeter.greet("Marion")) Parentheses -
@glaforge Optional... 27 public class Greeter {
private String owner ! public String getOwner() { return owner } ! public void setOwner(String owner) { this.owner = owner } ! public String greet(String name) { return "Hello " + name + ", I am " + owner } } ! Greeter greeter = new Greeter() greeter.setOwner "Guillaume" ! System.out.println greeter.greet("Marion") -
@glaforge Optional... 27 public class Greeter {
private String owner ! public String getOwner() { return owner } ! public void setOwner(String owner) { this.owner = owner } ! public String greet(String name) { return "Hello " + name + ", I am " + owner } } ! Greeter greeter = new Greeter() greeter.setOwner "Guillaume" ! System.out.println greeter.greet("Marion") return keyword -
@glaforge Optional... 28 public class Greeter {
private String owner ! public String getOwner() { owner } ! public void setOwner(String owner) { this.owner = owner } ! public String greet(String name) { "Hello " + name + ", I am " + owner } } ! Greeter greeter = new Greeter() greeter.setOwner "Guillaume" ! System.out.println greeter.greet("Marion") -
@glaforge Optional... 28 public class Greeter {
private String owner ! public String getOwner() { owner } ! public void setOwner(String owner) { this.owner = owner } ! public String greet(String name) { "Hello " + name + ", I am " + owner } } ! Greeter greeter = new Greeter() greeter.setOwner "Guillaume" ! System.out.println greeter.greet("Marion") public keyword -
@glaforge Optional... 29 class
Greeter { private String owner ! String getOwner() { owner } ! void setOwner(String owner) { this.owner = owner } ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! Greeter greeter = new Greeter() greeter.setOwner "Guillaume" ! System.out.println greeter.greet("Marion") -
@glaforge Optional... 29 class
Greeter { private String owner ! String getOwner() { owner } ! void setOwner(String owner) { this.owner = owner } ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! Greeter greeter = new Greeter() greeter.setOwner "Guillaume" ! System.out.println greeter.greet("Marion") optional typing -
@glaforge Optional... 30 class
Greeter { private String owner ! String getOwner() { owner } ! void setOwner(String owner) { this.owner = owner } ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! def greeter = new Greeter() greeter.setOwner "Guillaume" ! System.out.println greeter.greet("Marion") -
@glaforge Optional... 30 class
Greeter { private String owner ! String getOwner() { owner } ! void setOwner(String owner) { this.owner = owner } ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! def greeter = new Greeter() greeter.setOwner "Guillaume" ! System.out.println greeter.greet("Marion") handy println shortcut -
@glaforge Optional... 31 class
Greeter { private String owner ! String getOwner() { owner } ! void setOwner(String owner) { this.owner = owner } ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! def greeter = new Greeter() greeter.setOwner "Guillaume" ! println greeter.greet("Marion") -
@glaforge Optional... 31 class
Greeter { private String owner ! String getOwner() { owner } ! void setOwner(String owner) { this.owner = owner } ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! def greeter = new Greeter() greeter.setOwner "Guillaume" ! println greeter.greet("Marion") verbose Java properties! -
@glaforge Optional... 32 class
Greeter { String owner ! ! ! ! ! ! ! ! ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! def greeter = new Greeter() greeter.setOwner "Guillaume" ! println greeter.greet("Marion") -
@glaforge Optional... 32 class
Greeter { String owner ! ! ! ! ! ! ! ! ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! def greeter = new Greeter() greeter.setOwner "Guillaume" ! println greeter.greet("Marion") Property notation -
@glaforge Optional... 33 class
Greeter { String owner ! ! ! ! ! ! ! ! ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! def greeter = new Greeter() greeter.owner = "Guillaume" ! println greeter.greet("Marion") -
@glaforge Optional... 33 class
Greeter { String owner ! ! ! ! ! ! ! ! ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! def greeter = new Greeter() greeter.owner = "Guillaume" ! println greeter.greet("Marion") Named argument constructor -
@glaforge Optional... 34 class
Greeter { String owner ! ! ! ! ! ! ! ! ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! def greeter = new Greeter(owner: "Guillaume") ! ! println greeter.greet("Marion") -
@glaforge Optional... 34 class
Greeter { String owner ! ! ! ! ! ! ! ! ! String greet(String name) { "Hello " + name + ", I am " + owner } } ! def greeter = new Greeter(owner: "Guillaume") ! ! println greeter.greet("Marion") Interpolated strings! (aka GStrings) -
@glaforge Optional... 35 class
Greeter { String owner ! ! ! ! ! ! ! ! ! String greet(String name) { "Hello ${name}, I am ${owner}" } } ! def greeter = new Greeter(owner: "Guillaume") ! ! println greeter.greet("Marion") -
@glaforge Optional... 35 class
Greeter { String owner ! ! ! ! ! ! ! ! ! String greet(String name) { "Hello ${name}, I am ${owner}" } } ! def greeter = new Greeter(owner: "Guillaume") ! ! println greeter.greet("Marion") Let’s reformat that mess of whitespace! -
@glaforge Optional... 36 class Greeter {
String owner ! String greet(String name) { "Hello ${name}, I am ${owner}" } } ! def greeter = new Greeter(owner: "Guillaume") ! println greeter.greet("Marion") -
@glaforge Optional... 36 class Greeter {
String owner ! String greet(String name) { "Hello ${name}, I am ${owner}" } } ! def greeter = new Greeter(owner: "Guillaume") ! println greeter.greet("Marion") public class Greeter { private String owner; ! public String getOwner() { return owner; } ! public void setOwner(String owner) { this.owner = owner; } ! public String greet(String name) { return "Hello " + name + ", I am " + owner; } } ! Greeter greeter = new Greeter(); greeter.setOwner("Guillaume"); ! System.out.println(greeter.greet("Marion")); -
@glaforge Optional... 36 class Greeter {
String owner ! String greet(String name) { "Hello ${name}, I am ${owner}" } } ! def greeter = new Greeter(owner: "Guillaume") ! println greeter.greet("Marion") -
@glaforge Native syntax constructs 37 // closures def adder
= { a, b -‐> a + b } ! // lists def list = [1, 2, 3, 4, 5] ! // maps def map = [a: 1, b: 2, c: 3] ! // regular expressions def regex = ~/.*foo.*/ ! // ranges def range 128..255 -
@glaforge Closures — the basics • Functions as first-class citizen
of the language 38 def adder = { a, b -‐> a + b } ! assert adder(1, 2) == 3 assert adder('a', 'b') == 'ab' -
@glaforge Closures — the basics • Functions as first-class citizen
of the language 38 def adder = { a, b -‐> a + b } ! assert adder(1, 2) == 3 assert adder('a', 'b') == 'ab' Closure parameters -
@glaforge Closures — the basics • Functions as first-class citizen
of the language 38 def adder = { a, b -‐> a + b } ! assert adder(1, 2) == 3 assert adder('a', 'b') == 'ab' Assign a function into a variable Closure parameters -
@glaforge Closures — the basics • Functions as first-class citizen
of the language 38 def adder = { a, b -‐> a + b } ! assert adder(1, 2) == 3 assert adder('a', 'b') == 'ab' Short form of: adder.call(‘a’, ‘b’) Assign a function into a variable Closure parameters -
@glaforge Closures — the basics • Functions as first-class citizen
of the language 38 def adder = { a, b -‐> a + b } ! assert adder(1, 2) == 3 assert adder('a', 'b') == 'ab' Short form of: adder.call(‘a’, ‘b’) Genericity with duck typing & operator overloading Assign a function into a variable Closure parameters -
@glaforge Closures — explicit type 39 ! def intAdder =
{ int a, int b -‐> a + b } -
@glaforge Closures — explicit type 39 ! def intAdder =
{ int a, int b -‐> a + b } Be explicit about the types -
@glaforge Closures — implicit parameter 40 def doubler = {
it * 2 } ! assert doubler(3) == 6 assert doubler('a') == 'aa' -
@glaforge Closures — implicit parameter 40 def doubler = {
it * 2 } ! assert doubler(3) == 6 assert doubler('a') == 'aa' Implicit parameter -
@glaforge Closures — implicit parameter 40 def doubler = {
it * 2 } ! assert doubler(3) == 6 assert doubler('a') == 'aa' Implicit parameter Multiply also defined on strings -
@glaforge Closures — variable arguments 41 def sum = {
... elements -‐> elements.sum() } ! assert sum(1, 2) == 3 assert sum('a', 'b', 'c') == 'abc' -
@glaforge Closures — variable arguments 41 def sum = {
... elements -‐> elements.sum() } ! assert sum(1, 2) == 3 assert sum('a', 'b', 'c') == 'abc' Variable number of arguments -
@glaforge Closures — variable arguments 41 def sum = {
... elements -‐> elements.sum() } ! assert sum(1, 2) == 3 assert sum('a', 'b', 'c') == 'abc' You can specify the type: int... Variable number of arguments -
@glaforge Closures — default values 42 def mult = {
int a, int b = 10 -‐> a * b } ! assert mult(2, 3) == 6 assert mult(5) == 50 -
@glaforge Closures — default values 42 def mult = {
int a, int b = 10 -‐> a * b } ! assert mult(2, 3) == 6 assert mult(5) == 50 Default value -
@glaforge Closures — default values 42 def mult = {
int a, int b = 10 -‐> a * b } ! assert mult(2, 3) == 6 assert mult(5) == 50 Default value Provided value for b -
@glaforge Closures — default values 42 def mult = {
int a, int b = 10 -‐> a * b } ! assert mult(2, 3) == 6 assert mult(5) == 50 Default value Provided value for b Default value used for b -
@glaforge Closures — methods as functions 43 def logBase10 =
Math.&log10 def printer = System.out.&println ! assert logBase10(10) == 1 printer 'abc' -
@glaforge Closures — methods as functions 43 def logBase10 =
Math.&log10 def printer = System.out.&println ! assert logBase10(10) == 1 printer 'abc' Turn a method into a closure function -
@glaforge Closures — map / filter / reduce 44 @groovy.transform.Immutable
class Person { String name int age } -
@glaforge Closures — map / filter / reduce 44 @groovy.transform.Immutable
class Person { String name int age } -
@glaforge Closures — map / filter / reduce 44 @groovy.transform.Immutable
class Person { String name int age } def persons = [ new Person('Guillaume', 36), new Person('Marion', 6), new Person('Erine', 1) ] -
@glaforge Closures — map / filter / reduce 44 @groovy.transform.Immutable
class Person { String name int age } def persons = [ new Person('Guillaume', 36), new Person('Marion', 6), new Person('Erine', 1) ] -
@glaforge Closures — map / filter / reduce 44 @groovy.transform.Immutable
class Person { String name int age } def persons = [ new Person('Guillaume', 36), new Person('Marion', 6), new Person('Erine', 1) ] def names = persons.findAll { it.age < 18 } .collect { it.name.toUpperCase() } .sort() .join(', ') -
@glaforge Closures — map / filter / reduce 44 @groovy.transform.Immutable
class Person { String name int age } def persons = [ new Person('Guillaume', 36), new Person('Marion', 6), new Person('Erine', 1) ] def names = persons.findAll { it.age < 18 } .collect { it.name.toUpperCase() } .sort() .join(', ') -
@glaforge Closures — map / filter / reduce 44 @groovy.transform.Immutable
class Person { String name int age } def persons = [ new Person('Guillaume', 36), new Person('Marion', 6), new Person('Erine', 1) ] def names = persons.findAll { it.age < 18 } .collect { it.name.toUpperCase() } .sort() .join(', ') assert names == "ERINE, MARION" -
@glaforge Closures — map / filter / reduce 44 @groovy.transform.Immutable
class Person { String name int age } def persons = [ new Person('Guillaume', 36), new Person('Marion', 6), new Person('Erine', 1) ] def names = persons.findAll { it.age < 18 } .collect { it.name.toUpperCase() } .sort() .join(', ') assert names == "ERINE, MARION" find/findAll, inject, collect, flatten, min/max, unique, reverse, collate, groupBy, any/ every, head/tail/last, count/ countBy, combinations/ permutations/subsequences/ transpose, withDefault/ withLazyDefault -
@glaforge Closures — resource handling 45 new File('bible.txt').withReader { r
-‐> new File('out.txt').withWriter { w -‐> r.eachLine { line -‐> if (line.contains('Groovy')) w << line.toUpperCase() } } } -
@glaforge Closures — resource handling 45 new File('bible.txt').withReader { r
-‐> new File('out.txt').withWriter { w -‐> r.eachLine { line -‐> if (line.contains('Groovy')) w << line.toUpperCase() } } } Take care of properly opening / closing resources -
@glaforge Closures — Closures vs Java 8 lambdas? 46 IntStream.range(1,
100).forEach(s -‐> System.out.println(s)); ! Files.lines(Paths.get('README.adoc')) .map(it -‐> it.toUpperCase()) .forEach(it -‐> System.out.println it); -
@glaforge Closures — Closures vs Java 8 lambdas? 46 IntStream.range(1,
100).forEach { println it } ! Files.lines(Paths.get('README.adoc')) .map { it.toUpperCase() } .forEach { println it } IntStream.range(1, 100).forEach(s -‐> System.out.println(s)); ! Files.lines(Paths.get('README.adoc')) .map(it -‐> it.toUpperCase()) .forEach(it -‐> System.out.println it); -
@glaforge Closures — Closures vs Java 8 lambdas? 46 IntStream.range(1,
100).forEach { println it } ! Files.lines(Paths.get('README.adoc')) .map { it.toUpperCase() } .forEach { println it } IntStream.range(1, 100).forEach(s -‐> System.out.println(s)); ! Files.lines(Paths.get('README.adoc')) .map(it -‐> it.toUpperCase()) .forEach(it -‐> System.out.println it); Use Groovy closures wherever you pass lambdas in Java 8 -
@glaforge Closures — custom control structures 47 void unless(boolean cond,
Closure c) { if (!cond) c() } ! unless (10 < 9) { println "less" } -
@glaforge Closures — custom control structures 47 void unless(boolean cond,
Closure c) { if (!cond) c() } ! unless (10 < 9) { println "less" } Closure as last argument -
@glaforge Closures — custom control structures 47 void unless(boolean cond,
Closure c) { if (!cond) c() } ! unless (10 < 9) { println "less" } Closure as last argument Equivalent to: unless(10<9, {...}) -
@glaforge Lists 48 def list = ['a', 'b', 'c']
! list << 'd' assert list.contains('d') ! assert list.findAll { it.startsWith 'a' }.size() == 1 assert list.collect { it.toUpperCase() } == ['A', 'B', 'C', 'D'] assert list.inject('') { a, b -‐> a + b } == 'abcd' -
@glaforge Lists 48 def list = ['a', 'b', 'c']
! list << 'd' assert list.contains('d') ! assert list.findAll { it.startsWith 'a' }.size() == 1 assert list.collect { it.toUpperCase() } == ['A', 'B', 'C', 'D'] assert list.inject('') { a, b -‐> a + b } == 'abcd' List definition -
@glaforge Lists 48 def list = ['a', 'b', 'c']
! list << 'd' assert list.contains('d') ! assert list.findAll { it.startsWith 'a' }.size() == 1 assert list.collect { it.toUpperCase() } == ['A', 'B', 'C', 'D'] assert list.inject('') { a, b -‐> a + b } == 'abcd' List definition Append an element (operator overloading) -
@glaforge Lists 48 def list = ['a', 'b', 'c']
! list << 'd' assert list.contains('d') ! assert list.findAll { it.startsWith 'a' }.size() == 1 assert list.collect { it.toUpperCase() } == ['A', 'B', 'C', 'D'] assert list.inject('') { a, b -‐> a + b } == 'abcd' List definition Append an element (operator overloading) Functional-style map / filter / reduce with closures -
@glaforge Maps 49 def map = [name: 'Guillaume', age: 36]
! map.daughters = ['Marion', 'Erine'] ! assert map['daughters'].contains('Marion') -
@glaforge Maps 49 def map = [name: 'Guillaume', age: 36]
! map.daughters = ['Marion', 'Erine'] ! assert map['daughters'].contains('Marion') Map definition -
@glaforge Maps 49 def map = [name: 'Guillaume', age: 36]
! map.daughters = ['Marion', 'Erine'] ! assert map['daughters'].contains('Marion') Map definition Indexed access -
@glaforge Maps 49 def map = [name: 'Guillaume', age: 36]
! map.daughters = ['Marion', 'Erine'] ! assert map['daughters'].contains('Marion') Map definition Indexed access Property notation access -
@glaforge Regular expressions 50 def pattern = ~/.*foo.*/ !
assert "Alibaba" ==~ /.*(ba){2}/ ! def matcher = "Superman" =~ /([A-‐Z][a-‐z]+)man/ assert matcher[0][0] == 'Superman' assert matcher[0][1] == 'Super' ! '75001 Paris'.find(/(\d{5})\s(\w+)/) { match, zip, town -‐> println "The Zip code of ${town} is ${zip}" } -
@glaforge Regular expressions 50 def pattern = ~/.*foo.*/ !
assert "Alibaba" ==~ /.*(ba){2}/ ! def matcher = "Superman" =~ /([A-‐Z][a-‐z]+)man/ assert matcher[0][0] == 'Superman' assert matcher[0][1] == 'Super' ! '75001 Paris'.find(/(\d{5})\s(\w+)/) { match, zip, town -‐> println "The Zip code of ${town} is ${zip}" } Pattern -
@glaforge Regular expressions 50 def pattern = ~/.*foo.*/ !
assert "Alibaba" ==~ /.*(ba){2}/ ! def matcher = "Superman" =~ /([A-‐Z][a-‐z]+)man/ assert matcher[0][0] == 'Superman' assert matcher[0][1] == 'Super' ! '75001 Paris'.find(/(\d{5})\s(\w+)/) { match, zip, town -‐> println "The Zip code of ${town} is ${zip}" } Pattern Match -
@glaforge Regular expressions 50 def pattern = ~/.*foo.*/ !
assert "Alibaba" ==~ /.*(ba){2}/ ! def matcher = "Superman" =~ /([A-‐Z][a-‐z]+)man/ assert matcher[0][0] == 'Superman' assert matcher[0][1] == 'Super' ! '75001 Paris'.find(/(\d{5})\s(\w+)/) { match, zip, town -‐> println "The Zip code of ${town} is ${zip}" } Pattern Match Find -
@glaforge Regular expressions 50 def pattern = ~/.*foo.*/ !
assert "Alibaba" ==~ /.*(ba){2}/ ! def matcher = "Superman" =~ /([A-‐Z][a-‐z]+)man/ assert matcher[0][0] == 'Superman' assert matcher[0][1] == 'Super' ! '75001 Paris'.find(/(\d{5})\s(\w+)/) { match, zip, town -‐> println "The Zip code of ${town} is ${zip}" } Pattern Match Find Nice way to decompose the matched regions -
@glaforge Ranges 51 def range = 'a'..'z' ! assert
range.contains('m') assert range.contains('z') ! def exclusive = 1..<10 ! assert !exclusive.contains(10) ! def reverse = 10..0 ! assert reverse[0] == 10 assert reverse[-‐1] == 0 -
@glaforge Ranges 51 def range = 'a'..'z' ! assert
range.contains('m') assert range.contains('z') ! def exclusive = 1..<10 ! assert !exclusive.contains(10) ! def reverse = 10..0 ! assert reverse[0] == 10 assert reverse[-‐1] == 0 Range -
@glaforge Ranges 51 def range = 'a'..'z' ! assert
range.contains('m') assert range.contains('z') ! def exclusive = 1..<10 ! assert !exclusive.contains(10) ! def reverse = 10..0 ! assert reverse[0] == 10 assert reverse[-‐1] == 0 Range Excluded upper bound -
@glaforge Ranges 51 def range = 'a'..'z' ! assert
range.contains('m') assert range.contains('z') ! def exclusive = 1..<10 ! assert !exclusive.contains(10) ! def reverse = 10..0 ! assert reverse[0] == 10 assert reverse[-‐1] == 0 Range Excluded upper bound Reverse range -
@glaforge Ranges 51 def range = 'a'..'z' ! assert
range.contains('m') assert range.contains('z') ! def exclusive = 1..<10 ! assert !exclusive.contains(10) ! def reverse = 10..0 ! assert reverse[0] == 10 assert reverse[-‐1] == 0 Range Excluded upper bound Reverse range Negative index count from the end -
@glaforge Strings, GStrings, multiline strings 52 def name = 'Groovy'
def tmpl = """ Dear Mr ${name}, You're the winner of the lottery! Yours sincerly, Dave """ ! assert tmpl.toString().contains('Groovy') -
@glaforge Strings, GStrings, multiline strings 52 def name = 'Groovy'
def tmpl = """ Dear Mr ${name}, You're the winner of the lottery! Yours sincerly, Dave """ ! assert tmpl.toString().contains('Groovy') Plain java.lang.String -
@glaforge Strings, GStrings, multiline strings 52 def name = 'Groovy'
def tmpl = """ Dear Mr ${name}, You're the winner of the lottery! Yours sincerly, Dave """ ! assert tmpl.toString().contains('Groovy') Plain java.lang.String Multiline string with expression interpolation -
@glaforge BigDecimal by default! 55 assert 2.0 -‐ 1.1 ==
0.9 assert 3 / 2 == 1.5 -
@glaforge BigDecimal by default! 55 assert 2.0 -‐ 1.1 ==
0.9 assert 3 / 2 == 1.5 One of the reasons why micro- benchmarks sometimes showed Groovy to be slow... -
@glaforge BigDecimal by default! 55 assert 2.0 -‐ 1.1 ==
0.9 assert 3 / 2 == 1.5 One of the reasons why micro- benchmarks sometimes showed Groovy to be slow... But you can use doubles & floats for performance, with ‘d’ or ‘f’ suffixes or with explicit type -
@glaforge Powerful switch 56 Powerful switch / case on steroids
switch(obj) { case 123: "number 123"; break case "abc": "string abc"; break case String: "is a string"; break case [1, 2, 3]: "in list"; break case ~/.*o+.*/: "regex match"; break case { it < 3 }: "closure criteria"; break default: "unknown" } -
@glaforge Named arguments 57 move obj, x: 3, y: 4
Normal argument -
@glaforge Named arguments 57 move obj, x: 3, y: 4
Normal argument Named argument -
@glaforge Named arguments 57 move obj, x: 3, y: 4
Normal argument Named argument Calls: move(Map m, Object) -
@glaforge Command chains • Ability to chain method calls
without parentheses and dots 58 move forward at 3.km/h -
@glaforge Command chains • Ability to chain method calls
without parentheses and dots 58 move forward at 3.km/h Actually equivalent to: move(forward).at(3.getKm().div(h)) -
@glaforge Named arguments & command chains 59 check that: vodka
tastes good Will call: check(that: vodka).tastes(good) -
@glaforge Multiple assignment & destructuring 60 def (a, b) =
['A', 'B'] ! (a, b) = [b, a] ! def (int i, int j) = [1, 2] ! def geocode(String place) { return [45.4, 2.3] } ! def (la, lo) = geocode("Paris") ! assert la == 45.4 && lo == 2.3 -
@glaforge Multiple assignment & destructuring 60 def (a, b) =
['A', 'B'] ! (a, b) = [b, a] ! def (int i, int j) = [1, 2] ! def geocode(String place) { return [45.4, 2.3] } ! def (la, lo) = geocode("Paris") ! assert la == 45.4 && lo == 2.3 Classic « swap » -
@glaforge Multiple assignment & destructuring 60 def (a, b) =
['A', 'B'] ! (a, b) = [b, a] ! def (int i, int j) = [1, 2] ! def geocode(String place) { return [45.4, 2.3] } ! def (la, lo) = geocode("Paris") ! assert la == 45.4 && lo == 2.3 Classic « swap » With types -
@glaforge Multiple assignment & destructuring 60 def (a, b) =
['A', 'B'] ! (a, b) = [b, a] ! def (int i, int j) = [1, 2] ! def geocode(String place) { return [45.4, 2.3] } ! def (la, lo) = geocode("Paris") ! assert la == 45.4 && lo == 2.3 Classic « swap » With types Method returning a list -
@glaforge Multiple assignment & destructuring 60 def (a, b) =
['A', 'B'] ! (a, b) = [b, a] ! def (int i, int j) = [1, 2] ! def geocode(String place) { return [45.4, 2.3] } ! def (la, lo) = geocode("Paris") ! assert la == 45.4 && lo == 2.3 Classic « swap » With types Method returning a list Destructuring -
@glaforge Multiple assignment and destructuring 61 class Point {
double x, y ! double getAt(int idx) { if (idx == 0) x else if (idx == 1) y else throw new Exception("Wrong index") } } ! def (x, y) = new Point(x: 48.3, y: 3.5) ! assert x == 48.3 && y == 3.5 -
@glaforge Multiple assignment and destructuring 61 class Point {
double x, y ! double getAt(int idx) { if (idx == 0) x else if (idx == 1) y else throw new Exception("Wrong index") } } ! def (x, y) = new Point(x: 48.3, y: 3.5) ! assert x == 48.3 && y == 3.5 Method signature convention: getAt(int) -
@glaforge Multiple assignment and destructuring 61 class Point {
double x, y ! double getAt(int idx) { if (idx == 0) x else if (idx == 1) y else throw new Exception("Wrong index") } } ! def (x, y) = new Point(x: 48.3, y: 3.5) ! assert x == 48.3 && y == 3.5 Method signature convention: getAt(int) Transparent destructuring -
@glaforge Builders — JSON builder 62 import groovy.json.* !
def json = new JsonBuilder() json.person { name 'Guillaume' age 36 daughters 'Marion', 'Erine' address { street '1 Main Street' zip 75001 city 'Paris' } } -
@glaforge Builders — JSON builder 62 import groovy.json.* !
def json = new JsonBuilder() json.person { name 'Guillaume' age 36 daughters 'Marion', 'Erine' address { street '1 Main Street' zip 75001 city 'Paris' } } Hierarchical data representation -
@glaforge Builders — JSON builder 62 import groovy.json.* !
def json = new JsonBuilder() json.person { name 'Guillaume' age 36 daughters 'Marion', 'Erine' address { street '1 Main Street' zip 75001 city 'Paris' } } Hierarchical data representation Closure blocks delimiting the structure -
@glaforge Builders — JSON builder 62 import groovy.json.* !
def json = new JsonBuilder() json.person { name 'Guillaume' age 36 daughters 'Marion', 'Erine' address { street '1 Main Street' zip 75001 city 'Paris' } } Hierarchical data representation Closure blocks delimiting the structure { "person": { "name": "Guillaume", "age": 36, "daughters": [ "Marion", "Erine" ], "address": { "street": "1 Main Street", "zip": 75001, "city": "Paris" } } } -
@glaforge GPath expressions • GPath expressions are like XPath
but for an object graph 63 import groovy.json.* ! def url = "https://api.github.com/repos/groovy/groovy-‐core/commits" ! def commits = new JsonSlurper().parseText(url.toURL().text) ! assert commits[0].commit.author.name == 'Cedric Champeau' -
@glaforge GPath expressions • GPath expressions are like XPath
but for an object graph 63 import groovy.json.* ! def url = "https://api.github.com/repos/groovy/groovy-‐core/commits" ! def commits = new JsonSlurper().parseText(url.toURL().text) ! assert commits[0].commit.author.name == 'Cedric Champeau' GPath expression -
@glaforge GPath expressions • GPath expressions are like XPath
but for an object graph 63 import groovy.json.* ! def url = "https://api.github.com/repos/groovy/groovy-‐core/commits" ! def commits = new JsonSlurper().parseText(url.toURL().text) ! assert commits[0].commit.author.name == 'Cedric Champeau' GPath expression Add find / findAll into the mix -
@glaforge GPath expressions • GPath expressions are like XPath
but for an object graph 63 import groovy.json.* ! def url = "https://api.github.com/repos/groovy/groovy-‐core/commits" ! def commits = new JsonSlurper().parseText(url.toURL().text) ! assert commits[0].commit.author.name == 'Cedric Champeau' GPath expression Add find / findAll into the mix No (un)marshalling! -
@glaforge Power asserts 64 def (a, b, c) = [20,
30, 40] ! assert a * (b -‐ 1) / 10 == 3 * c / 2 + 1 -
@glaforge Power asserts 64 def (a, b, c) = [20,
30, 40] ! assert a * (b -‐ 1) / 10 == 3 * c / 2 + 1 Assertion failed: ! assert a * (b -‐ 1) / 10 == 3 * c / 2 + 1 | | | | | | | | | | | 580| 29 58 false| | 60 61 20 30 | 40 120 ! at script1.run(script1.groovy:3) -
@glaforge Power asserts 64 def (a, b, c) = [20,
30, 40] ! assert a * (b -‐ 1) / 10 == 3 * c / 2 + 1 Assertion failed: ! assert a * (b -‐ 1) / 10 == 3 * c / 2 + 1 | | | | | | | | | | | 580| 29 58 false| | 60 61 20 30 | 40 120 ! at script1.run(script1.groovy:3) Invented by the Spock testing framework -
@glaforge Null handling 65 class Order {
LineItem line } class LineItem { int quantity Item item } class Item { String name } ! def o = new Order( line: new LineItem( quantity: 2, item: null)) ! println o.line.item.name -
@glaforge Null handling 65 class Order {
LineItem line } class LineItem { int quantity Item item } class Item { String name } ! def o = new Order( line: new LineItem( quantity: 2, item: null)) ! println o.line.item.name With Java, you only get an NPE. No idea where it came from! -
@glaforge Null handling 65 class Order {
LineItem line } class LineItem { int quantity Item item } class Item { String name } ! def o = new Order( line: new LineItem( quantity: 2, item: null)) ! println o.line.item.name With Java, you only get an NPE. No idea where it came from! Groovy will say: Cannot get property ‘name’ on null object -
@glaforge Null handling 65 class Order {
LineItem line } class LineItem { int quantity Item item } class Item { String name } ! def o = new Order( line: new LineItem( quantity: 2, item: null)) ! println o.line.item.name -
@glaforge Null handling 65 class Order {
LineItem line } class LineItem { int quantity Item item } class Item { String name } ! def o = new Order( line: new LineItem( quantity: 2, item: null)) ! println o.line.item.name o?.line?.item?.name -
@glaforge Null handling 65 class Order {
LineItem line } class LineItem { int quantity Item item } class Item { String name } ! def o = new Order( line: new LineItem( quantity: 2, item: null)) ! println o.line.item.name o?.line?.item?.name Safe navigation: will just return null; No NPE -
@glaforge 66 The Truth, the GroovyTruth! And what if I
could customize the truth? -
@glaforge The Groovy Truth 67 assert !( null )
assert !( "" ) assert !( [] ) assert !( 0 ) assert new Object() assert "string" assert [1, 2, 3] assert 1234 -
@glaforge The Groovy Truth 67 assert !( null )
assert !( "" ) assert !( [] ) assert !( 0 ) assert new Object() assert "string" assert [1, 2, 3] assert 1234 null, empty, 0-sized, zero are coerced to false -
@glaforge The Groovy Truth 67 assert !( null )
assert !( "" ) assert !( [] ) assert !( 0 ) assert new Object() assert "string" assert [1, 2, 3] assert 1234 null, empty, 0-sized, zero are coerced to false true otherwise -
@glaforge Customizing the truth! 68 class Account {
String name boolean disabled = false ! boolean asBoolean() { !disabled } } ! assert new Account(name: 'current') assert !new Account(name: 'old', disabled: true) -
@glaforge Customizing the truth! 68 class Account {
String name boolean disabled = false ! boolean asBoolean() { !disabled } } ! assert new Account(name: 'current') assert !new Account(name: 'old', disabled: true) while (account), if (account), etc… -
@glaforge Towards Elvis... 70 def (x, y) = ['MacBook Pro',
'unknown'] if (x != null && x.size() > 0) x else y -
@glaforge Towards Elvis... 70 def (x, y) = ['MacBook Pro',
'unknown'] if (x != null && x.size() > 0) x else y if (x && x.size()) x else y -
@glaforge Towards Elvis... 70 def (x, y) = ['MacBook Pro',
'unknown'] if (x != null && x.size() > 0) x else y if (x && x.size()) x else y if (x) x else y -
@glaforge Towards Elvis... 70 def (x, y) = ['MacBook Pro',
'unknown'] if (x != null && x.size() > 0) x else y if (x && x.size()) x else y if (x) x else y x ? x : y -
@glaforge Towards Elvis... 70 def (x, y) = ['MacBook Pro',
'unknown'] if (x != null && x.size() > 0) x else y if (x && x.size()) x else y if (x) x else y x ? x : y x ?: y -
@glaforge Towards Elvis... 70 def (x, y) = ['MacBook Pro',
'unknown'] if (x != null && x.size() > 0) x else y if (x && x.size()) x else y if (x) x else y x ? x : y x ?: y Null, empty, zero- sized... false, otherwise true! -
@glaforge Towards Elvis... 70 def (x, y) = ['MacBook Pro',
'unknown'] if (x != null && x.size() > 0) x else y if (x && x.size()) x else y if (x) x else y x ? x : y x ?: y Null, empty, zero- sized... false, otherwise true! Good old ternary operator -
@glaforge Towards Elvis... 70 def (x, y) = ['MacBook Pro',
'unknown'] if (x != null && x.size() > 0) x else y if (x && x.size()) x else y if (x) x else y x ? x : y x ?: y Null, empty, zero- sized... false, otherwise true! Good old ternary operator Elvis! -
@glaforge AST transformations • Abstract Syntax Tree – in memory
representation of your program before being compiled into bytecode ! • AST transformation == process of transforming the AST of a program before it’s compiled ! • Macro-like compiler hook! 71 -
@glaforge Lots of AST transformations... • Code generation – @ToString,
@EqualsAndHashCode, @Canonical, @TupleConstructor, @InheritConstructors, @Category, @IndexedProperty, @Lazy, @Newify, @Sortable, @Builder • Class design – @Delegate, @Immutable, @Memoized, @Singleton, @Mixin • Logging – @Log, @Log4j, @Log4j2, @Slf4j 72 -
@glaforge Lots of AST transformations... • Safer scripting – @ConditionalInterrupt,
@ThreadInterrupt, @TimedInterupt • Compiler directives – @Field, @PackageScope, @AnnotationCollector, @DelegatesTo, @TypeChecked, @CompileStatic, @CompileDynamic, @TailRecursive • Swing patterns – @Bindable, @ListenerList, @Vetoable 73 -
@glaforge Lots of AST transformations... • Dependencies handling – @Grab,
@GrabConfig, @GrabExclude, @GrabResolver • Test assistance – @NotYetImplemented, @ASTTest • Misc. – @SourceURI 74 -
@glaforge Immutability • Implement immutability by the book !
– final class – tuple-style constructor – private final backing fields – defensive copying of collections – equals() and hashCode() methods – toString() method – ... 75 -
@glaforge Immutability • Implement immutability by the book !
– final class – tuple-style constructor – private final backing fields – defensive copying of collections – equals() and hashCode() methods – toString() method – ... 75 Can be error-prone to write immutable classes oneself! -
@glaforge Immutability • A Person class with – a String
name – an int age 76 public final class Person {! private final String name;! private final int age;! ! public Person(String name, int age) {! this.name = name;! this.age = age;! }! ! public String getName() {! return name;! }! ! public int getAge() {! return age;! }! ! public int hashCode() {! return age + 31 * name.hashCode();! }! ! public boolean equals(Object other) {! if (other == null) {! return false;! }! if (this == other) {! return true;! }! if (Person.class != other.getClass()) {! return false;! }! Person otherPerson = (Person)other;! if (!name.equals(otherPerson.getName()) {! return false;! }! if (age != otherPerson.getAge()) {! return false;! }! return true;! }! ! public String toString() {! return "Person(" + name + ", " + age + ")";! }! }! -
@glaforge Immutability • A Person class with – a String
name – an int age 76 public final class Person {! private final String name;! private final int age;! ! public Person(String name, int age) {! this.name = name;! this.age = age;! }! ! public String getName() {! return name;! }! ! public int getAge() {! return age;! }! ! public int hashCode() {! return age + 31 * name.hashCode();! }! ! public boolean equals(Object other) {! if (other == null) {! return false;! }! if (this == other) {! return true;! }! if (Person.class != other.getClass()) {! return false;! }! Person otherPerson = (Person)other;! if (!name.equals(otherPerson.getName()) {! return false;! }! if (age != otherPerson.getAge()) {! return false;! }! return true;! }! ! public String toString() {! return "Person(" + name + ", " + age + ")";! }! }! Damn verbose Java! -
@glaforge Immutability • A Person class with – a String
name – an int age 76 public final class Person {! private final String name;! private final int age;! ! public Person(String name, int age) {! this.name = name;! this.age = age;! }! ! public String getName() {! return name;! }! ! public int getAge() {! return age;! }! ! public int hashCode() {! return age + 31 * name.hashCode();! }! ! public boolean equals(Object other) {! if (other == null) {! return false;! }! if (this == other) {! return true;! }! if (Person.class != other.getClass()) {! return false;! }! Person otherPerson = (Person)other;! if (!name.equals(otherPerson.getName()) {! return false;! }! if (age != otherPerson.getAge()) {! return false;! }! return true;! }! ! public String toString() {! return "Person(" + name + ", " + age + ")";! }! }! Damn verbose Java! Although it’s also a valid Groovy program! -
@glaforge @Immutable 77 import groovy.transform.* ! @Immutable class
Person { String name int age } -
@glaforge Memoization • Cache the result of previous invocations of
closures or methods with the same set of argument values 78 import groovy.transform.* ! @Memoized long fib(long n) { if (n == 0) 0 else if (n == 1) 1 else fib(n -‐ 1) + fib(n -‐ 2) } ! println fib(40) -
@glaforge Memoization • Cache the result of previous invocations of
closures or methods with the same set of argument values 78 import groovy.transform.* ! @Memoized long fib(long n) { if (n == 0) 0 else if (n == 1) 1 else fib(n -‐ 1) + fib(n -‐ 2) } ! println fib(40) Best applied to side-effect free functions -
Groovy allows you to be lazy The compiler will do
the job for you -
Groovy allows you to be lazy The compiler will do
the job for you More concise, more readable code -
Groovy allows you to be lazy The compiler will do
the job for you More concise, more readable code Less stuff to maintain and worry about -
@glaforge @TypeChecked & @CompileStatic • Static type checking with @TypeChecked,
throws compilation errors on... – typos in method and variable names – incompatible return types – wrong type assignments ! • Supports fine-grained type inference – « Least Upper Bound » – « Flow typing » 80 -
@glaforge @TypeChecked & @CompileStatic • Static type checking with @TypeChecked,
throws compilation errors on... – typos in method and variable names – incompatible return types – wrong type assignments ! • Supports fine-grained type inference – « Least Upper Bound » – « Flow typing » 80 You can even extend the static type checker! -
@glaforge @TypeChecked & @CompileStatic • Static type checking with @TypeChecked,
throws compilation errors on... – typos in method and variable names – incompatible return types – wrong type assignments ! • Supports fine-grained type inference – « Least Upper Bound » – « Flow typing » 80 You can even extend the static type checker! Type check DSLs or dynamic features! -
@glaforge @TypeChecked & @CompileStatic ! • What is type checked
can also be compiled statically with @CompileStatic ! – generate the same bytecode as javac ! – same performance as Java 81 -
@glaforge Static compilation performance 82 Fibonacci Pi (π) quadrature Binary
trees Java 191 ms 97 ms 3.6 s Static compilation 197 ms 101 ms 4.3 s Primitive optimizations 360 ms 111 ms 23.7 s No prim. optimizations 2590 ms 3220 ms 50.0 s 1.7 1.8 2.x -
@glaforge GVM: Groovy enVironment Manager • The new kid on
the block – http://gvmtool.net/ — @gvmtool ! • Manage parallel versions of the various ecosystem projects ! • Supports... – Groovy, Grails, Griffon, Gradle, Vert.x, Spring Boot ! • On Linux, MacOS, Cygwin, Solaris, FreeBSD 91 -
@glaforge Spock example 93 @Grab('org.spockframework:spock-‐core:0.7-‐groovy-‐2.0') import spock.lang.* !
class MathSpec extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c ! where: a | b || c 1 | 3 || 3 7 | 4 || 7 0 | 0 || 0 } } -
@glaforge Spock example 93 @Grab('org.spockframework:spock-‐core:0.7-‐groovy-‐2.0') import spock.lang.* !
class MathSpec extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c ! where: a | b || c 1 | 3 || 3 7 | 4 || 7 0 | 0 || 0 } } @Grab a dependency -
@glaforge Spock example 93 @Grab('org.spockframework:spock-‐core:0.7-‐groovy-‐2.0') import spock.lang.* !
class MathSpec extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c ! where: a | b || c 1 | 3 || 3 7 | 4 || 7 0 | 0 || 0 } } @Grab a dependency Meaningful test method names -
@glaforge Spock example 93 @Grab('org.spockframework:spock-‐core:0.7-‐groovy-‐2.0') import spock.lang.* !
class MathSpec extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c ! where: a | b || c 1 | 3 || 3 7 | 4 || 7 0 | 0 || 0 } } @Grab a dependency Meaningful test method names Clever use of labels for BDD style -
@glaforge Spock example 93 @Grab('org.spockframework:spock-‐core:0.7-‐groovy-‐2.0') import spock.lang.* !
class MathSpec extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c ! where: a | b || c 1 | 3 || 3 7 | 4 || 7 0 | 0 || 0 } } @Grab a dependency Meaningful test method names Clever use of labels for BDD style Expression to be asserted -
@glaforge Spock example 93 @Grab('org.spockframework:spock-‐core:0.7-‐groovy-‐2.0') import spock.lang.* !
class MathSpec extends Specification { def "maximum of two numbers"() { expect: Math.max(a, b) == c ! where: a | b || c 1 | 3 || 3 7 | 4 || 7 0 | 0 || 0 } } @Grab a dependency Meaningful test method names Clever use of labels for BDD style Expression to be asserted Cute data- driven tests! -
@GrabResolver("https://oss.jfrog.org/artifactory/repo") @Grab("org.ratpack-‐framework:ratpack-‐groovy:0.9.0-‐SNAPSHOT") import static org.ratpackframework.groovy.RatpackScript.ratpack import static
org.ratpackframework.groovy.Template.groovyTemplate ! ratpack { handlers { get { response.send "Welcome!" } ! get("date") { render groovyTemplate("date.html") } ! assets "public" } } -
@GrabResolver("https://oss.jfrog.org/artifactory/repo") @Grab("org.ratpack-‐framework:ratpack-‐groovy:0.9.0-‐SNAPSHOT") import static org.ratpackframework.groovy.RatpackScript.ratpack import static
org.ratpackframework.groovy.Template.groovyTemplate ! ratpack { handlers { get { response.send "Welcome!" } ! get("date") { render groovyTemplate("date.html") } ! assets "public" } } Lightweight Netty-based web app toolkit -
@glaforge Geb • Browser automation solution ! • WebDriver +
jQuery selectors + Groovy ! • Handy for – scripting, scraping, automation... – functional / web / acceptance testing •when integrated with JUnit, TestNG or Spock 95 -
@glaforge Geb — Example 96 import geb.Browser ! Browser.drive
{ go "http://myapp.com/login" ! assert $("h1").text() == "Please Login" ! $("form.login").with { username = "admin" password = "password" login().click() } ! assert $("h1").text() == "Admin Section" } -
@glaforge Geb — Example 96 import geb.Browser ! Browser.drive
{ go "http://myapp.com/login" ! assert $("h1").text() == "Please Login" ! $("form.login").with { username = "admin" password = "password" login().click() } ! assert $("h1").text() == "Admin Section" } Drive the browser to this site -
@glaforge Geb — Example 96 import geb.Browser ! Browser.drive
{ go "http://myapp.com/login" ! assert $("h1").text() == "Please Login" ! $("form.login").with { username = "admin" password = "password" login().click() } ! assert $("h1").text() == "Admin Section" } Drive the browser to this site Check the content of the title -
@glaforge Geb — Example 96 import geb.Browser ! Browser.drive
{ go "http://myapp.com/login" ! assert $("h1").text() == "Please Login" ! $("form.login").with { username = "admin" password = "password" login().click() } ! assert $("h1").text() == "Admin Section" } Drive the browser to this site Check the content of the title Find & fill in the form -
@glaforge Geb — Example 96 import geb.Browser ! Browser.drive
{ go "http://myapp.com/login" ! assert $("h1").text() == "Please Login" ! $("form.login").with { username = "admin" password = "password" login().click() } ! assert $("h1").text() == "Admin Section" } Drive the browser to this site Check the content of the title Find & fill in the form Submit the form -
@glaforge Geb — Example 96 import geb.Browser ! Browser.drive
{ go "http://myapp.com/login" ! assert $("h1").text() == "Please Login" ! $("form.login").with { username = "admin" password = "password" login().click() } ! assert $("h1").text() == "Admin Section" } Drive the browser to this site Check the content of the title Find & fill in the form Submit the form In the admin section, yeah! -
@glaforge Geb — With page objects and Spock 97 import
geb.spock.GebSpec ! class GoogleWikipediaSpec extends GebSpec { ! def "first result for wikipedia search should be wikipedia"() { given: to GoogleHomePage ! expect: at GoogleHomePage ! when: search.field.value("wikipedia") ! then: waitFor { at GoogleResultsPage } ! and: firstResultLink.text() == "Wikipedia" ! when: firstResultLink.click() ! then: waitFor { at WikipediaPage } } } -
@glaforge Geb — With page objects and Spock 97 import
geb.spock.GebSpec ! class GoogleWikipediaSpec extends GebSpec { ! def "first result for wikipedia search should be wikipedia"() { given: to GoogleHomePage ! expect: at GoogleHomePage ! when: search.field.value("wikipedia") ! then: waitFor { at GoogleResultsPage } ! and: firstResultLink.text() == "Wikipedia" ! when: firstResultLink.click() ! then: waitFor { at WikipediaPage } } } With page objects -
@glaforge Geb — With page objects and Spock 97 import
geb.spock.GebSpec ! class GoogleWikipediaSpec extends GebSpec { ! def "first result for wikipedia search should be wikipedia"() { given: to GoogleHomePage ! expect: at GoogleHomePage ! when: search.field.value("wikipedia") ! then: waitFor { at GoogleResultsPage } ! and: firstResultLink.text() == "Wikipedia" ! when: firstResultLink.click() ! then: waitFor { at WikipediaPage } } } With page objects BDD style: given/when/then -
@glaforge Geb — With page objects and Spock 97 import
geb.spock.GebSpec ! class GoogleWikipediaSpec extends GebSpec { ! def "first result for wikipedia search should be wikipedia"() { given: to GoogleHomePage ! expect: at GoogleHomePage ! when: search.field.value("wikipedia") ! then: waitFor { at GoogleResultsPage } ! and: firstResultLink.text() == "Wikipedia" ! when: firstResultLink.click() ! then: waitFor { at WikipediaPage } } } With page objects BDD style: given/when/then Wait for slow loading pages -
@glaforge Java’s best friend • Java derived syntax –Flat learning
curve –Easy to learn • But goes beyond Java –Concise, expressive, readable –Fit for Domain-Specific Languages • Seamless & transparent Java integration –Mix & match Groovy & Java classes (joint compil.) –No language barrier to cross 99 -
@glaforge Groovy’s nature ! • Object oriented dynamic language... !
• But... – as type safe as you want it — static type checking – as fast as you need it — static compilation – as functional as you make it — closures... 100 -
@glaforge Groovy use cases • Business languages & Domain-Specific Languages
• Scripting tasks, build automation • More readable and expressive tests • Extension points for customizing / configuring apps • Full blown apps – for the web with Grails, Ratpack, Gaelyk – for web reactive programming with Reactor – for desktop with Griffon 101