Monday, May 5, 2014

Speeding up the Feedback loop on Grails Unit Tests

grailsFeedbackLoop

I am doing Test Driven Development on a Groovy/Grails project. I use IntelliJ for my IDE and there was a time when working this way and running tests in IntelliJ would be fine, but I got spoiled. For the last year I have been working with AngularJS and using Karma to run my Jasmine JavaScript unit tests. These tests ran fast and continuously. Now every time I run my unit tests for Grails in IntelliJ I am waiting and can feel how the delay impacts my flow. By using grails interactive TMUX and Gaurd, I was able to produce the same results.

Step one - Grails Interactive

Running the tests in IntelliJ or by typing in

grails test-app unit:

just to slow. It starts up the container every time which took at least 15 seconds. This doesn’t seem like a lot, but it adds up over the course of the day. By using grails in interactive mode the start up time is removed and the tests complete faster. Once in interactive mode the tests command can be repeated by pressing the up arrow. This was the first step, but chaning to the terminal window to hit up arrow after every change was taking to much time also. Now to make them run automatically when I save a file so I don’t have to change windows.

Step Two - Watch Mode

To accomplish watch mode I had to use a few tools. The first thing is to watch the files and to execute a command when something changes. To do this I used the Ruby tool Guard. I set up a guard file with the following

guard :shell do
  watch /.groovy/ do |m|
   n m[0], 'Changed'
        `tmux send-keys -t :1.1 'test-app unit: ' C-m`
  end
end

file watches all groovy files under the directory and then executes

tmux send-keys -t :1.1 'test-app unit: ' C-m

tmux send-keys command acts like I typed the command myself in the session making it execute the test-app every time a file is saved.
tmux and tmuxinator

In order for the above command to work, I need to have a tmux session set up. I use tmux with tmuxinator to create the session. My tmuxinator file for create my session looks like this

# ~/.tmuxinator/server.yml
name: server_dev
root: ~/Documents/workspace/server 
windows:
  - testing:
      layout: main-horizontal
      panes:
        - grails:
          - grails
        - guard -G ../guardFile
        - #empty, just shell

layout gives me three panes. Pane one the grails interactive pane, pane two launches Guard, pane three is an shell prompt in case anything needs to be run.

One note about the Gurad file and the tmuxinator script, I changed my tmux settings to make the windows and panes have 1 based lists instead of 0 based lists. If you do not modify this setting you will need to modify the Guardfile for this.

This set up works great, although, when I am working on one test, it would be nice if only that test was run, and not the whole suite. To do this I modified the guardfile to this

guard :shell do
  watch /.groovy/ do |m|
   n m[0], 'Changed'
    if(! ENV['GRAILS_TEST'] or ENV['GRAILS_TEST'].nil?)
  `tmux send-keys -t :1.1 'test-app unit:' C-m`
 else
  `tmux send-keys -t :1.1 'test-app unit: #{ENV["GRAILS_TEST"]}' C-m`
 end
  end
end

change here is to look for an environment variable in the guard session, if it is set, it will add that test to the send keys command and only that test will run. Setting the environment variable in the guard prompt is a bit verbose, fortunately guard runs using Pry, that allows me to create commands. I created two commands

  • setTest - takes a test name and will set it to the env variable
  • clearTest - clears the variable and will make all the tests run again

To create the pry commands for guard you need to modify the .guardrc file in your home directory. I added the following

Pry::Commands.block_command "setTest", "Set a specific test to run from guard" do |x|
  ENV['GRAILS_TEST'] = x
  output.puts "Guard will only run #{x} now"
end
Pry::Commands.block_command "clearTest", "Clears out specific test so all are run" do
  ENV['GRAILS_TEST'] = ''
  output.puts "Guard will run all unit tests now"
end 

Mission Complete

Now I have my grails tests running every time I save a file. I can specify which tests to run and they execute quickly.