MacOS – Setting up automatic red / green notifications for continuous testing and TDD

growlmacos

Note: this was originally posted under stackoverflow, and someone sensibly downvoted it since it's really OS X specific. So I'm reposting it over here, since it's been generally helpful.

I like doing Test Driven Development (TDD), and so I would like a way for my test suite to run whenever I make a change to a source file or test file.

In addition, I'd like the results of the test to display in an ephemeral "Growl" style window in the corner of my screen, showing a green icon if all tests passed and a red icon if one or more tests fail.

I know there is such a mechanism for node.js in mocha, and for ruby environments there's guard-rspec. But how do I create one for my [fill in the blank] testing environment under OS X?

Best Answer

Under OS X, you can create a simple solution using two off-the-shelf utilities: fswatch and terminal-notifier and three simple bash scripts. Here's how:

download fswatch and terminal-notifier

$ brew install fswatch
$ brew install terminal-notifier

set up project directories

My project directories are laid out as follows, but of course you can choose alternate strategies:

$PROJECT_ROOT/
  logs/
  lib/
  scripts/
    icons/
  tests/

create three bash scripts

file: scripts/run-tests

This is a simple script that invokes your framework's tests. In this case we're running python's unittest, but you can customize it to suit your needs:

#!/bin/bash
# Run unit tests over the test directory

PROJECT_ROOT=$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )

PYTHON=python                                                           # customize for your needs
export PYTHONPATH="$PROJECT_ROOT/lib"                                   # customize for your needs
${PYTHON} -m unittest discover --start-directory "$PROJECT_ROOT/tests"  # customize for your needs

file: scripts/growl-tests

This script invokes run-tests and captures stdout and stderr in log files. It then displays a growl-like message based on the results of run-tests. You must customize this script to parse the results of run-tests and decide whether to display a red or green icon. The example shown here parses the output of Python's unittest. If you use a different system, you'll need to make appropriate changes to the script.

#!/bin/bash
# Run tests and display results in a "growl"-like window.

PROJECT_ROOT=$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )
TEST_RUNNER="$PROJECT_ROOT/scripts/run-tests"
STDOUT_LOG="$PROJECT_ROOT/logs/unittest_stdout.log"
STDERR_LOG="$PROJECT_ROOT/logs/unittest_stderr.log"

# Run the tests, copy stdout and stderr to STDOUT_LOG and STDERR_LOG
# respectively
$TEST_RUNNER > >(tee "$STDOUT_LOG") 2> >(tee "$STDERR_LOG" >&2)

# Capture the exit status of TEST_RUNNER.  We will use this for the
# red / green distinction in our Growl display.
TEST_STATUS=$?

# The following lines are specific to the Python `unittest` output,
# and aren't required for the red / green display.  We extract a few
# extra bits of info so the Growl window can display something like:
#     Ran 452 tests in 8.300s
#     FAILED (failures=3)

# extract "Ran n tests in n.nnns"
TEST_SUMMARY=`tail -3 "$STDERR_LOG" | head -1`

# extract "OK" or "FAILED (failures=n)"
TEST_RESULT=`tail -1 "$STDERR_LOG"`

# Compose the two strings to make the entire message
GROWL_MESSAGE="$TEST_SUMMARY
$TEST_RESULT"

# Pick a sound and an icon based on TEST_STATUS
if [ $TEST_STATUS -eq 0 ] ; then
    GROWL_SOUND="Submarine"
    GROWL_ICON="$PROJECT_ROOT/scripts/icons/GreenBead.png"
else
    GROWL_SOUND="Purr"
    GROWL_ICON="$PROJECT_ROOT/scripts/icons/RedBead.png"
fi

# Now display the results in a Growl-like window
echo "$GROWL_MESSAGE" | terminal-notifier -sound $GROWL_SOUND -contentImage $GROWL_ICON

file: scripts/autorun-tests

This script invokes growl-tests once at startup, and then again whenever some file is modified. Under most circumstances, this file may be used verbatim without any modification.

#!/bin/bash
# Run tests automatically whenever files change in the PROJECT_ROOT

PROJECT_ROOT=$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )
TEST_RUNNER="$PROJECT_ROOT/scripts/growl-tests"

# run tests once at startup
"$TEST_RUNNER"

# Run tests whenever anything under PROJECT_ROOT changes
# Note that we explicitly exclude the log files, since they get
# written as a result of running the tests.  If we didn't exclude
# them, fswatch would retrigger continuously.
fswatch --one-per-batch "$PROJECT_ROOT" --exclude logs | xargs -n1 -I{} "$TEST_RUNNER"

add icons to make it pretty

Put these icons in your scripts/icons directory. These will be used as the red and green icons in the Growl window.

scripts/icons/GreenBead.png:

GreenBead.png

scripts/icons/RedBead.png:

RedBead.png

to run it

Simply open a terminal window and launch autorun-tests:

$ scripts/autorun-tests