Automatically test your dirty Grails classes

A script to grab the list of modified files from git, parse their filenames, and pass those names for testing in grails test-app..

Igor Shults

Summary

When refactoring code in bulk, I occasionally run into a scenario where it’s simpler to make the change all at once and run the tests afterwards than it is to test after every iteration of the change. Some examples include renaming method or variable names (or anything with an IDE’s find-and-replace feature), changing internationalized text, or adding/removing a method call. However, the full test suite for a project can take a very long time to run, so I initially test only the modified (and related) classes.  I figured it would be nice to have a script automatically test the modified files for me, so I tried my hand at writing one.

Granted, this is my first bash script so I may have missed some standards/conventions, but I did my best to keep up with best practices. The script will grab the list of modified files from git, parse their “base” file names, and then pass those names appended with the wildcard character (*) to grails test-app. It’s pretty simple, and I wasn’t able to find any scripts that already did this, so I figured it would be a good chance to brush up on my bash scripting skills.

Source code

Without further ado, here is the script itself:

#!/bin/sh
usage(){
    echo "Usage: `basename $0` [-h] [-r] [-t testOptions] [<directory>...]\n"
    echo ' -h Show this help message.'
    echo ' -r Remove the default directories, and only use the ones provided. Without the flag, the provided directories will be appended to the default list.'
    echo ' -t Options to pass to "grails test-app". Default: none.'
    echo ' See: http://grails.org/doc/latest/ref/Command%20Line/test-app.html'
}

# Parse the arguments
testOpts=
directories='grails-app src/groovy'

while getopts “hrt:” option
do
case ${option} in
        h) usage; exit 0;;
        r) directories='';;
        t) testOpts=${OPTARG};;
        *) usage; exit 1;;
    esac
done

shift $(( OPTIND-1 ))
directories+=" ${*}"

# Get the modified files
modifiedFiles=`git diff --name-only HEAD ${directories}`
regex="([^/]*)\.groovy"
testPatterns=''

for filename in ${modifiedFiles}; do
    if [[ ${filename} =~ ${regex} ]]; then
        testPatterns+="${BASH_REMATCH[1]}* "
    fi
done

# Run the tests
if [ -n "${testPatterns}" ]; then
    echo "Calling... grails test-app ${testOpts} ${testPatterns}\n"

    grails test-app ${testOpts} ${testPatterns}
else
    echo "No modified classes found to test."
fi

Simply copy that into a .sh file, chmod +x permissions to it, and (optionally) add an alias (e.g. alias gitTestModified=’~/scripts/gitTestModified.sh’) in your .bashrc to easily invoke it. So with our example, we could call it with:gitTestModified

Usage

By default the script takes no arguments and simply checks the following directories for modified .groovy files (as according to git diff HEAD):grails-app src/groovyThis list can be appended simply by passing some bulk arguments to the script. For example, to check for tests that were modified as well, we can do:gitTestModified test # Add 'test' to the directories to search for git changesIf there are modified files, the tests will be invoked.  If not, then a simple message will pop up saying so.

-r Remove default directories

gitTestModified also allows a -r flag to exclusively check the directories passed in, overriding the defaults in the script. So the following call would look ONLY in the ‘test’ directory:gitTestModified -r test # Only check the 'test' directory for git changesOnce changed files are found and parsed, the filenames are appended with the wildcard (*) and passed to grails test-app. Since the typical Grails test naming convention is Spec.groovy or Tests.groovy, the wildcard will capture these test cases and automatically run them. Obviously if your project does not follow this convention, this script will not work.

-t Test options

If we want to pass additional arguments to grails test-app, so we can for example, run only tests that failed last time, we can do so using the -t option and then providing the arguments:gitTestModified -t "-rerun" # Only run tests that failed last time we called test-appSince these arguments are passed directly to grails test-app, it’s best to wrap them in quotes due to the dashes and potential spaces. This also means we can append additional tests to run:gitTestModified -t "-unit MyExtraTests.groovy"

-h Help

Finally, the script has a -h flag to display its usage information:

Usage: gitTestModified.sh [-h] [-r] [-t testOptions] [...]</code>

-h Show this help message.
-r Only check the directories provided. Otherwise, the provided directories will be appended to the directory list.
-t Options to pass to "grails test-app". Default: none.
See: http://grails.org/doc/latest/ref/Command%20Line/test-app.html

gitTestModified also outputs each call to grails test-app both for easier debugging and for easy copying/pasting in case you want to run the tests independently.

Caveats

The script currently only checks for .groovy files, and relies on git diff HEAD to get its list of changed files, so committed changes may not show up in that list. It only checks .groovy files (not .java), but you can change the regex variable in the script if you need Java files too. It’s also my first major bash script, so it may be a little rough around the edges. If you’d like to fork it and/or improve on it, I’ve uploaded the code on GitHub.

Happy refactoring!

Igor Shults

Share this Post

Related Blog Posts

JVM

Grails R14 Error (Memory quota exceeded) on Heroku

May 13th, 2014

Explains how to resolve the Grails R14 Error when it is encountered on Heroku.

Brandon Fish
JVM

Reuse your Gradle logic across the enterprise

April 24th, 2014

Gradle build code will sometimes need to be reused across the enterprise. Here are a few ways to do so.

David Norton
JVM

Tracking Hibernate statistics across Grails actions

April 22nd, 2014

Using controller filters to configure tracking and logging of Hibernate statistics in HTTP requests.

Igor Shults

About the author

Igor Shults

Sr. Consultant

Igor is a self-driven developer with a desire to apply and expand his current skill set.  He has experience working with companies of all sizes on the whole application stack, from the database to the front-end.  He enjoys working in collaborative environments involving discussion and planning around data modeling and product development.