Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

Industrial Strength Groovy - Tools for the Professional Groovy Developer: Paul King

  • 11,573 views
Uploaded on

 

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
11,573
On Slideshare
11,330
From Embeds
243
Number of Embeds
6

Actions

Shares
Downloads
230
Comments
0
Likes
16

Embeds 243

http://www.chfstudio.com 206
http://www.slideshare.net 21
http://chfstudio.tumblr.com 11
http://translate.googleusercontent.com 2
http://www.slideee.com 2
http://www.linkedin.com 1

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Industrial Strength Groovy Tools for the Professional Groovy Developer Dr Paul King ASERT Brisbane, Australia
  • 2. 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
  • 3. 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
  • 4. 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 } }
  • 5. Testing: TestNG import ... public class SimpleTest { @BeforeClass public void setUp() { // code invoked when test is created } @Test(groups = [ quot;fastquot; ]) public void aFastTest() { System.out.println(quot;Fast testquot;); } @Test(groups = [ quot;slowquot; ]) public void aSlowTest() { System.out.println(quot;Slow testquot;); } }
  • 6. Mocking: EasyMock import org.easymock.EasyMock mockControl = EasyMock.createStrictControl() mockReverser = mockControl.createMock(Reverser.class) storer = new JavaStorer(mockReverser) testStorage() def testStorage() { expectReverse(123.456, -123.456) expectReverse('hello', 'olleh') mockControl.replay() checkReverse(123.456, -123.456) checkReverse('hello', 'olleh') mockControl.verify() } def expectReverse(input, output) { EasyMock.expect(mockReverser.reverse(input)).andReturn(output) } def checkReverse(value, reverseValue) { storer.put(value) assert value == storer.get() assert reverseValue == storer.getReverse() }
  • 7. Testing: Instinct 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 } @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
  • 8. Mocking: MockFor 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 } // 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() }
  • 9. Mocking: Gmock ...  Method mocking: mockLoader.load(quot;fruitquot;).returns(quot;applequot;)  Exception mocking: mockLoader.load(quot;unknownquot;).raises(new RuntimeException())  Stub mocking: mockLoader.load(quot;fruitquot;).returns(quot;applequot;).stub()  Static method mocking: mockMath.static.random().returns(0.5)  Property mocking: mockLoader.name.returns(quot;loaderquot;)  Constructor mocking: def mockFile = mock(File, constructor('/a/path/file.txt'))  Partial mocking: mock(controller).params.returns([id: 3])  Times expectation: mockLoader.load(quot;fruitquot;).returns(quot;applequot;).atLeastOnce()  Custom matcher: mockLoader.load(match{ it.startsWith(quot;fruquot;) })  Strict ordering: ordered { ... }  Optional support for Hamcrest matcher: mockLoader.put(quot;testquot;, is(not(lessThan(5))))  GMockController if you can't extend GMockTestCase in your test
  • 10. Mocking: Gmock import org.gmock.GMockTestCase class LoaderTest extends GMockTestCase { void testLoader(){ def mockLoader = mock() mockLoader.load('key').returns('value') play { assertEquals quot;valuequot;, mockLoader.load('key') } } }
  • 11. Testing: Spock ...
  • 12. ... Testing: Spock @Speck @RunWith(Sputnik) class PublisherSubscriberSpeck { def quot;events are received by all subscribersquot;() { def pub = new Publisher() def sub1 = Mock(Subscriber) def sub2 = Mock(Subscriber) pub.subscribers << sub1 << sub2 when: pub.send(quot;eventquot;) then: 1 * sub1.receive(quot;eventquot;) 1 * sub2.receive(quot;eventquot;) } }
  • 13. Testing: EasyB ... given quot;an invalid zip codequot;, { invalidzipcode = quot;221o1quot; } and quot;given the zipcodevalidator is initializedquot;, { zipvalidate = new ZipCodeValidator() } when quot;validate is invoked with the invalid zip codequot;, { value = zipvalidate.validate(invalidzipcode) } then quot;the validator instance should return falsequot;, { value.shouldBe false }
  • 14. Testing: EasyB ... before quot;start seleniumquot;, { given quot;selenium is up and runningquot;, { // start selenium } } scenario quot;a valid person has been enteredquot;, { when quot;filling out the person form with a first and last namequot;, { selenium.open(quot;http://acme.racing.net/greport/personracereport.htmlquot;) selenium.type(quot;fnamequot;, quot;Britneyquot;) selenium.type(quot;lnamequot;, quot;Smithquot;) } and quot;the submit link has been clickedquot;, { selenium.click(quot;submitquot;) } then quot;the report should have a list of races for that personquot;, { selenium.waitForPageToLoad(quot;5000quot;) values = [quot;Mclean 1/2 Marathonquot;, quot;Reston 5Kquot;, quot;Herndon 10Kquot;, quot;Leesburg 10Kquot;] for(i in 0..<values.size()){ selenium.getText(quot;//table//tr[${(i+3)}]/tdquot;).shouldBeEqualTo values[i] } } } after quot;stop seleniumquot; , { then quot;selenium should be shutdownquot;, { // stop selenium } }
  • 15. 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”  quot;...except for the problem of too many layers of indirection“  For attributions, see http://en.wikipedia.org/wiki/Inversion_of_control
  • 16. 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) } } } }  Need to select setter,  Service locator/factory constructor, field style  Tightly coupled?  Can add complexity  Hard to test?  Manage configuration  Easy to understand?  Direct or framework  Refactoring/navigation?  Consistency/lifecycle
  • 17. 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) } }
  • 18. ... Dependency Injection: Spring import org.springframework.context.support.GenericApplicationContext import org.springframework.context.annotation.ClassPathBeanDefinitio nScanner 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
  • 19. 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() // ...
  • 20. 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]
  • 21. Coverage: Cobertura ... Ant
  • 22. ... Coverage: Cobertura ... Maven cobertura-instrument java –cp ... Command-line cobertura-report cobertura-check cobertura-merge Grails grails install-plugin code-coverage --global
  • 23. ... Coverage: Cobertura
  • 24. Remember 100% coverage rule ...  Necessary but not sufficient condition // 100%
  • 25. Remember 100% coverage rule ...  Necessary but not sufficient condition // 100% // Fail
  • 26. ... Remember 100% coverage rule  Necessary but not sufficient condition
  • 27. Code style: CodeNarc ... About 50 rules (1 broken?)
  • 28. ... Code style: CodeNarc ...
  • 29. ... Code style: CodeNarc
  • 30. Code style: IntelliJ
  • 31. Duplication: Simian ... Simian fully supports the following languages:  Java  C#  C++ with partial support for:  C • JSP  Objective-C • ASP  JavaScript (ECMAScript) • HTML  COBOL, ABAP • XML  Ruby  Lisp  SQL  Visual Basic  Groovy
  • 32. ... Duplication: Simian ...
  • 33. ... Duplication: Simian ...
  • 34. ... 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/W ritableRaster.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
  • 35. Documentation: GroovyDoc ... <taskdef name=quot;groovydocquot; classname=quot;org.codehaus.groovy.ant.Groovydocquot;> <classpath> <path path=quot;${mainClassesDirectory}quot; /> <path refid=quot;compilePathquot; /> </classpath> </taskdef> <groovydoc destdir=quot;${docsDirectory}/gapiquot; sourcepath=quot;${mainSourceDirectory}quot; packagenames=quot;**.*quot; use=quot;truequot; windowtitle=quot;${title} quot; doctitle=quot;${title}quot; header=quot;${title}quot; footer=quot;${docFooter}quot; overview=quot;src/main/overview.htmlquot; private=quot;falsequot;> <link packages=quot;java.,org.xml.,javax.,org.xml.quot; href=quot;http://java.sun.com/j2se/1.5.0/docs/apiquot; /> <link packages=quot;org.apache.ant.,org.apache.tools.ant.quot; href=quot;http://www.dpml.net/api/ant/1.7.0quot; /> <link packages=quot;org.junit.,junit.framework.quot; href=quot;http://junit.sourceforge.net/junit3.8.1/javadoc/quot; /> </groovydoc>
  • 36. ... Documentation: GroovyDoc
  • 37. Builds: Groovy from Ant  Need groovy jar on your Ant classpath <taskdef name=quot;groovyquot; classname=quot;org.codehaus.groovy.ant.Groovyquot; classpathref=quot;my.classpathquot;/> <target name=quot;printXmlFileNamesFromJarquot;> <zipfileset id=quot;foundquot; src=quot;foobar.jarquot; includes=quot;**/*.xmlquot;/> <groovy> project.references.found.each { println it.name } </groovy> </target>
  • 38. 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(quot;Helloquot;); } } ''') javac(srcdir:'.', includes:'Temp.java', fork:'true') java(classpath:'.', classname:'Temp', fork:'true') echo('Done') } // => // [javac] Compiling 1 source file // [java] Hello // [echo] Done
  • 39. 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 ( ) }
  • 40. 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
  • 41. 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> ...
  • 42. 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: quot;/C ${wlsDir}/bin/setWLSEnv.cmd && java ...quot; ... } ...
  • 43. 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!  Very powerful support for multi-project builds  Very powerful dependency management (based on Apache Ivy)  Full support for your existing Maven or Ivy repository infrastructure  Support for transitive dependency management without the need for remote repositories or pom.xml and ivy.xml files  Ant tasks as first class citizens  Groovy build scripts  A rich domain model for describing your build
  • 44. ... Builds: Gradle
  • 45. 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
  • 46. 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'
  • 47. 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  Apache Open Source project See also: Grails JCR plugin See also: http://hamletdarcy.blogspot.com
  • 48. Modularisation: Futures  Future ... possibly maybe ... Grapes then OSGi  Command line/Conf file grab support (thanks Danno)  Grab within a script without class/method  Grapes may have some light-weight hooks  groovyMethods  groovyStaticMethods  groovyExpandoMethods  groovyAstTransforms  groovyBuilderMetadata  Auto imports  Runner registration