testing grails: experiencies from the field

79
Testing Grails Source Code: github.com/zanthrash/loopback Experiences from the Field Colin Harrington :: SpringOne2gx 2011 Friday, October 28, 2011

Upload: spring-io

Post on 06-May-2015

7.258 views

Category:

Technology


0 download

DESCRIPTION

Speaker: Colin Harrington Testing is built into grails, but many Grails apps go untested. We'll cover how to test many different artefacts as well cover many principles that have helped lead to succesfully tested Grails application.s Testing is built into grails, but many Grails apps go untested. We'll cover how to test many different artefacts as well cover many principles that have helped lead to succesfully tested Grails application. Adapted from a talk called "Testing the Crap out of your Grails application" We'll go beyond the basics and talk about my experiences in the 10+ Grails codebases and tems that I've had the opportunity to work with.

TRANSCRIPT

Page 1: Testing Grails: Experiencies from the field

Testing Grails

Source Code: github.com/zanthrash/loopback

Experiences from the Field

Colin Harrington :: SpringOne2gx 2011

Friday, October 28, 2011

Page 2: Testing Grails: Experiencies from the field

Friday, October 28, 2011

Page 3: Testing Grails: Experiencies from the field

Friday, October 28, 2011

Page 4: Testing Grails: Experiencies from the field

Testing the CRAP out of your Grails App

Friday, October 28, 2011

Page 5: Testing Grails: Experiencies from the field

whoamiColin Harrington

@ColinHarrington

[email protected]@objectpartners.com

Friday, October 28, 2011

Page 6: Testing Grails: Experiencies from the field

“Testing is the engineering rigor of software development”

- Neal Ford

Friday, October 28, 2011

Page 7: Testing Grails: Experiencies from the field

History

Friday, October 28, 2011

Page 8: Testing Grails: Experiencies from the field

Pre Grails 1.0Testing Sucked

Friday, October 28, 2011

Page 9: Testing Grails: Experiencies from the field

Friday, October 28, 2011

Page 10: Testing Grails: Experiencies from the field

Grails 1.0

Friday, October 28, 2011

Page 11: Testing Grails: Experiencies from the field

2.0Changes &

Enhancements

Friday, October 28, 2011

Page 12: Testing Grails: Experiencies from the field

• Testing Basics

• Stubs, Spies, & Mocks

• Unit Testing

• Integration Testing

• Functional Testing

• What’s changing in Grails 2.0

• Grails Testing Ecosystem

• Experiences

Agenda

Friday, October 28, 2011

Page 13: Testing Grails: Experiencies from the field

Friday, October 28, 2011

Page 14: Testing Grails: Experiencies from the field

1st Class Citizen

• create-* scripts

• baked in the framework

• JUnit

• grails.test.*

Friday, October 28, 2011

Page 15: Testing Grails: Experiencies from the field

Class Hierarchy

Friday, October 28, 2011

Page 16: Testing Grails: Experiencies from the field

Running Tests All tests grails test-app

Failed test grails test-app -rerun

Specific test class grails test-app PresentationController

Specific test case grails test-app PresentationController.testFoo

Only unit test grails test-app unit:

Only integration test grails test-app integration:

Only Controller test grails test-app *Controller

Only test in a package grails test-app com.foo.*

Show output grails test-app -echoOut -echoErr

Friday, October 28, 2011

Page 17: Testing Grails: Experiencies from the field

Phases & Types

Phases

unit

integration

functional

other

Types

unit

integration

functional

spock

custom

etc.

:http://ldaley.com/post/615966534/custom-grails-test

Friday, October 28, 2011

Page 18: Testing Grails: Experiencies from the field

Unit Testing• No I/O

• Prove the basic correctness of the system

• State Testing

• Collaboration Testing

‣ Test Behavior NOT Implementation

Friday, October 28, 2011

Page 19: Testing Grails: Experiencies from the field

void testAddSucceedsWhenSpeakerIsFound() {controller.params.title = "Test Presentation"def user = new User(id:1)

controller.springSecurityService = [currentUser: user] controller.accessCodeService = [createFrom: {title, event -> "abcd1234"}] mockDomain Presentation

controller.add()

controller.with{ assert redirectArgs.size() == 2 assert redirectArgs.controller == "speaker" assert redirectArgs.action == "index" assert flash.message == "'Test Presentation' added with access code: abcd1234" }}

Friday, October 28, 2011

Page 20: Testing Grails: Experiencies from the field

Setup and Teardown• Always call super.setUp() and super.tearDown()

• super.setUp() creates:

• mock the applicationContext

• container for loaded codecs

• container for saved meta classes

• container for errorMap (for validation errors)

• Registers Sets, Lists, Maps, Errors so can be converted to XML and JSON

Friday, October 28, 2011

Page 21: Testing Grails: Experiencies from the field

Setup and Teardown

• super.tearDown():

• removes the mocked meta-class from the GroovySystem.metaClassRegistry

• sets the original meta-class back in the GroovySystem.metaClassRegistry

• resets the mocked domain class id’s

Friday, October 28, 2011

Page 22: Testing Grails: Experiencies from the field

Test Doubles

•Stubs

•Spies

•Mocks

Friday, October 28, 2011

Page 23: Testing Grails: Experiencies from the field

Stubs

• A stand in for the real implementation with the simplest possible implementation

controller.accessCodeService = [ convertFrom: {title, event -> '1234abc'} ]

Friday, October 28, 2011

Page 24: Testing Grails: Experiencies from the field

Spies• Stand in to capture an observation point

• Useful when:

• there is no output to inspect

• only concerned that an associated method or closure is called

Friday, October 28, 2011

Page 25: Testing Grails: Experiencies from the field

Test Spy: Map with closure

void testControllerWithServiceCall() { def fooCalled = false controller.myService = [foo:{ a -> fooCalled = true}]

controller.methodThatCallsFooOnService()

assertTrue(fooCalled)}

Friday, October 28, 2011

Page 26: Testing Grails: Experiencies from the field

Test Spy: mockFor(Class, loose=true)

void testControllerWithServiceCall() { def fooCalled = false

def mockService = mockFor(MyService, true) mockService.demand.foo() { a -> fooCalled = true} controller.myService = mockService.createMock()

controller.methodThatCallsFooOnService()

assertTrue fooCalled

}

Friday, October 28, 2011

Page 27: Testing Grails: Experiencies from the field

Mocks

• mockFor()

• Used to contain the surprises & control:

• input params

• output

• method call count

• method call order

Friday, October 28, 2011

Page 28: Testing Grails: Experiencies from the field

mockFor

• strict vs. loose

• createMock()

• verify()

• AVOID doing MetaClass programming yourself!

Friday, October 28, 2011

Page 29: Testing Grails: Experiencies from the field

Strict Mocks: mockFor(Class)

void testControllerWithServiceCall() {

def mockService = mockFor(MyService) mockService.demand.bar(2..2) { a -> return a + 1} controller.myService = mockService.createMock()

controller.methodThatCallsBarOnService()

assert controller.redirectArgs.action == ‘foo’

mockService.verify()

}

Friday, October 28, 2011

Page 30: Testing Grails: Experiencies from the field

Testing Domain Classes

• State Testing

• Constraint Testing

• mockForConstraintsTest()

• Domain Expectations Plugin

Friday, October 28, 2011

Page 31: Testing Grails: Experiencies from the field

mockForConstraintsTest()

• Use in a GrailsUnitTestCase

• Think as double entry book keeping

• Signatures:

• mockForConstraintsTest( Domain )

• mockForConstraintsTest( Domain, [new Domain()]

✴ list param is used to test ‘unique’ constraint

Friday, October 28, 2011

Page 32: Testing Grails: Experiencies from the field

mockForConstraintsTests(Class)

void testEventNameShouldNotValidateIfNull() { def event = new Event()

assertFalse event.validate() assertTrue event.hasErrors()

assert event.errors['name'] == 'nullable'}

Friday, October 28, 2011

Page 33: Testing Grails: Experiencies from the field

Domain Expectations Plugin

• Designed to TDD your domain constraints

• Uses a GORM-like dynamic finder syntax

• expect[FieldName][Suffix]

• eg: user.expectAddressIsNotNullable

• Or a natural language like syntax

• eg: user.“address is not nullable”

Friday, October 28, 2011

Page 34: Testing Grails: Experiencies from the field

Domain Expectations Example

void testEventNameConstraints() { Expectations.applyTo Event

def event = new Event()

event.expectNameIsNotNullable() event.expectNameIsNotBlank() event.expectNameHasMaxSize(40) event.expectNameHasAValidator()

event.expectNameIsValid(“jojo”)event.expectNameIsNotValid(“Bob”, ‘validator’)

}

Friday, October 28, 2011

Page 35: Testing Grails: Experiencies from the field

Friday, October 28, 2011

Page 36: Testing Grails: Experiencies from the field

Testing Controllers

• ControllerUnitTestCase (1.3.x)

• Mocking Domain Classes

• Mocking Services

• Mocking Grails Config

Friday, October 28, 2011

Page 37: Testing Grails: Experiencies from the field

ContollerUnitTestCase

• Call super.setUp()

• Inspects the name of your test and instantiates an instance of your Controller Class and assigns it to the “controller” field.

• mocks out all the stuff in the controller class and adds helper methods for testing

Friday, October 28, 2011

Page 38: Testing Grails: Experiencies from the field

What’s Mocked Out?Controller Methods

Helper MethodsHelper Methods

redirect getRedirectArgs getResponse

forward getForwardArgs getSession

chain getChainArgs getParams

render getRenderArgs getFlash

withFormat getTemplate getChainModel

withForm getModelAndView getActionName

log setModelAndView getControllerName

getRequest getServletContextFriday, October 28, 2011

Page 39: Testing Grails: Experiencies from the field

Mocking Domain Objects

• mockDomain(Person)

• mockDomain(Person, [new Person()])

‣ used to simulate existing saved records

Friday, October 28, 2011

Page 40: Testing Grails: Experiencies from the field

What you get

findAll load validate delete

findAllWhere getAll getErrors discard

findAllBy* ident hasErrors refresh

findBy* exists setErrors attach

get count clearErrors addTo*

read list save removeFrom*

create

= don’t do anything in testingFriday, October 28, 2011

Page 41: Testing Grails: Experiencies from the field

• String based HQL queries

• Composite Identifiers

• Dirty checking methods

• Direct interaction with Hibernate

What you don’t get

Friday, October 28, 2011

Page 42: Testing Grails: Experiencies from the field

Controller Unit Test Example

void testAddSucceedsWhenSpeakerIsFound() { controller.params.title = "Test Presentation" def user = new User(id:1) controller.springSecurityService = [currentUser: user] controller.accessCodeService = [createFrom: {title, event -> "abcd1234"}] mockDomain Presentation

controller.add()

controller.with{ assert redirectArgs.size() == 2 assert redirectArgs.controller == "speaker" assert redirectArgs.action == "index" assert flash.message == "'Test Presentation' added with access code: abcd1234" }}

Friday, October 28, 2011

Page 43: Testing Grails: Experiencies from the field

Mocking Grails Config

mockConfig(''' braintree { merchantKey = "abcd1234" }''')

With String

With actual Config.groovy file *

mockConfig( new File('grails-app/conf/Config.groovy').text)

* violates No I/O ruleFriday, October 28, 2011

Page 44: Testing Grails: Experiencies from the field

Testing URL Mappings

• GrailsUrlMappingsTestCase

• UrlMappingsUnitTestMixin (2.0+)

• Forward, Reverse, View and Response Code mappings

Friday, October 28, 2011

Page 45: Testing Grails: Experiencies from the field

UrlMappings.groovyclass UrlMappings {! static mappings = {! ! "/$controller/$action?/$id?"{! ! ! constraints {! ! ! ! // apply constraints here! ! ! }! ! }! ! "/login"(controller: 'login', action:'auth')

! ! "/comment/$action/$accessCode"(controller: 'comment')

! ! "/"(controller:'speaker', action:"index")! ! "500"(view:'/error')! ! "404"(view:'/error404')! }}

Friday, October 28, 2011

Page 46: Testing Grails: Experiencies from the field

Testing URLMappings

void testLoginPage() { assertUrlMapping(“/login”, controller:‘login’, action: ‘auth’)}

void testErrorPages() { assertUrlMapping(500, view: ‘error’) assertUrlMapping(404, view: ‘error404’)}

void testSiteRoot() { assertUrlMapping(‘/’, controller: ‘speaker’, action: ‘index’)}

Friday, October 28, 2011

Page 47: Testing Grails: Experiencies from the field

Testing URLMappingsvoid testCommentMappings() {! assertUrlMapping("/comment/code/12345", controller: 'comment', action: 'code') {! ! accessCode = "12345"! }! assertUrlMapping("/comment/post/12345", controller: 'comment', action: 'post') {! ! accessCode = "12345"! }}

void testDefaultControllerActionIdMapping() {! assertUrlMapping("/event/show/3210", controller: 'event', action: 'show') {! ! id = 3210!!! }! assertUrlMapping("/presentation/show/125", controller: 'presentation', action: 'show') {! ! id = 125! }}

Friday, October 28, 2011

Page 48: Testing Grails: Experiencies from the field

Testable Mappings

• Forward

• Reverse

• Status Code

• Views

Friday, October 28, 2011

Page 49: Testing Grails: Experiencies from the field

static mappings = {

"/product/$id"(controller: "product") { action = [GET: "show", PUT: "update", DELETE: "delete", POST: "save"] }!}

No HTTP Methods support until 2.0

Friday, October 28, 2011

Page 50: Testing Grails: Experiencies from the field

Friday, October 28, 2011

Page 51: Testing Grails: Experiencies from the field

Testing Taglibs

• unit or integration

• TagLibUnitTestCase

• GroovyPagesTestCase

Friday, October 28, 2011

Page 52: Testing Grails: Experiencies from the field

Unit Testing TagLibsclass LoopbackTagLibTests extends TagLibUnitTestCase {! protected void setUp() {! ! super.setUp()! }

! void testFixedWidthIpDefaultsTag() {! ! tagLib.fixedWidthIp(null, null)! ! assertEquals "................", tagLib.out.toString()! }

! void testFixedWidthIpParameters() {! ! tagLib.fixedWidthIp(ip: '127.0.0.1', width: '20', pad: '=', null)! ! assertEquals "127.0.0.1===========", tagLib.out.toString()

! ! shouldFail {! ! ! tagLib.fixedWidthIp(ip: '127.0.0.1', width: 'not-a-number', pad: '=', null)! ! }! }}

Friday, October 28, 2011

Page 53: Testing Grails: Experiencies from the field

Integration Testing TagLibs

import grails.test.*

class LoopbackTagLibGSPTests extends GroovyPagesTestCase {

! void testFixedWidthIp() {! ! def template = '<g:fixedWidthIp ip="127.0.0.1" width="21" pad="-"/>'

! ! assertOutputEquals('127.0.0.1------------', template)! }

}

Friday, October 28, 2011

Page 54: Testing Grails: Experiencies from the field

Testing GSPs

• Integration Tests

• GroovyPagesTestCase

• Views

• Templates

• assertOutputEquals(expected, template, model)

Friday, October 28, 2011

Page 55: Testing Grails: Experiencies from the field

Integration Testing

• Domain Objects. Grails vs Rails

• Services

• Plugin utilization

• Rendering of Views

slower than unit testsFriday, October 28, 2011

Page 56: Testing Grails: Experiencies from the field

Testing Services

• Autowired Beans

• Transactionality

• Full Database interactions

• Fixtures + Build Test Data

Friday, October 28, 2011

Page 57: Testing Grails: Experiencies from the field

Functional Testing

• Testing from the outside

Fully functional app

Real database

Real HTTP traffic

Real integrations

Geb

Canoo Webtest

Selenium RC

EasyB

Fitness

BDD

Friday, October 28, 2011

Page 58: Testing Grails: Experiencies from the field

Geb + Spockimport geb.spock.GebSpec

class MySpec extends GebSpec {

def "test something"() { when: go "/help"

then: $("h1").text() == "Help" }

}

Friday, October 28, 2011

Page 59: Testing Grails: Experiencies from the field

Spock

• Highly expressive specification language

• Given ➡ When ➡ Then

• Drop in replacement for Testing Plugin for both Unit & Integration test

• Simple but detailed assertions

• Parameterized testing with wiki like syntax

Friday, October 28, 2011

Page 60: Testing Grails: Experiencies from the field

Testing with Spock: Parallel Hierarchy

Testing Plugin Spock Plugin

GrailsUnitTestCase UnitSpec

ControllerUnitTestCase ControllerSpec

TagLibUnitTestCase TagLibSpec

GroovyTestCase IntegrationSpec

GroovyPagesTestCase GroovyPagesSpec

Friday, October 28, 2011

Page 61: Testing Grails: Experiencies from the field

Spock Parameterized Test Example: class ParameterizedSpec extends UnitSpec{

@Unroll("#a + #b = #c") def "summation of 2 numbers"() { expect: a + b == c

where: a |b |c 1 |1 |2 2 |1 |3 3 |2 |6 }}

Condition not satisfied:a + b == c| | | | |3 5 2 | 6 false

Friday, October 28, 2011

Page 62: Testing Grails: Experiencies from the field

Spock ControllerSpec Example: class PresentationControllerSpec extends ControllerSpec{

@Shared def user = new User(id:1)

@Unroll("add presentation for user: #testUser with title: #title") def "call to add a new presentation for logged in user"() { given:"collaborators are mocked" mockDomain Presentation mockDomain Speaker, [new Speaker(user:user)] controller.springSecurityService = [currentUser: testUser ] controller.accessCodeService = [createFrom:{title, event -> "1234abcd"}] and:"prarams are set" controller.params.event = "Event Name" controller.params.date = new Date() controller.params.title = title when:"call the action" controller.add()

then:"should be redirected to '/speaker/index'" controller.redirectArgs.controller == 'speaker' controller.redirectArgs.action == 'index'

and: "should have correct flash message" controller.flash.message == expectedFlashMessage

where: title | testUser |expectedFlashMessage 'test' | user |"'test' added with access code: 1234abcd" null | user |"Could not add null to your list at this time" 'test' | null |"Could not find a Speaker to add test to your list at this time" }}

Friday, October 28, 2011

Page 63: Testing Grails: Experiencies from the field

2.0 Changes

• Testing Hierarchy is dead

• Replaced with annotated mix-ins

• In-memory GORM implementation

• Unit Tests finally use GrailsMockHttpServletReponse

Friday, October 28, 2011

Page 64: Testing Grails: Experiencies from the field

2.0 Testing Annotations & Mixins

@TestFor(ClassUnderTest)

@Mock(Class) OR @Mock([Class1, Class2])

@TestMixin(Mixin)

- DomainClassUnitTestMixin- ControllerUnitTestMixin- FiltersUnitTestMixin- GroovyPageUnitTestMixin- ServiceUnitTestMixin- UrlMappingsUnitTestMixin

Friday, October 28, 2011

Page 65: Testing Grails: Experiencies from the field

2.0 Domain Constraints Example

@TestFor(Event)class EventTests {

@Before void setup() { mockForConstraintsTests Event }

@Test void eventNameShouldNotValidateIfNull() { def event = new Event()

assertFalse event.validate() assertTrue event.hasErrors() assert event.errors['name'] == 'nullable' }}

Friday, October 28, 2011

Page 66: Testing Grails: Experiencies from the field

2.0 Controller Example@TestFor(PresentationController)@Mock([User, Speaker, Presentation])class PresentationControllerTests {

User user

@Before void setUp() { controller.params.title = "Test Presentation" user = new User(id:1) new Speaker(user:user).save(validate: false) }

@Test void testAddSucceedsWhenSpeakerIsFound() { controller.springSecurityService = [currentUser: user] controller.accessCodeService = [createFrom: {title, event -> "abcd1234"}]

controller.add()

assert response.redirectedUrl == '/speaker/index' assert flash.message == "'Test Presentation' added with access code: abcd1234" }}

Friday, October 28, 2011

Page 67: Testing Grails: Experiencies from the field

Friday, October 28, 2011

Page 68: Testing Grails: Experiencies from the field

Ingredients for Success

• Jenkins

• Git / VCS

• Servers

Friday, October 28, 2011

Page 69: Testing Grails: Experiencies from the field

Tools

Friday, October 28, 2011

Page 70: Testing Grails: Experiencies from the field

Plugins

• Spock (http://code.google.com/p/spock/)

• Spock Plugin (http://www.grails.org/plugin/spock)

• Geb (http://geb.codehaus.org/latest/index.html)

• Domain Expectation Plugin (http://www.grails.org/plugin/domain-expectations)

• Build Test Data Plugin (http://www.grails.org/plugin/build-test-data)

• Remote Control Plugin (http://www.grails.org/plugin/remote-control)

• Fixtures Plugin (http://www.grails.org/plugin/fixtures)

• Greenmail Plugin (http://www.grails.org/plugin/greenmail)

Friday, October 28, 2011

Page 71: Testing Grails: Experiencies from the field

Perspective

Friday, October 28, 2011

Page 72: Testing Grails: Experiencies from the field

Spectrum

• Run-before-checkin

• Continuous Testing (on each checkin)

• Scheduled Testing

• Load Testing?

• Manual Testing

• Regression Testing

Friday, October 28, 2011

Page 73: Testing Grails: Experiencies from the field

The Future is now

• Git + Gerrit + Jenkins

http://alblue.bandlem.com/2011/02/gerrit-git-review-with-jenkins-ci.html

http://www.infoq.com/articles/Gerrit-jenkins-hudson

Friday, October 28, 2011

Page 74: Testing Grails: Experiencies from the field

Considerations:• Passing Tests != stable != quality // Think!

• Test your Migrations { structure, data }

• Finite Resources

• Developers fight with ‘Done’

• Lost in the weeds.

• lack of skill with the tools, (its OK)

• Professional Yak shaving

Friday, October 28, 2011

Page 75: Testing Grails: Experiencies from the field

Code Coverage• Focus on test quality NOT test coverage

void testWhyCodeCoverageNumbersAreCrap() { def service = new AccessCodeService() service.metaClass.methods.*name.sort().unique().each { try{ service.invokeMethod(it, ['blah', 'blah']) } catch (Exception ex ){ //eat it } } }

Friday, October 28, 2011

Page 76: Testing Grails: Experiencies from the field

Now what?

• Download the Testing Cheat Sheet

• http://bit.ly/GRAILSTESTING

• Look into Geb and Spock

• Take the small steps forward!

Source Code: github.com/zanthrash/loopbackFriday, October 28, 2011

Page 77: Testing Grails: Experiencies from the field

Questions ?

Thanks

Friday, October 28, 2011

Page 78: Testing Grails: Experiencies from the field

Friday, October 28, 2011

Page 79: Testing Grails: Experiencies from the field

Thank you

Friday, October 28, 2011