Integration and unit tests in large Grails project
It is usually more complicated to write unit tests due to having to deal with mock objects than integration tests in a large Grails project. This article even suggests we开发者_StackOverflow社区 can even do away with unit tests altogether and write only integration tests which I tend to agree.
The only disadvantage I see is the speed of execution for integration test as compared to same unit test.
What are your thoughts about this from your actual experience working on a large scale Grails project?
If we write a unit test that tests exactly same method and also write integration test that also tests exactly same method, is this normal way of writing tests?
What you ended up with in terms of ratio of unit tests to integrations tests in actual large Grails project?
Have you successfully completed a large Grails project without writing any tests?
I always write my tests as unit tests if possible. I do this because:
- unit tests execute quicker
- I prefer to test each component in isolation, rather than testing all the components integrated together, because this makes it easier to identify the source of an error
- the unit testing environment is simpler (e.g. no Spring application context), so there are fewer potential sources of failure that are unrelated to the test being performed
An example of where I would write an integration test is if I want to test a Spring bean that I've defined in resources.groovy
. Although I could instantiate the class and test it directly, my test would then need to know the current implementation class of that bean (which might change).
In the long-run I think it's actually more complicated to write integration tests, because the cost of maintaining them over time is higher than unit tests. Groovy/Grails has excellent support for mocking/stubbing, so the cost of mocking dependencies in unit tests is relatively low. Here's a real-world example from one of my unit tests where I:
- mock the
messageSource
Spring bean that would normally only be available in an integration test - mock two command classes such that I can call the
validate()
method, inspect the.errors
property, etc.
class MyUnitTests extends GrailsUnitTestCase {
MessageSource messageSource
protected void setUp() {
super.setUp()
// mockForConstraintsTests is a method provided by GrailsUnitTestCase
[Complex, CategoryCommand].each {mockForConstraintsTests(it)}
// 'mockMessage' will be returned by every method call on messageSource
messageSource = {Object[] args -> "mockMessage"} as MessageSource
}
}
I've worked on 3 large grails apps, and innumerable smaller ones. My current project has a mix of unit and integration tests (currently 2110 unit tests and 493 integration tests).
I've spent a bunch of time trying to improve testing speed as well as making tests more maintainable.
My integration tests are often a hybrid where if I'm testing a service, I might mock out some other services/methods being called to ensure I get the values that I want, but leave in other integration pieces to exercise HQL or database integration. To this end, I use prototype instances of what are normally singleton services so that I can muck with the service instance without polluting later tests.
I find the build-test-data plugin invaluable for creating maintainable unit tests as it lets me create test data where I explicitly populate the pieces I need and let the plugin fill in the other required details. Creating test data in integration tests is easier for me than mocking it out in unit tests.
If you do use both integration and unit tests, eventually the speed of running all of the tests serially will become an impediment. My team uses the splitTests.groovy script to spin off two separate threads, one for unit tests, one for integration tests. This exercises our tests about 40% faster. Further parallelization is possible, but we haven't gone there yet (and the current grails gant scripts are pretty nasty under the covers, I'm looking forward to the gradle rewrite in grails 2.0).
Unit tests are nice for hitting all of the conditional nooks and crannies of a method (though if you've got too many, your cyclomatic complexity is probably too high and you should refactor). Integration tests are useful for exercising database and service integration as well as helping you understand what you've broken when you change a piece of code.
I think that the refactoring courage that you get from having high test coverage is partly dependent on some of the tests being integration tests. If all you have are unit tests that don't interact with other code pieces, you won't be alerted of affected areas when you make a code change, and because groovy is a dynamic language, the compiler likely won't help you find these areas either.
精彩评论