atom feed21 messages in org.codehaus.grails.userRe: [grails-user] how thoroughly to t...
FromSent OnAttachments
CoreyFeb 19, 2008 4:03 pm 
Geoff LaneFeb 19, 2008 4:17 pm 
Victor SaarFeb 19, 2008 4:51 pm 
Erick EricksonFeb 19, 2008 5:06 pm 
CoreyFeb 19, 2008 5:53 pm 
CoreyFeb 19, 2008 5:59 pm 
CoreyFeb 19, 2008 6:48 pm 
Robert FletcherFeb 20, 2008 1:31 am 
CoreyFeb 20, 2008 11:23 am 
CoreyFeb 20, 2008 11:35 am 
Ray TayekFeb 20, 2008 12:18 pm 
CoreyFeb 20, 2008 12:38 pm 
Geoff LaneFeb 20, 2008 1:36 pm 
CoreyFeb 20, 2008 2:35 pm 
Ray TayekFeb 20, 2008 5:16 pm 
Andreas HeydlerFeb 20, 2008 5:33 pm 
Marcos Silva PereiraFeb 20, 2008 5:41 pm 
CoreyFeb 20, 2008 6:03 pm 
CoreyFeb 20, 2008 6:13 pm 
Marcos Silva PereiraFeb 20, 2008 6:24 pm 
Ray TayekFeb 21, 2008 3:31 pm 
Subject:Re: [grails-user] how thoroughly to test domain classes?
From:Corey (core@qwest.net)
Date:Feb 20, 2008 11:23:39 am
List:org.codehaus.grails.user

On Wednesday 20 February 2008 02:31:47 am Robert Fletcher wrote:

On Feb 20, 2008 12:04 AM, Corey <core@qwest.net> wrote:

My first initial feeling was that I'd start out with 4 basic tests - the crud operations:

testInsert() testSelect() testUpdate() testDelete()

There's been some good advice on this thread that I won't repeat, but I think one thing is worth mentioning. If you structure your tests in terms of behaviour instead of 1 test per method I find it tends to be a lot more useful. What I mean is, instead of having a single testInsert() method that tries to cover the happy path and all the edge cases, consider splitting it up into things like testSimpleInsert() where you test that a basic insert with no constraint violations works and the object reads back off the database correctly, then a testInsertFailsWhenNameIsNull(), testInsertSavesAssociatedObjects(), testUpdateRemovesAssociatedObjectsFromCollection() etc. to cover edge cases, constraint violations and so on. You can then add extra tests as your class changes without having a single test method that becomes unreadable. The test method names describe the behaviour they're trying to excercise which aids understanding and you can easily add tests for edge cases as you discover them.

So - to demonstrate your advice with an approximated example:

class FooSuiteTests extends GroovyTestCase {

FooSuite cut

void setUp() {

cut = new FooSuite( name : 'aFooSuite', description : 'for testing purposes', isSystem : false, tests : [ new Foo(name:'foo1'), new Foo(name:'foo2') ] ) }

// sub-optimal? // void testNameConstraints() {

assert cut.validate()

// blank: false cut.name = '' assertFalse cut.validate()

// nullable: false cut.name = null assertFalse cut.validate()

// maxSize: 64 cut.name = '12345678' * 8 + 1 assertFalse cut.validate()

// unique: true cut.name = 'aFooSuite' assert cut.save(flush:true) assertFalse new FooSuite( cut.properties ).validate() }

// better? // void testInvalidIfNameBlank() {

assert cut.validate()

cut.name = '' assertFalse cut.validate() }

void testInvalidIfNameNull() {

assert cut.validate()

cut.name = null assertFalse cut.validate() }

void testInvalidIfNameGreaterThan64Chars() {

assert cut.validate()

cut.name = '12345678' * 8 + 1 assertFalse cut.validate() }

void testInvalidIfNameNotUnique() {

assert cut.validate()

cut.name = 'aFooSuite' assert cut.save(flush:true) assertFalse new FooSuite( cut.properties ).validate() }

}

In the above example under the "sub-optimal?" comment, I show basically what I was doing late last night after going through this thread and trying a few different similar approaches until I settled on something the "felt right" for me.

Under the "better?" comment, I went and kind of mixed your advice with the advice from others in this thread: I'm separating my tests more granularly as you suggest, however I'm not saving and verifying actual inserts because it was advised that doing so is testing the framework more than it is testing my code.

I personally much prefer the more granular, focused tests - although a bit less convenient when writing them, they seem more useful when ran, and it's easier to immediately see exactly what when wrong when they fail - vs. with the broader tests (i.e. testNameConstraints), if it fails, I need to scan for the line-number of where it failed in the test report to pinpoint where it failed. I'm still not sure as to whether I should make it habit to perform actual saves and then verify the results in all cases (such as in your Thing example), or just certain ones like perhaps for collections and/or custom class/domain-specific crud operations/methods.

Thoughts?

( thanks to everyone who've provided input in this thread - it's all been extremely helpful )

I've found when testing domain classes that flushing and clearing the hibernate session is really useful in terms of proving that your code is really doing what you think it is. Particularly when dealing with associations sometimes the stale state of the object graph in the hibernate session can be misleading. I tend to do things like:

def thing = new Thing() // populate thing's properties assert thing.validate() assert thing.save(flush: true)

sessionFactory.currentSession.clear()

thing = Thing.get(thing.id) // test that thing's properties look how they should

That's been really invaluable as some problems don't become apparent until you actually write to the database and read back from it. Integration tests run in a transaction that rolls back on tear down so flushes won't happen unless you force them.

--------------------------------------------------------------------- To unsubscribe from this list, please visit:

http://xircles.codehaus.org/manage_email