BREAKING: USPS slashes address verification to 60 addresses/hour. What now?
Smarty

Testing in Go by example: Part 2

Smarty header pin graphic
Updated July 2, 2025
Tags
Smarty header pin graphic

Here's part 2 of our "Testing in Go" series. If you're new, feel free to catch up before reading on.


Basics

You've already learned how to execute tests in Go for a single package.

$ go test

There's a bit more to it, though. You can run any package from anywhere if you provide the import path. For example, this command runs the actual tests for the "testing" package from the standard library:

$ go test -v testing

If you've already run go get github.com/bradfitz/http2 you can execute those tests from anywhere with this:

$ go test -v github.com/bradfitz/http2

Try running multiple packages with a single command:

$ go test -v testing encoding/json

If your project has lots of packages, you can run this at the root of the project to gather and run all tests:

$ go test ./...

Better workflows

If you practice TDD/BDD then you are probably alternating very frequently between a text editor (to write tests and production code) and the command line (to run your tests). This gets really tedious. There are various things you can do to make running tests more automatic:

1. Text editor plugins

There are lots of plugins that allow more seamless integration with the Go tool chain from within your editor of choice:

I hope that Jetbrains will jump on the bandwagon soon and create an IDE for Go. Their products always have nice test runners. We'll see if that happens anytime soon.

2. Auto-run Script

But even the plugin-style integrations can get in the way. So here's one thing I've tried:

#!/usr/bin/env python

"""
//gist.github.com/mdwhatcott/9107649

This script scans the current working directory for changes 
to .go files and runs `go test` in each folder where *_test.go 
files are found. It does this indefinitely or until a 
KeyboardInterrupt is raised (<Ctrl+c>). This script passes the 
verbosity command line argument (-v) to `go test`.
"""


import os
import subprocess
import sys
import time


def main(verbose):
    working = os.path.abspath(os.path.join(os.getcwd()))    
    scanner = WorkspaceScanner(working)
    runner = TestRunner(working, verbose)

    while True:
        if scanner.scan():
            runner.run()


class WorkspaceScanner(object):
    def __init__(self, top):
        self.state = 0
        self.top = top

    def scan(self):
        time.sleep(.75)
        new_state = sum(self._checksums())
        if self.state != new_state:
            self.state = new_state
            return True
        return False

    def _checksums(self):
        for root, dirs, files in os.walk(self.top):
            for f in files:
                if f.endswith('.go'):
                    try:
                        stats = os.stat(os.path.join(root, f))
                        yield stats.st_mtime + stats.st_size
                    except OSError:
                        pass


class TestRunner(object):
    def __init__(self, top, verbosity):
        self.repetitions = 0
        self.top = top
        self.working = self.top
        self.verbosity = verbosity

    def run(self):
        self.repetitions += 1
        self._display_repetitions_banner()
        self._run_tests()

    def _display_repetitions_banner(self):
        number = ' {} '.format(
            self.repetitions if self.repetitions % 50 else
            'Wow, are you going for a top score? Keep it up!')
        half_delimiter = (EVEN if not self.repetitions % 2 else ODD) *\
                         ((80 - len(number)) / 2)
        write('\n{0}{1}{0}\n'.format(half_delimiter, number))

    def _run_tests(self):
        self._chdir(self.top)
        if self.tests_found():
            self._run_test()
        
        for root, dirs, files in os.walk(self.top):
            self.search_for_tests(root, dirs, files)

    def search_for_tests(self, root, dirs, files):
        for d in dirs:
            if '.git' in d or '.git' in root:
                continue

            self._chdir(os.path.join(root, d))
            if self.tests_found():
                self._run_test()

    def tests_found(self):
        for f in os.listdir(self.working):
            if f.endswith('_test.go'):
                return True

        return False

    def _run_test(self):
        subprocess.call('go test -i', shell=True)
        try:
            output = subprocess.check_output(
                'go test ' + self.verbosity, shell=True)
            self.write_output(output)
        except subprocess.CalledProcessError as error:
            self.write_output(error.output)

        write('\n')

    def write_output(self, output):
        write(output)

    def _chdir(self, new):
        os.chdir(new)
        self.working = new


def write(value):
    sys.stdout.write(value)
    sys.stdout.flush()


EVEN = '='
ODD  = '-'
RESET_COLOR  = '\033[0m'
RED_COLOR    = '\033[31m'
YELLOW_COLOR = '\033[33m'
GREEN_COLOR  = '\033[32m'


def parse_bool_arg(name):
    for arg in sys.argv:
        if arg == name:
            return True
    return False


if __name__ == '__main__':
    verbose = '-v' if parse_bool_arg('-v') else ''
    main(verbose)

The script has its origins in an extinct script by Jeff Winkler called nosy, which I used several years ago, and which has been reborn among the python community and is still known as nosy.

Magical auto-updating web UI

That python script worked pretty well, but once you get lots of tests and lots of packages being tested it became difficult to sort through the output for a failure or error. As I was using this script I had an idea:

"What if the results were shown in a web browser? And what if failures and errors automatically bubbled up to the top? Yeah, the browser client could maintain a persistent connection to a server that basically does what the auto-run python script did, and pipe the results down to the client whenever a change happened?"

I was already working on a new testing package and decided to bundle this idea along with it. The result is goconvey, which comes with just such a tool.

Here's how to install and run the goconvey web UI:

$ go get github.com/smartystreets/goconvey
$ $GOPATH/bin/goconvey

Then, open a browser at //localhost:8080 and you will see the results of running go test in every package at and below your current working directory. Try creating/deleting/saving a *.go file and watch the UI update with the latest results.

GoConvey

What are you waiting for? go test!

Subscribe to our blog!
Learn more about RSS feeds here.
Read our recent posts
Christmas traditions around Smarty
Arrow Icon
Around the world, people celebrate Christmas in all kinds of ways. In Iceland, it’s tradition to read books by candlelight on Christmas Eve. Christmas firework shows are a staple in El Salvador. Families in Australia often spend Christmas Day playing cricket. At Smarty, we keep the season bright with a few traditions of our own. From wacky holiday decorating to gingerbread house competitions, we love taking the holidays to the next level. So stick around; things are about to get festive. Our favorite Christmas traditions are:Seasonal service projectsUpside-down holiday decorUgly Sweater DayThe Great Gingerbread House Bake OffAddy the Address ElfYuletide karaokeLetters to SantaGift-wrapping extravaganzaSeasonal service projectsWe like to celebrate the season of giving by giving back! This year, we participated in United Way's Sub for Santa program, where families—or Smartys 😉—sub in for Santa by donating gifts to local families in need.
Accessibility best practices at Smarty
Arrow Icon
An enterprise-level customer of Smarty’s recently shared how impressed they were with our accessibility-first approach, inquiring about how we designed our tools to function in a way that feels equitable and usable for everyone. At Smarty, one of our core values is outwardness — seeing people as people, not objects or caricatures. Every person who interacts with our tools has their own needs, wants, and objectives, and accessibility is one of the most meaningful ways we connect with people - an area we’re striving to improve.
Introducing Smarty’s mascot… Hermes?
Arrow Icon
Greek gods, as it turns out, have a lot to say to the mortals on Earth and the souls in Hades. What they don’t have is time to travel from Olympus all the way to Earth or Hades just to tell a mere mortal to perform 12 labors… or eat a magical herb… or free Odysseus. That’s why the gods have a personal messenger: Hermes. Equipped with winged sandals, Hermes travels between Olympus, Earth, and the underworld with lightning speed (Get it? Lightning? Because his dad is Zeus? 🤣)Smarty also knows a thing or two about outrageous speed, and that’s not where our similarities with Hermes end.

Ready to get started?