cut + grep + awk + sed in action; prerequisite: Git repository:
git status -s | \
cut -c4- | \
grep "$test_type.*[Test|Spec]" | \
awk 'BEGIN { FS = "/" } ; { print ($(NF)) }' | \
sed 's/\.groovy//' | tr '\n' ' '
cut + grep + awk + sed in action; prerequisite: Git repository:
git status -s | \
cut -c4- | \
grep "$test_type.*[Test|Spec]" | \
awk 'BEGIN { FS = "/" } ; { print ($(NF)) }' | \
sed 's/\.groovy//' | tr '\n' ' '
If you wrote multithreaded code in Groovy/Grails, I’m sure you stumbled upon the GPars library. It simplifies parallel processing – sometimes it’s as easy as wrapping a code fragment with a GPars closure, changing the iteration method (in our example from each to eachParallel) and passing the closure to a GPars pool. The library implementation handles the executor pool creation and adds new methods to collections.
Whenever I deliberately add multithreading, I always separate responsibilities (processing logic and concurrency handling). I wrap the single-threaded class with a new one, handing the parallel execution (creating a pool, managing threads, handling cancellation, etc.):
class MultithreadedProcessorService {
ProcessorService processorService
def threadPoolSize = ConfigurationHolder.config.threadPool.size
void processMultithreaded(batches) {
GParsPool.withPool(threadPoolSize) {
batches.eachParallel { batch ->
processorService.process(batch)
}
}
}
}
class ProcessorService {
void process(batch) {
// some processing
}
}
To test the multithreaded class you can you use Spock interactions. As opposed to Groovy and Grails mocks, they are thread-safe and you won’t get any weird and unstable results:
class MultithreadedProcessorServiceSpec extends UnitSpec {
private MultithreadedProcessorService multithreadedProcessorService
// Using Spock mocks because they are thread-safe
private ProcessorService processorService = Mock(ProcessorService)
def setup() {
mockConfig('threadPool.size=2')
multithreadedProcessorService = new MultithreadedProcessorService(
processorService: processorService
)
}
def 'processes batches using multiple threads'() {
given:
def batches = [[0, 1], [2, 3, 4], [5]]
def processedBatches = markedAsNotProcessed(batches)
when:
multithreadedProcessorService.processMultithreaded(batches)
then:
batches.size() * processorService.process({ batch ->
processedBatches[batch] = true
batch in batches
})
assertAllProcessed(processedBatches)
}
private markedAsNotProcessed(batches) {
batches.inject([:]) { processed, batch ->
processed[batch] = false
processed
}
}
private void assertAllProcessed(batches) {
assert batches*.value == [true] * batches.size()
}
}
In our projects we include at least one integration test per domain class. We want to assure us that the GORM mapping is correct and the class is persistable in the target database.
The test plan is simple:
equals() but be different object referencesWe put the steps above in a base abstract specification. In concrete specifications we implement merely the factory method spawning a persistable object instance.
abstract class PersistenceSpec extends IntegrationSpec {
SessionFactory sessionFactory
protected abstract createPersistableDomainObject()
def 'persistable domain object should be able to be saved and retrieved'() {
given:
def persistableDomainObject = createPersistableDomainObject()
when:
def savedDomainObject = persistableDomainObject.save()
then:
savedDomainObject.id != null
when:
clearFirstLevelCache()
def retrievedDomainObject = persistableDomainObject.class.get(savedDomainObject.id)
then:
savedDomainObject == retrievedDomainObject
!savedDomainObject.is(retrievedDomainObject)
}
private clearFirstLevelCache() {
sessionFactory.currentSession.flush()
sessionFactory.currentSession.clear()
}
}
If you work with GORM and your objects are held in collections, you should implement equals and hashCode methods. As everything, they should be thouroughly tested if they obey equals-hashCode contract. At Osoco we created a equals-hashcode-test Grails plugin that provides a base Spock specification for that.
In your equals and hashCode tests you simply have to extend the EqualsHashCodeSpec and provide at least two methods:
createDomainObjectToCompare that will create a fresh object for the comparisonAdditionally in case of inheritance, we check whether the equals method complies with the symmetry rule. We do it in an extra feature method added to the spec:
def 'equals relation must be symmetric with inheritance'() {
given:
def content = new Content()
and:
def book = new Book()
expect:
(content == book) == (book == content)
}
You may say, in Grails 2.0 and Groovy 1.8 there are @Equals and @HashCode AST transformations and you don’t need to write tests of them. I agree to some point. I think you can trust Groovy developers and you don’t have to test the methods for reflexivity, symmetry, transivity, consistency, and ‘non-nullity’. Anyway, you have to test the annotation configuration if it includes the properties that actually account for the logical equality between objects.
In the future releases of the plugin we will surely simplify the base specification and remove some checks but we will surely continue testing both methods (with our plugin it’s dirt-cheap!)
In our application we have to consume JMS messages. We install Grails JMS plugin and write a listener for this task. To not violate SRP, our consumer receives a message and routes it to a MessageProcessorService for further processing.
class QueueListenerService {
static final QUEUE_NAME = 'aQueue'
static exposes = ['jms']
MessageProcessorService messageProcessorService
@Queue(name = QueueListenerService.QUEUE_NAME)
void onMessage(msg) {
messageProcessorService.process(msg)
}
}
MessageProcessorService is not relevant for this test; we have a stub for it:
class MessageProcessorService {
void process(msg) {
log.info("Processing message ${msg}")
}
}
We use Spock for testing. We want to verify that our listener:
In this kind of spec we have to deal with asychrony. We are going to send a message from the spec, but it will be received by another thread. We synchronize the test and listener threads by a signal (CountDownLatch) that will indicate us that the JMS message has been received and passed to the processor. We initialize the latch at the beginning of the test. Then we send the message and block until the counter reaches zero (that means, our mock has received the message).
Last but not least we want to check if the queue either has been emptied (first test method) or not (second test method). Once again we have to handle the asynchronous behaviour of JMS. After receiving the message we start to poll the queue checking if the given condition (queue empty or not ) is fulfilled. We establish a 5 second timeout – it should be enough for JMS broker to handle a processing exception (see the Timeout helper class).
So, ladies and gentlemen, the final code:
class QueueListenerServiceSpec extends IntegrationSpec {
private static final QUEUE_RECEPTION_TIMEOUT_SEC = 5
private static final QUEUE_POLL_TIMEOUT_MILLIS = 5000
private static final QUEUE_POLL_INTERVAL_MILLIS = 500
QueueListenerService queueListenerService
JmsService jmsService
private messageReceived = new CountDownLatch(1)
private messageProcessor = Mock(MessageProcessorService)
def setup() {
queueListenerService.messageProcessorService = messageProcessor
}
def 'receives a message and passes it to further processing'() {
when:
jmsService.send(QueueListenerService.QUEUE_NAME, message())
messageReceived.await(QUEUE_RECEPTION_TIMEOUT_SEC, SECONDS)
then:
1 * messageProcessor.process({ Map msg ->
messageReceived.countDown()
msg == message()
})
assertQueue(empty())
}
def 'message is returned to the queue if an exception is thrown'() {
given:
messageProcessor.process(_) >> {
messageReceived.countDown()
throw new IllegalStateException('Processing error')
}
when:
jmsService.send(QueueListenerService.QUEUE_NAME, message())
messageReceived.await(QUEUE_RECEPTION_TIMEOUT_SEC, SECONDS)
then:
assertQueue(notEmpty())
}
private message() {
[key: 'a value']
}
private void assertQueue(condition) {
def timeout = new Timeout(QUEUE_POLL_TIMEOUT_MILLIS)
while (!condition.fulfilled()) {
if (timeout.hasTimedOut()) {
throw new AssertionError(condition.describeFailure())
}
sleep(QUEUE_POLL_INTERVAL_MILLIS)
}
}
private empty() {
[
fulfilled: { jmsService.browse(QueueListenerService.QUEUE_NAME) == []},
describeFailure: 'Expected queue to be empty'
]
}
private notEmpty() {
[
fulfilled: { jmsService.browse(QueueListenerService.QUEUE_NAME) != []},
describeFailure: 'Expected queue to be NOT empty'
]
}
}
class Timeout {
private endTime
Timeout(duration) {
endTime = System.currentTimeMillis() + duration
}
def hasTimedOut() {
timeRemaining() <= 0
}
def timeRemaining() {
endTime - System.currentTimeMillis()
}
}