Skip to content

drinkin/gunit

 
 

Repository files navigation

gunit

Installation

$ go get github.com/smartystreets/gunit/gunit

We now present gunit, yet another testing tool for Go.

Not again... (GoConvey was crazy enough...but sort of cool, ok I'll pay attention...)

No wait, this tool has some very interesting properties. It's a mix of good things provided by the built-in testing package, the assertions you know and love from the GoConvey project, the xUnit testing style (the first real unit testing framework), and it's all glued together with go test.

Blah, blah, yeah, yeah. Ok, so what's wrong with just using the standard "testing" package? What's better about this gunit thing?

The convention established by the "testing" package and the go test tool only allows for local function scope:

func TestSomething(t *testing.T) {
	// blah blah blah
}

This limited scope makes extracting functions or structs inconvenient as state will have to be passed to such extractions or state returned from them. It can get messy to keep a test nice and short. Here's the basic idea of what the test author using gunit would implement in a *_test.go file:

package examples

import (
	"github.com/smartystreets/assertions/should"
	"github.com/smartystreets/gunit"
)

type ExampleFixture struct {
	*gunit.Fixture // Required: Embedding this type is what makes the magic happen.

	// Declare useful state here (probably the stuff being tested, any fakes, etc...).
}

func (self *ExampleFixture) SetupStuff() {
	// This optional method will be executed before each "Test"
	// method (because it starts with "Setup").
}
func (self *ExampleFixture) TeardownStuff() {
	// This optional method will be executed after each "Test"
	// method (because it starts with "Teardown"), even if the test method panics.
}


// This is an actual test case:
func (self *ExampleFixture) TestWithAssertions() {
	// Here's how to use the functions from the `should`
	// package at github.com/smartystreets/assertions/should
	// to perform assertions:
	self.So(42, should.Equal, 42)
	self.So("Hello, World!", should.ContainSubstring, "World")
}

func (self *ExampleFixture) SkipTestWithNothing() {
	// Because this method's name starts with 'Skip', it will be skipped.
}

func (self *ExampleFixture) LongTestSlowOperation() {
	// Because this method's name starts with 'Long', it will be skipped if `go test` is run with the `short` flag.
	time.Sleep(time.Hour)
	So(true, should.BeTrue)
}

So, this doesn't import the standard go testing package at all, right? Do I have to run some other command to run my tests?

You're correct, the code you see above doesn't have anything to do with the "testing" package. But you still run go test to execute those tests...

Wait, without any test functions (you know, func TestSomething(t *testing.T) {...}) and without any reference to *testing.T anywhere, how do you mark a test as failed? You're saying I can still run go test? I don't get it.

Astute observations. gunit allows the test author to use a struct as the scope for a group of related test cases, in the style of xUnit fixtures. This makes extraction of setup/teardown behavior (as well as invoking the system under test) much simpler because all state for the test can be declared as fields on a struct which embeds the Fixture type from the gunit package.

Your question about the missing func Test... and the non-existent *testing.T is relevant. The missing link is a command that comes with the gunit project that scans your test fixtures and generates test functions that call all the appropriate methods for you! *testing.T is wrapped up by that generated code and you don't need to worry about calling any methods on it.

Wow, that sounds strangely cool and border-line wrong at the same time.

In either case, here's what the generated code looks like:

//////////////////////////////////////////////////////////////////////////////
// Generated Code ////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

package examples

import (
	"testing"

	"github.com/smartystreets/gunit"
)

///////////////////////////////////////////////////////////////////////////////

func Test_ExampleFixture__with_assertions(t *testing.T) {
	t.Parallel()
	fixture := gunit.NewFixture(t, testing.Verbose())
	defer fixture.Finalize()
	test := &ExampleFixture{Fixture: fixture}
	defer test.TeardownStuff()
	test.SetupStuff()
	test.TestWithAssertions()
}

func Test_ExampleFixture__skip_with_nothing(t *testing.T) {
	t.Skip("Skipping test case: 'SkipTestWithNothing'")

	t.Parallel()
	fixture := gunit.NewFixture(t, testing.Verbose())
	defer fixture.Finalize()
	test := &ExampleFixture{Fixture: fixture}
	defer test.TeardownStuff()
	test.SetupStuff()
	test.SkipTestWithNothing()
}

///////////////////////////////////////////////////////////////////////////////

func init() {
	gunit.Validate("98884d1f827ddcee8a923e672c3cf2ba")
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////// Generated Code //
///////////////////////////////////////////////////////////////////////////////

What's with the init function at the bottom of the generated code?

Ah, that. The call to gunit.Validate passes an md5 checksum of the contents of all *.go files in the package when the code was generated. Any change to those files between initial generation and test execution will necessitate regenerating the code, by running the gunit command.

Oh, so that prevents your struct-based tests from getting out of sync with the test functions go test expects to run.

Exactly. And you can invoke the gunit command be calling go generate if you put the following comment somewhere in your package (even in a *_test.go file):

//go:generate gunit

We use a script that runs our tests automatically whenever a *.go file changes (and it also runs go generate). Depending on the number of test fixtures in a package it generally takes a hundredth of a second to run. Your mileage may vary. Enjoy.

Advanced Examples


About

xUnit-style test fixture generator for Go.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Go 99.6%
  • Makefile 0.4%