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

  • 7,671 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
7,671
On Slideshare
0
From Embeds
0
Number of Embeds
3

Actions

Shares
Downloads
230
Comments
0
Likes
16

Embeds 0

No embeds

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