More Related Content Similar to Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King Similar to Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King (20) Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King2. Topics
• Testing/Mocking: JUnit, TestNG, EasyB, Spock,
Instinct, MockFor, Gmock, EasyMock
• Injection: Spring, Guice
• Coverage: Cobertura
• Code style: CodeNarc, IntelliJ
• Duplication: Simian
• Documentation: GroovyDoc
• Builds: Ant, Gant, GMaven, Gradle, Hudson
• Modularisation: Grapes, OSGi
4. © What is Groovy?
A
S
E
R
T
2
0
0
6
-
2
0 • ―Groovy is like a super version
0
9 of Java. It can leverage Java's
enterprise capabilities but also
has cool productivity features like closures,
DSL support, builders and dynamic typing.‖
SpringOne2gx_Oct2009 - 4
5. © Groovy Goodies Overview
A
S
E
• Fully object oriented
R
T
2
0
• Closures: reusable • GPath: efficient
0
6
and assignable pieces object navigation
-
2
of code • GroovyBeans
• Operators can be
0
0 • grep and switch
overloaded
9
• Templates, builder,
• Multimethods swing, Ant, markup,
• Literal declaration for XML, SQL, XML-RPC,
lists (arrays), maps, Scriptom, Grails,
ranges and regular tests, Mocks
expressions
SpringOne2gx_Oct2009 - 5
7. ©
… Growing Acceptance …
A
S
E
R
T
2
0
0
6
-
2
0
0
9
http://www.jroller.com/scolebourne/entry/devoxx_2008_whitebo
SpringOne2gx_Oct2009 - 7
http://www.java.net
8. © … Growing Acceptance …
What alternative JVM language are you using or intending to use
A
S
E
R
T
2
0
0
6
-
2
0
0
9
http://www.leonardoborges.com/writings
SpringOne2gx_Oct2009 - 8
9. © … Growing Acceptance …
A
S
E
R
T
2
0
0
6
-
2
0
0
9
http://it-republik.de/jaxenter/quickvote/results/1/poll/44 (translated using http://babelfish.yahoo
SpringOne2gx_Oct2009 - 9
10. © … Growing Acceptance
A
S
E
R
T
2
0
0
6
-
2
0
0
9
SpringOne2gx_Oct2009 - 10
11. Groovy‘s Appeal
• Innovators/Thought leaders
– Ideas, power, flexibility, novelty, thinking community
• Early adopters
– Productivity benefits and collegiate community
– Leverage JVM and potential for mainstream
• Mainstream
– Leverage existing Java skills, low learning curve
– Leverage JVM and production infrastructure
– Professional community
– Tools, tools, tools
12. TESTING
SpringOne 2GX 2009. All rights reserved. Do not distribute without permission.
13. Utilities Runners
Groovy and Testing Tool Spectrum
AllPairs,
Combinations Native Groovy, JUnit, TestNG, Spock, EasyB,
Polyglot languages JBehave, Cucumber, Robot Framework
Logic programming
Threads, Parallel /
Concurrency Web Database SOAP / Other
libraries Drivers Drivers REST Drivers
Data-driven Drivers
WebTest DbUnit FEST
libraries
WebDriver DataSets GroovyWS Email
Networking libraries
XML Processing JWebUnit SqlUnit XML-RPC FTP
Read/write files / Tellurium groovy.sql CXF AntUnit
Excel / Word / CSV Selenium JPA Axis2 Telnet
Reporting, Logging HtmlUnit JDO JAX-WS SSH
Watij BigTable JAX-RS Exec
Tools
HttpBuilder JDBC
iTest2, SoapUI,
Twist, IDEs, JMeter,
Cyberneko
Text editors,
Recorders, Sahi,
Build Tools, CI
14. Testing: JUnit
import org.junit.Test
import static org.junit.Assert.assertEquals
class ArithmeticTest {
@Test
void additionIsWorking() {
assertEquals 4, 2+2
}
@Test(expected=ArithmeticException)
void divideByZero() {
println 1/0
}
}
15. Testing: TestNG
import ...
public class SimpleTest {
@BeforeClass
public void setUp() {
// code invoked when test is created
}
@Test(groups = [ "fast" ])
public void aFastTest() {
System.out.println("Fast test");
}
@Test(groups = [ "slow" ])
public void aSlowTest() {
System.out.println("Slow test");
}
}
16. import static org.easymock.EasyMock.*
mockControl = createStrictControl()
mockReverser = mockControl.createMock(Reverser)
storer = new JavaStorer(mockReverser)
testStorage()
def testStorage() {
expectReverse(123.456, -123.456)
Mocking: EasyMock
expectReverse('hello', 'olleh')
mockControl.replay()
checkReverse(123.456, -123.456)
checkReverse('hello', 'olleh')
mockControl.verify()
}
def expectReverse(input, output) {
expect(mockReverser.reverse(input)).andReturn(output)
}
def checkReverse(value, reverseValue) {
storer.put(value)
assert value == storer.get()
assert reverseValue == storer.getReverse()
}
17. class a_default_storer {
def storer
@initially void create_new_storer() {
storer = new Storer()
}
private check_persist_and_reverse(value, expectedReverse) {
storer.put(value)
def persisted = storer.get()
assert persisted == value
def reversed = storer.reverse
assert reversed == expectedReverse
Testing: Instinct
}
@spec def should_reverse_numbers() {
check_persist_and_reverse 123.456, -123.456
}
@spec def should_reverse_strings() {
check_persist_and_reverse 'hello', 'olleh'
}
@spec def should_reverse_lists() {
check_persist_and_reverse([1, 3, 5], [5, 3, 1])
}
}
check_specs_for a_default_storer
18. import groovy.mock.interceptor.MockFor
def mocker = new MockFor(Collaborator.class) // create the Mock support
mocker.demand.one(1..2) { 1 } // demand the 'one' method one
// or two times, returning 1
mocker.demand.two() { 2 } // demand the 'two' method
// exactly once, returning 2
mocker.use { // start using the Mock
def caller = new Caller() // caller will call Collaborator
assertEquals 1, caller.collaborateOne() // will call Collaborator.one
assertEquals 1, caller.collaborateOne() // will call Collaborator.one
assertEquals 2, caller.collaborateTwo() // will call Collaborator.two
Mocking: MockFor
} // implicit verify here
import groovy.mock.interceptor.MockFor
def mocker = new MockFor(Collaborator.class)
mocker.demand.one(1..2) { 1 }
mocker.demand.two() { 2 }
mocker.use {
def caller = new Caller()
assertEquals 1, caller.collaborateOne()
assertEquals 1, caller.collaborateOne()
assertEquals 2, caller.collaborateTwo()
}
19. Mocking: Gmock ...
• Method mocking: mockLoader.load("fruit").returns("apple")
• Exception mocking: mockLoader.load("unknown").raises(new RuntimeException())
• Stub mocking: mockLoader.load("fruit").returns("apple").stub()
• Static method mocking: mockMath.static.random().returns(0.5)
• Property mocking: mockLoader.name.returns("loader")
• Constructor mocking: def mockFile = mock(File, constructor('/a/path/file.txt'))
• Partial mocking: mock(controller).params.returns([id: 3])
• Times expectation: mockLoader.load("fruit").returns("apple").atLeastOnce()
• Custom matcher: mockLoader.load(match{ it.startsWith("fru") })
• Strict ordering: ordered { ... }
• Optional support for Hamcrest matcher: mockLoader.put("test", is(not(lessThan(5))))
• GMockController if you can't extend GMockTestCase in your test
20. Mocking: Gmock
import org.gmock.GMockTestCase
class LoaderTest extends GMockTestCase {
void testLoader(){
def mockLoader = mock()
mockLoader.load('key').returns('value')
play {
assertEquals "value", mockLoader.load('key')
}
}
}
22. ... Testing: Spock
@Speck
@RunWith(Sputnik)
class PublisherSubscriberSpeck {
def "events are received by all subscribers"() {
def pub = new Publisher()
def sub1 = Mock(Subscriber)
def sub2 = Mock(Subscriber)
pub.subscribers << sub1 << sub2
when:
pub.send("event")
then:
1 * sub1.receive("event")
1 * sub2.receive("event")
}
}
23. Testing: EasyB ...
given "an invalid zip code", {
invalidzipcode = "221o1"
}
and "given the zipcodevalidator is initialized", {
zipvalidate = new ZipCodeValidator()
}
when "validate is invoked with the invalid zip code", {
value = zipvalidate.validate(invalidzipcode)
}
then "the validator instance should return false", {
value.shouldBe false
}
24. before "start selenium", {
given "selenium is up and running", {
// start selenium
}
}
scenario "a valid person has been entered", {
when "filling out the person form with a first and last name", {
selenium.open("http://acme.racing.net/greport/personracereport.html")
selenium.type("fname", "Britney")
selenium.type("lname", "Smith")
}
Testing: EasyB ...
and "the submit link has been clicked", {
selenium.click("submit")
}
then "the report should have a list of races for that person", {
selenium.waitForPageToLoad("5000")
values = ["Mclean 1/2 Marathon", "Reston 5K", "Herndon 10K", "Leesburg 10K"]
for(i in 0..<values.size()){
selenium.getText("//table//tr[${(i+3)}]/td").shouldBeEqualTo values[i]
}
}
}
after "stop selenium" , {
then "selenium should be shutdown", {
// stop selenium
}
}
25. Dependency Injection
• Hollywood Principle
– Don‘t call us, we‘ll call you
• “All problems in computer science
can be solved by another level of
indirection”
• "...except for the problem of too
many layers of indirection“
– For attributions, see
http://en.wikipedia.org/wiki/Inversion_of_control
26. Dependency Injection
• Pattern for loosely coupled & testable objects
class Client { class Client {
Calculator calc = Calculator calc
new CalculatorImpl() def executeCalc(a, b) {
def executeCalc(a, b) { calc.add(a, b)
calc.add(a, b) }
} }
}
Service locator/factory Need to select setter,
constructor, field style
Tightly coupled?
Can add complexity
Hard to test?
Manage configuration
Easy to understand?
Direct or framework
Refactoring/navigation?
Consistency/lifecycle
27. Dependency Injection: Spring ...
• Several flavors
– let‘s look at Annotation and BeanBuilder flavors
import org.springframework.stereotype.Component
@Component class AdderImpl {
def add(x, y) { x + y }
}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
@Component class CalcImpl3 {
@Autowired private AdderImpl adder
def doAdd(x, y) { adder.add(x, y) }
}
28. ... Dependency Injection: Spring
import org.springframework.context.support.GenericApplicationContext
import
org.springframework.context.annotation.ClassPathBeanDefinitionScanner
def ctx = new GenericApplicationContext()
new ClassPathBeanDefinitionScanner(ctx).scan('')
ctx.refresh()
def calc = ctx.getBean('calcImpl3')
println calc.doAdd(3, 4) // => 7
def bb = new grails.spring.BeanBuilder()
bb.beans {
adder(AdderImpl)
calcBean(CalcImpl2) { delegate.adder = adder }
}
def ctx = bb.createApplicationContext()
def calc = ctx.getBean('calcBean')
println calc.doAdd(3, 4) // => 7
29. Dependency Injection: Guice
import com.google.inject.*
@ImplementedBy(CalculatorImpl)
interface Calculator {
def add(a, b)
}
@Singleton
class CalculatorImpl implements Calculator {
private total = 0
def add(a, b) { total++; a + b }
def getTotalCalculations() { 'Total Calculations: ' + total }
String toString() { 'Calc: ' + hashCode()}
}
class Client {
@Inject Calculator calc
// ...
}
def injector = Guice.createInjector()
// ...
30. Dependency Injection:
Metaprogramming Style
class Calculator {
def total = 0
def add(a, b) { total++; a + b }
}
def INSTANCE = new Calculator()
Calculator.metaClass.constructor = { -> INSTANCE }
def c1 = new Calculator()
def c2 = new Calculator()
assert c1.add(1, 2) == 3
assert c2.add(3, 4) == 7
assert c1.is(c2)
assert [c1, c2].total == [2, 2]
33. ...Coverage: Cobertura ...
Maven
cobertura-instrument
java –cp ... Command-line
cobertura-report
cobertura-check
cobertura-merge
Grails
grails install-plugin code-coverage --global
42. Duplication: Simian ...
Simian fully supports the following languages:
• Java
• C#
• C++
with partial support
• C
for:
• Objective-C
• JavaScript (ECMAScript) • JSP
• COBOL, ABAP • ASP
• Ruby • HTML
• Lisp • XML
• SQL
• Visual Basic
• Groovy
45. ... Duplication: Simian
Similarity Analyser 2.2.23 -
http://www.redhillconsulting.com.au/products/simian/index.html
Copyright (c) 2003-08 RedHill Consulting Pty. Ltd. All rights reserved.
Simian is not free unless used solely for non-commercial or evaluation
purposes.
{failOnDuplication=true, ignoreCharacterCase=true,
ignoreCurlyBraces=true, ignoreIdentifierCase=true,
ignoreModifiers=true, ignoreStringCase=true, threshold=6}
Found 6 duplicate lines in the following files:
Between lines 201 and 207 in
/Users/haruki_zaemon/Projects/redhill/simian/build/dist/src/java/
awt/image/WritableRaster.java
...
Found 66375 duplicate lines in 5949 blocks in 1260 files
Processed a total of 390309 significant (1196065 raw) lines in 4242 files
Processing time: 9.490sec
46. Documentation: GroovyDoc ...
<taskdef name="groovydoc"
classname="org.codehaus.groovy.ant.Groovydoc">
<classpath>
<path path="${mainClassesDirectory}" />
<path refid="compilePath" />
</classpath>
</taskdef>
<groovydoc destdir="${docsDirectory}/gapi"
sourcepath="${mainSourceDirectory}"
packagenames="**.*" use="true" windowtitle="${title} "
doctitle="${title}" header="${title}" footer="${docFooter}"
overview="src/main/overview.html" private="false">
<link packages="java.,org.xml.,javax.,org.xml."
href="http://java.sun.com/j2se/1.5.0/docs/api" />
<link packages="org.apache.ant.,org.apache.tools.ant."
href="http://www.dpml.net/api/ant/1.7.0" />
<link packages="org.junit.,junit.framework."
href="http://junit.sourceforge.net/junit3.8.1/javadoc/" />
</groovydoc>
49. Builds: Groovy from Ant
• Need groovy jar on your Ant classpath
<taskdef name="groovy"
classname="org.codehaus.groovy.ant.Groovy"
classpathref="my.classpath"/>
<target name="printXmlFileNamesFromJar">
<zipfileset id="found" src="foobar.jar"
includes="**/*.xml"/>
<groovy>
project.references.found.each {
println it.name
}
</groovy>
</target>
50. Builds: Ant from Groovy
• Built-in (need ant.jar on your Groovy classpath)
new AntBuilder().with {
echo(file:'Temp.java', '''
class Temp {
public static void main(String[] args) {
System.out.println("Hello");
}
}
''')
javac(srcdir:'.', includes:'Temp.java', fork:'true')
java(classpath:'.', classname:'Temp', fork:'true')
echo('Done')
}
// =>
// [javac] Compiling 1 source file
// [java] Hello
// [echo] Done
51. Builds: Gant
• lightweight façade on Groovy's AntBuilder
• target def‘ns, pre-defined ‗ant‘, operations on
predefined objects
includeTargets << gant.targets.Clean
cleanPattern << [ '**/*~' , '**/*.bak' ]
cleanDirectory << 'build'
target ( stuff : 'A target to do some stuff.' ) {
println ( 'Stuff' )
depends ( clean )
echo ( message : 'A default message from Ant.' )
otherStuff ( )
}
target ( otherStuff : 'A target to do some other stuff' ) {
println ( 'OtherStuff' )
echo ( message : 'Another message from Ant.' )
clean ( )
}
52. Builds: GMaven
• Implementing Maven plugins has never been
Groovier!
• Groovy Mojos
– A Simple Groovy Mojo
• Building Plugins
– Project Definition
– Mojo Parameters
• Putting More Groove into your Mojo
– Using ant, Using fail()
• gmaven-archetype-mojo Archetype
• gmaven-plugin Packaging
53. Builds: GMaven
<plugin>
<groupId>org.codehaus.mojo.groovy</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<executions>
<execution>
<id>restart-weblogic</id>
<phase>pre-integration-test</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
${pom.basedir}/src/main/script/restartWeblogic.groovy
</source>
</configuration>
</execution>
...
54. Builds: GMaven
def domainDir =
project.properties['weblogic.domain.easyimage.dir']
stopWebLogic()
copyFiles(domainDir)
startWebLogic(domainDir)
waitForWebLogicStartup()
def stopWebLogic() {
weblogicServerDir = project.properties['weblogic.server.dir']
adminUrl = project.properties['easyimage.weblogic.admin.t3']
userName = 'weblogic'
password = 'weblogic'
ant.exec(executable: 'cmd', failonerror: 'false') {
arg(line: "/C ${wlsDir}/bin/setWLSEnv.cmd && java ..." ...
}
...
55. Builds: Gradle ...
• A very flexible general purpose build tool like Ant
• Switchable, build-by-convention frameworks a la Maven.
But we never lock you in!
• Powerful support for multi-project builds
• Powerful dependency management (Apache Ivy based)
• Full support for your existing Maven or Ivy repository
infrastructure
• Support for transitive dependency management without
the need for remote repositories or pom.xml/ivy.xml files
• Ant tasks as first class citizens
• Groovy build scripts
• A rich domain model for describing your build
57. Builds: Hudson
• Gant Plugin — This plugin allows Hudson to invoke
Gant build script as the main build step
• Gradle Plugin — This plugin allows Hudson to
invoke Gradle build script as the main build step
• Grails Plugin — This plugin allows Hudson to invoke
Grails tasks as build steps
• Hudson CLI and GroovyShell Usage pattern?
Source: http://weblogs.java.net/blog/kohsuke/archive/2009/05/hudson_cli_and.html
58. <project name="StringUtilsBuild" default="package"
xmlns:ivy="antlib:org.apache.ivy.ant" xmlns="antlib:org.apache.tools.ant">
<target name="clean">
<delete dir="target"/>
<delete dir="lib"/>
</target>
<target name="compile" depends="-init-ivy">
<mkdir dir="target/classes"/>
<javac srcdir="src/main"
destdir="target/classes"/>
</target>
<target name="compileTest" depends="compile">
<mkdir dir="target/test-classes"/>
Ant Build File...
<javac srcdir="src/test"
destdir="target/test-classes">
<classpath>
<pathelement location="target/classes"/>
<fileset dir="lib" includes="*.jar"/>
</classpath>
</javac>
</target>
...
59. ...
<target name="test" depends="compileTest">
<mkdir dir="target/test-reports"/>
<junit printsummary="yes" fork="yes" haltonfailure="yes">
<classpath>
<pathelement location="target/classes"/>
<pathelement location="target/test-classes"/>
<fileset dir="lib" includes="*.jar"/>
</classpath>
<formatter type="plain"/>
<formatter type="xml"/>
<batchtest fork="yes" todir="target/test-reports">
<fileset dir="target/test-classes"/>
</batchtest>
</junit>
</target>
<target name="package" depends="test">
<jar destfile="target/stringutils-1.0-SNAPSHOT.jar"
basedir="target/classes"/>
</target>
...Ant Build File
<target name="-init-ivy" depends="-download-ivy">
<taskdef resource="org/apache/ivy/ant/antlib.xml"
uri="antlib:org.apache.ivy.ant" classpath="lib/ivy.jar"/>
<ivy:settings file="ivysettings.xml"/>
<ivy:retrieve/>
</target>
<target name="-download-ivy">
<property name="ivy.version" value="2.1.0-rc2"/>
<mkdir dir="lib"/>
<get
src="http://repo2.maven.org/maven2/org/apache/ivy/ivy/${ivy.version}/ivy-
${ivy.version}.jar"
dest="lib/ivy.jar" usetimestamp="true"/>
</target>
</project>
60. <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.groovycookbook.builds</groupId>
<artifactId>stringutils</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>stringutils</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- this is a java 1.5 project -->
Maven POM
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
61. import static groovy.xml.NamespaceBuilder.newInstance as namespace
ant = new AntBuilder()
clean()
doPackage()
def doPackage() {
test()
ant.jar destfile: 'target/stringutils-1.0-SNAPSHOT.jar',
basedir: 'target/classes'
}
private dependencies() {
def ivy_version = '2.1.0-rc2'
def repo = 'http://repo2.maven.org/maven2'
ant.mkdir dir: 'lib'
ant.get dest: 'lib/ivy.jar',
usetimestamp: 'true',
src: "$repo/org/apache/ivy/ivy/$ivy_version/ivy-${ivy_version}.jar"
build.groovy ...
ant.taskdef classpath: 'lib/ivy.jar',
uri: 'antlib:org.apache.ivy.ant',
resource: 'org/apache/ivy/ant/antlib.xml'
def ivy = namespace(ant, 'antlib:org.apache.ivy.ant')
ivy.settings file: 'ivysettings.xml'
ivy.retrieve()
}
def clean() {
ant.delete dir: 'target'
ant.delete dir: 'lib'
}
...
62. ...
def compile() {
dependencies()
ant.mkdir dir: 'target/classes'
ant.javac destdir: 'target/classes', srcdir: 'src/main',
includeantruntime: false
}
def compileTest() {
compile()
ant.mkdir dir: 'target/test-classes'
ant.javac(destdir: 'target/test-classes', srcdir: 'src/test',
includeantruntime: false) {
classpath {
pathelement location: 'target/classes'
fileset dir: 'lib', includes: '*.jar'
}
}
}
... build.groovy
def test() {
compileTest()
ant.mkdir dir: 'target/test-reports'
ant.junit(printsummary: 'yes', haltonfailure: 'yes', fork: 'yes') {
classpath {
pathelement location: 'target/classes'
pathelement location: 'target/test-classes'
fileset dir: 'lib', includes: '*.jar'
}
formatter type: 'plain'
formatter type: 'xml'
batchtest(todir: 'target/test-reports', fork: 'yes') {
fileset dir: 'target/test-classes'
}
}
}
63. import static groovy.xml.NamespaceBuilder.newInstance as namespace
target('package': '') {
depends 'test'
jar destfile: 'target/stringutils-1.0-SNAPSHOT.jar',
basedir: 'target/classes'
}
target('-download-ivy': '') {
def ivy_version = '2.1.0-rc2'
def repo = 'http://repo2.maven.org/maven2'
mkdir dir: 'lib'
get dest: 'lib/ivy.jar', usetimestamp: 'true',
src: "$repo/org/apache/ivy/ivy/$ivy_version/ivy-${ivy_version}.jar"
}
target(clean: '') {
delete dir: 'target'
build.gant ...
delete dir: 'lib'
}
target(compile: '') {
depends '-init-ivy'
mkdir dir: 'target/classes'
javac destdir: 'target/classes', srcdir: 'src/main'
}
...
64. ...
target('-init-ivy': '') {
depends '-download-ivy'
taskdef classpath: 'lib/ivy.jar', uri: 'antlib:org.apache.ivy.ant',
resource: 'org/apache/ivy/ant/antlib.xml'
def ivy = namespace(ant, 'antlib:org.apache.ivy.ant')
ivy.settings file: 'ivysettings.xml'
ivy.retrieve()
}
target(compileTest: '') {
depends 'compile'
mkdir dir: 'target/test-classes'
javac(destdir: 'target/test-classes', srcdir: 'src/test') {
classpath {
pathelement location: 'target/classes'
fileset dir: 'lib', includes: '*.jar'
}
}
}
target(test: '') {
depends 'compileTest'
mkdir dir: 'target/test-reports'
junit(printsummary: 'yes', haltonfailure: 'yes', fork: 'yes') {
classpath {
... build.gant
pathelement location: 'target/classes'
pathelement location: 'target/test-classes'
fileset dir: 'lib', includes: '*.jar'
}
formatter type: 'plain'
formatter type: 'xml'
batchtest(todir: 'target/test-reports', fork: 'yes') {
fileset dir: 'target/test-classes'
}
}
}
setDefaultTarget 'package'
65. usePlugin 'java'
sourceCompatibility = 1.5
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
build.gradle
dependencies {
testCompile 'junit:junit:4.7'
}
67. Modularisation: Grapes
// Google Collections example
import com.google.common.collect.HashBiMap
@Grab(group='com.google.collections',
module='google-collections',
version='1.0-rc1')
def getFruit() {
[ grape:'purple',
lemon:'yellow',
orange:'orange' ] as HashBiMap
}
assert fruit.lemon == 'yellow'
assert fruit.inverse().yellow == 'lemon'
68. Modularisation: OSGi
This is Apache Sling in five bullets:
• REST based web framework
• Content-driven, using a JCR content repository
• Powered by OSGi
• Scripting inside, multiple languages See also: Grails JCR plugin
• Apache Open Source project
See also: http://hamletdarcy.blogspot.com
70. ©
Better Design Patterns:
A Immutable...
S
E // ...
R
• Java Immutable Class
@Override
T public boolean equals(Object obj) {
if (this == obj)
2 – As per Joshua Bloch
public final class Punter {
private final String first;
return true;
if (obj == null)
Effective Java
0 private final String last; return false;
0 if (getClass() != obj.getClass())
public String getFirst() { return false;
6 return first; Punter other = (Punter) obj;
- } if (first == null) {
2 if (other.first != null)
0 public String getLast() { return false;
return last; } else if (!first.equals(other.first))
0 } return false;
9 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; }
}
// ...
AUG 2009 - 70
71. ©
...Better Design Patterns:
A Immutable... boilerplate
S
E // ...
• Java Immutable Class
R @Override
T public boolean equals(Object obj) {
if (this == obj)
public final class Punter { return true;
2 private final String first; if (obj == null)
0 private final String last; return false;
0 if (getClass() != obj.getClass())
public String getFirst() { return false;
6 return first; Punter other = (Punter) obj;
- } if (first == null) {
2 if (other.first != null)
0 public String getLast() { return false;
return last; } else if (!first.equals(other.first))
0 } return false;
9 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; }
}
// ...
AUG 2009 - 71
72. ©
...Better Design Patterns:
A Immutable
S
E
R
T
2
0
0 @Immutable class Punter {
6
- String first, last
2
0 }
0
9
AUG 2009 - 72
73. ©
Better Design Patterns:
A Singleton
S
E
R class Calculator {
T def total = 0
2 def add(a, b) { total++; a + b }
0 }
0
6
- def INSTANCE = new Calculator()
2
0 Calculator.metaClass.constructor = { -> INSTANCE }
0
9
def c1 = new Calculator()
def c2 = new Calculator() @Singleton(lazy=true)
class X {
assert c1.add(1, 2) == 3 def getHello () {
assert c2.add(3, 4) == 7 "Hello, World!"
}
assert c1.is(c2) }
assert [c1, c2].total == [2, 2]
println X.instance.hello
AUG 2009 - 73
74. Better Design Patterns: Delegate…
©
public Date getWhen() {
import java.util.Date;
A return when;
S }
E public class Event {
R private String title;
public void setWhen(Date when) {
T private String url;
this.when = when;
private Date when;
2 }
0
0 public String getUrl() {
public boolean before(Date other) {
6 return url;
return when.before(other);
- }
2 }
0
public void setUrl(String url) {
0 public void setTime(long time) {
9 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);
}
}
// ...
// ...
AUG 2009 - 74
75. …Better Design Patterns: Delegate…
©
public Date getWhen() {
import java.util.Date;
A return when;
S boilerplate }
E public class Event {
R private String title;
public void setWhen(Date when) {
T private String url;
this.when = when;
private Date when;
2 }
0
0 public String getUrl() {
public boolean before(Date other)
6 return url;
{
- }
2 return when.before(other);
0 }
0
public void setUrl(String url) {
9 this.url = url;
public void setTime(long time) {
}
when.setTime(time);
}
public String getTitle() {
return title;
public long getTime() {
}
return when.getTime();
}
public void setTitle(String title)
{
public boolean after(Date other) {
this.title = title;
return when.after(other);
}
}
// ...
// ...
AUG 2009 - 75
76. …Better Design Patterns: Delegate
©
A
S
E
R
T class Event {
2
String title, url
0
0
@Delegate Date when
6 }
-
2
0
0 def gr8conf = new Event(title: "GR8 Conference",
9
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)
AUG 2009 - 76
78. © More Information: on the web
A
S
• Web sites
E
R
T
– http://groovy.codehaus.org
2 – http://grails.codehaus.org
0
0 – http://pleac.sourceforge.net/pleac_groovy (many examples)
6
- – http://www.asert.com.au/training/java/GV110.htm (workshop)
2
0 • Mailing list for users
0
9 – user@groovy.codehaus.org
• Information portals
– http://www.aboutgroovy.org
– http://www.groovyblogs.org
• Documentation (1000+ pages)
– Getting Started Guide, User Guide, Developer Guide, Testing
Guide, Cookbook Examples, Advanced Usage Guide
AUG 2009 - 78
79. ©
More Information: Groovy in Action
A
S
E
R
T
2
0
0
6
-
2
0
0
9
Second edition of GinA, ‘ReGinA’ now under development
AUG 2009 - 79