26
Grails 1.1 Testing Tech Talk @ 2Paths July 17, 2009 By Dave Koo ©2009 - 2Paths Solutions Ltd. (www.2paths.com) 7/17/09

Grails 1.1 Testing - Unit, Integration & Functional

Embed Size (px)

DESCRIPTION

A walkthrough of the benefits, drawbacks, new features, important "gotchas" and some code samples using the testing features available in Grails 1.1.We'll be covering:- unit testing (specifically GrailsUnitTestCase and it's extensions)- integration testing- functional testing (using WebTest)

Citation preview

Page 1: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Grails 1.1 Testing

Tech Talk @ 2Paths

July 17, 2009

By Dave Koo

7/17/09

Page 2: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Agenda

Unit Testing

Integration Testing

Functional Testing

Putting It All Together

7/17/09

Page 3: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Unit Testing - Overview

unit tests enable testing small bits of code in isolation

Grails does NOT inject any surrounding infrastructure (ie. no dynamic GORM methods, database, Spring resources, bootstrap, ServletContext, etc)

7/17/09

Page 4: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Unit Testing - Benefits

find problems sooner -> you can test your code before all it's dependencies are created/available

enables safer refactoring (of individual classes)

faster to run than integration & functional tests there isn't a big speed gap early in the project (PLAID: unit ~ 10s,

integration ~ 30) but it grows as the system gets bigger

makes TDD less painful -> breaks problem into smaller pieces

provides form of API documentation & spec

7/17/09

Page 5: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Unit Testing - Drawbacks

more code to maintain & debug -> often as buggy as code under test (if written by same person :)

sometimes you have to mock a LOT of stuff before you can write your little test

doesn't catch bugs at the integration or UI layers -> can create false sense of security

7/17/09

Page 6: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Unit Testing – GrailsUnitTestCase Overview

extends GroovyTestCase to add Grails-specific mocking and make testing more convenient for Grails users

mocking is test-specific and doesn't leak from one test to another

avoids having to do a lot of MetaClass hacking (and forgetting to teardown your metaclass hacking :)

7/17/09

Page 7: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Unit Testing – GrailsUnitTestCase methods

mockFor(class, loose = false)

mockDomain(class, testInstances[ ])

mockForConstraintsTests(class, testInstances[ ])

mockLogging(class, enableDebug = false)

Code samples: http://grails.org/doc/1.1.1/guide/9.%20Testing.html#9.1%20Unit%20Testing

7/17/09

Page 8: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Unit Testing – GrailsUnitTestCase mockFor(class, loose = false)

generic mocker for mocking methods of any class

returns a control object (not the actual mock)

loose - false == strict mocking (sequence of expected method calls matters), true == loose (sequence doesn't matter)

methods - you can mock instance or static methods

demands - your expectations of the mock

range - number of times a method is expected to be called (default === 1)

closure - mock implementation of the method

strictControl.createMock() - returns an actual mock object

strictControl.verify() - verifies that all demands were actually met

7/17/09

Page 9: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Unit Testing – GrailsUnitTestCase mockDomain(class, testInstances[ ])

mocks all methods (instance, static & dynamic) on a domain class testInstances - a list of domain class instances which replaces and acts as the "database” you can call save(), findBy*(), validate(), etc on an instance of a mocked domain class inheritance can be tricky!

assume you have ParentClass, ChildA, ChildB if you mock ParentClass and pass a collection of instances containing ChildA and/or ChildB

objects to the MockDomain() method, you must ensure that you have already mocked each type of ChildX class in the instance collection. This is needed even if you NEVER call a dynamic method on any child class (such as ChildClass.list()).

always mock a child class BEFORE mocking its parent, otherwise you may get "Method Not Found" exceptions when calling dynamic methods on the child

new addition to Grails still some bugs / missing features check Grails' JIRA & mailing lists if experiencing wierd behaviour

7/17/09

Page 10: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Unit Testing – GrailsUnitTestCase other methods…

mockForConstraintsTests(class, testInstances[ ])

Stripped-down version of mockDomain that allows for assertions to validate domain class constraints

Takes the pain out of testing domain class constraints

mockLogging(class, enableDebug = false)

Adds a mock "log" property to a class. Any messages passed to the mock logger are echoed to the console.

7/17/09

Page 11: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Unit Testing – ControllerUnitTestCase

extends GrailsUnitTestCase to add mocks for: all dynamic properties & methods Grails injects into controllers (render,

redirect, model, etc)

all HTTP objects available to controllers (request, response, session, params, flash, etc)

command object (if needed)

provides an implicit "controller" property which contains all the above mocks (no need to def the controller yourself in your test class)

don't forget to mock your Domain objects as needed if the controller action you call expects a database

7/17/09

Page 12: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Unit Testing – Other Unit Tests

TagLibUnitTestCase

extends GrailsUnitTestCase to add mocks for dynamic properties & methods a TagLib expects

dbUnit

extends jUnit to allow you to test that your DB is in the expected state

prevents subsequent tests from failing due to a previous test leaving the database "dirty"

7/17/09

Page 13: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Integration Testing - Overview

used to test that an entire module or sub-system consisting of multiple classes is working correctly (in isolation)

one level below a functional test as integration tests do not test the entire stack

Grails injects all the surrounding infrastructure such as data sources & Spring beans

pretty straightforward to implement since there's generally no mocking involved

where to use integration tests??? we'll come back to this in a few mins :)

7/17/09

Page 14: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Integration Testing - Benefits

tests the real interactions between individual pieces

finds more complex bugs that unit tests can't find

faster to run than functional tests (usually)

makes large-scale refactoring safer

find bugs sooner -> doesn't require other system modules to be built

7/17/09

Page 15: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Integration Testing - Drawbacks

perhaps overused by lazy testers who don't feel like mocking :)

requires more code to be written than unit tests

doesn't find system-level or inter-module bugs

value is debatable if you have good functional tests & good unit tests.

7/17/09

Page 16: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Functional Testing - Overview

used to test functionality from an end user's perspective across all layers of the system

7/17/09

Page 17: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Functional Testing - Benefits

finds complex system-level and inter-module bugs

finds UI bugs

can be written by non-techies (depends on tool used)

provides "done when" acceptance criteria for the customer

provides a form of user documentation

makes large-scale refactoring safer

7/17/09

Page 18: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Functional Testing - Drawbacks

very slow to execute HTTP level tests

often more cumbersome to write than other types of tests

often brittle and can require a lot of maintenance (especially if tied to UI)

Usually hard to write until UI is available (and stable)

7/17/09

Page 19: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Functional Testing - Tools

Canoo WebTest & Grails Functional Testing Both wrap HtmlUnit which provides a Java API that mocks a web

browser

Canoo WebTest wrapper for WebTest's XML syntax and therefore constrained by the underlying XML syntax

Grails Functional Testing pure-groovy solution more flexible, but newer & therefore fewer features than WebTest

7/17/09

Page 20: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Functional Testing - WebTest

test organization & re-use grouping steps with ant.group calling methods from other tests

Groovy step notation uses 3 double-quotes as Groovy code delimeter (“””) accessing HtmlUnit for fine-grained test control parameter passing between app context & WebTest context

AJAX testing w/ sleep()

7/17/09

Page 21: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Functional Testing – WebTestgrouping steps with ant.group

if your test has dozens of steps, it gets hard to read the report

grouping steps with ant.group rolls them up to 1 line in the test report, which can be easily expanded/collapsed:

ant.group(description: “verify bank account info”) { invoke “someController/someAction” verifyText “some text” verifyText “more text”}

7/17/09

Page 22: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Functional Testing – WebTestcalling methods from other tests

Instead of copying & pasting the same methods from one test to another, you can call a method in another test by passing it your AntBuilder instance (ant) as a param. This ensures the test steps are executed using the calling test’s AntBuilder.

====== in CallingTestClass() ====def otherTestClass = new OtherTestClass()otherTestClass.someMethod(ant)

====== in OtherTestClass() =====def someMethod(AntBuilder ab) { ab.invoke “someController/someAction” ab.verifyText “some text”}

7/17/09

Page 23: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Functional Testing – WebTestGroovy step

If you need to execute arbitrary Groovy code in your test, use WebTest’s groovy step.

Keep in mind that this groovy step won’t have access to any Groovy variables defined in your test (such as method params)…it only has access to the properties in the AntBuilder context.

So you need to pass any needed params to the AntBuilder before entering your groovy step. Then you can retrieve them from the AntBuilder from within your step.

The following example involves:

Storing Groovy variables into AntBuilder properties Retrieving AntBuilder properties for use in the Groovy step Calling some HtmlUnit methods which aren’t available in WebTest for fine-grained test control Pausing the test for 2 seconds (very useful with AJAX tests to give the server time to respond

7/17/09

Page 24: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Functional Testing – WebTestGroovy step

test method with Groovy step to test AJAX autocomplete form input:

def setAJAXInputField(elementId, elementValue, AntBuilder ab) {

ab.storeProperty(property: 'elementId', value: elementId)

ab.storeProperty(property: 'elementValue', value: elementValue)

ab.groovy ""” def elementId = step.webtestProperties.elementId

def elementValue = step.webtestProperties.elementValue def document = step.context.currentResponse.documentElement

def element = document.getHtmlElementById(elementId)

element.focus()

element.type(elementValue)

Thread.sleep(2000)“””

}

7/17/09

Page 25: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Putting It All Together - Running tests

test-app -> all tests

test-app -unit -> all unit tests

test-app -integration -> all integration tests

test-app -unit AnnualReport AssignmentHandler -> 2 unit tests & all integration tests

test-app AnnualReport AssignmentHandler WorkflowManagerService -> 2 unit tests & 1 integration test

run-webtest -> runs all tests using new server instance

run-webtest -nostart -> runs all tests using existing Tomcat instance (much faster!!!)

run-webtest ClassName TestName -> run TestName using existing Tomcat instance

7/17/09

Page 26: Grails 1.1 Testing - Unit, Integration & Functional

©2009 - 2Paths Solutions Ltd. (www.2paths.com)

Putting It All Together - Summary

Unit testing - taglibs, controllers, services, domain classes (including contraints), other delegates & "helpers", even your data (dbUnit)

Integration testing now that Grails 1.1 supports unit testing of controllers with

ControllerUnitTestCase, that seems to be the preferred way of testing them (rather than using integration tests).

This makes sense since controllers should not be doing the heavy lifting in a Grails app.

The same holds true for TagLib testing with TagLibUnitTestCase. this leaves Services as the most valuable thing to cover with integration

tests (but only after you’ve written unit tests for them ) Functional testing - smoke tests, acceptance tests, regression tests Manual testing by humans - look & feel, anything not covered above

7/17/09