Software has two ingredients: opinions and logic (=programming). The second ingredient is rare and is typically replaced by the first.
I blog about code correctness, maintainability, testability, and functional programming.
This blog does not represent views or opinions of my employer.

Saturday, August 2, 2014

I don't like Hibernate (and Grails), PART 1

My goal here is to write about Hibernate and GORM functionality that you could call 'It is not a bug, it is a feature' and which yield very negative and often non trivial consequences.
These topics are (I believe) documented nowhere and surprise every developer I know.
I also want to comment on what are the best ways to test for these.

Testing Grails Unit vs. Integration vs. Functional:
Is there some sort of a 'pyramid' consensus that you need a lot of unit tests, many in integration test,
and maybe just some (if any) functional tests?
Grails creates appearance of a test friendly framework with big focus on unit tests.
Out of the box, Grails will put test/unit and test/integration folder in your project
(You really should have test/functional there as well, but you need to work a bit harder to get it).
For each domain class you add to your project Grails will, by default, create an empty Unit Test for it (not integration, only unit).

Grails Unit Tests do not interact with database and anything related to hibernate/GORM has to be mocked. This makes Unit Tests a wrong choice for uncovering problems related to how hibernate/GORM is used/misused in your project.
I find this a bit ironic.  Here are two well know Hibernate gotchas: automatic saving of domains objects and LazyInitializationException.   Talking about these 2 feels like beating a dead hoarse so I will not.
I just want to point out that your domain objects maybe saved when they should not be (do you have non-transactional validation logic outside of your domain class?) or your views may throw LazyInitializationException (did you forgot to hydrate your model and transaction has rolled back?) but all your Grails unit test will pass, your Grails integration tests will also pass.  Did you write Functional Tests?

You maybe tempted to think about Unit Tests as simply tests that exercise atomic blocks of your code in isolation.  If this is your thinking you maybe putting your 'Unit Tests' into tests/integration folder and still think of them as 'unit'.  I admit to thinking this way.

There is an issue with this too.  Side effects do not work well with unit tests and, well, Grails is very much a side-effect framework.  Consider this high level hypothetical: you have programmed 'component A' and have a full logical unit coverage for it (if that is even possible) then you coded 'component B' and wrote full coverage for it as well.   You pat yourself on the back for having fully covered everything.
But wait... , if executing A creates side-effects which impact logic in B, then, well, you coverage has leaked on you.  You may be confident that you wrote a well tested app, but you really did not.  I think this is not well understood because, if it was, I would expect much more interest in FP.

My take on testing for possible Hibernate gotchas is to reverse the pyramid:  Focus on Functional Tests,  write more integration than unit tests.  But I will probably address it in more details in the future.

Example:
(I may reuse this simple Domain Class in future posts):
 class BankAccount {
    String name
    BankBranch branch
    Float amount
  
    static constraints = {
        name unique: true
    }
 }

Here is my 'component B' logic: (Assume properly defined equals/hasCode methods are in place  - not shown.)

 assert BankAccount.findAllByBranch(myBranch).every{
    it.branch == myBranch
 }

Would you think the above statement is guaranteed to be true?
If you are a sane person you will answer yes, this assertion has to hold no matter what.
If you have worked with Hibernate and/or GORM long enough then you (have lost your sanity by now and) can figure out what code to add in front of the above code block to have it break.

I call it 'repeatable finder problem' and I will write more about it next time.

3 comments:

  1. I agree, we were auto-creating integration tests for a while (I made that change) but reverted to unit tests when GORM was decoupled from Hibernate for NoSQL support. An in-memory GORM implementation was created then, and the new unit tests use this. But I've always advocated that you test persistence against a real database, ideally a test instance of the same db that you use in prod, but worst-case, the H2 database.

    Since only the unique constraint actually accesses the database, it's also generally fine to use unit tests for constraint tests, although I'd still tend to leave those in integration tests for consistency.

    Using GORM mocking is ok when testing controllers and services, because if you've already properly tested the domain layer, it's fine to mock out collaborators to focus on the class under test. But that should be the only use for in-memory GORM.

    btw - It's "Grails", not "GRAILS". It's not an acronym, or intentionally spelled like you're yelling. See grails.org for examples of usage.

    ReplyDelete
    Replies
    1. Thank you, I corrected the casing. Did not know.

      My biggest issue with unit tests is that is it easy to misuse hibernate, in many cases just adding extra query can break my code. Unit tests have no chance of uncovering these type of errors. This is my motivation for these posts: show how the code can break by making a very 'innocent' change.

      Thanks for taking time to read and comment on my post!

      Delete