My reason to be excited for Go 1.4
Dec 22, 2014
More than a week ago, Go 1.4 was released, bringing new features and bug fixes to the programming language.
Many people consider the support for building
shared libraries for Android the most important
addition in this version.
Others are excited about the new go generate
tool.
Some are happy that internal packages have landed,
as they should help reduce duplication of code.
However, my favorite new feature of Go 1.4 is hidden deep in a section called "Minor changes to the [standard] library" in the release notes. A list item there states:
The testing package has a new facility to provide more control over running a set of tests. If the test code contains a function
func TestMain(m *testing.M)
that function will be called instead of running the tests directly. The M struct contains methods to access and run the tests.
To sum up the benefits of this feature in one sentence, it lets you do global set-up/tear-down for tests.
Why that excites me
Recently, I have been developing a full-blown MVC web application in Go. Having come from Python, where Django wraps you tightly with all sorts of helpers, I initially struggled with several areas of architecture of a web application.
One of those was database testing. Django provides a test runner that creates a fresh database every time the tests are run. This way, you can do away with mocking and run your tests on real data, thus improving their accuracy.
In Go, I don't use any framework for my projects, and even if I did, none seem to have their own sophisticated testing tools. Thus, I had to invent my own way to swap out the database while testing . After thinking the design over for weeks, the final result looked like this:
func TestSomeFeature(t *testing.T) {
models.TestDBManager.Enter()
defer models.TestDBManager.Exit()
// Do the tests
}
Where Enter()
is responsible
for creating a new Postgres database,
running the migrations,
swapping out the usual database connection,
while Exit()
drops the database
and restores the original one.
This worked, but was less than ideal.
Every single test which utilized the DB
had to go through this process
and thus as the testing suite grow,
the time it took to run tests followed linearly.
Even if I wanted to only do this once,
there was no single point of entry or exit
I could hook on when go test
ran.
The introduction of TestMain()
made it possible
to run these migrations only once.
The code now looks like this:
func TestSomeFeature(t *testing.T) {
defer models.TestDBManager.Reset()
// Do the tests
}
func TestMain(m *testing.M) {
models.TestDBManager.Enter()
// os.Exit() does not respect defer statements
ret := m.Run()
models.TestDBManager.Exit()
os.Exit(ret)
}
While each test must still clean up after itself, that only involves restoring the initial data, which is way faster than doing the schema migrations.
Package | Without TestMain | With TestMain |
---|---|---|
models | 3.017s | 0.548s |
handlers | 9.329s | 2.971s |
settings | 0.008s | 0.008s |
cli | 1.230s | 0.528s |
The table above illustrates the performance improvements. The time it takes to run tests has reduced significantly, considering the database-heavy parts.
This approach also reduces code duplication by 50%: we now only have one line for database management in each test instead of two.
Less is more
The addition of the TestMain
function,
as simple change as it is,
improved my development process in important ways.
This is why Go makes me happy:
every 6 months, with a new version of the language,
new features come along.
Some are big, but most are little.
Despite that, they all play
an important part in making Go better,
without sacrificing core values of the language.
I am sure many others will benefit from
the improvements brought by Go 1.4.