Skip to content

Developer Conventions: Testing

Gautam Dey edited this page May 24, 2019 · 2 revisions

Testing

We only use the standard go testing library that comes with go. We use table driven tests with the following format:

package test

import "testing"

func TestFoo(t *testing.T) {
	

With this style of writing tests, we break our tests into three sections.

The first defines the test case; which takes the inputs and the expected outputs and wraps them up into a single unit.

I like to match the names of the function parameters to the field names. Say you are testing a function that takes two parameters a, b, and returns a^b.

func raise(a, b int) float {
  // ...
}

The test case should look like:

	
type tcase struct {
	a int // The a parameter to raise
	b int // the b parameter to raise

	expected float // a^b
}
	

The next section is the test func, which we will call fn. (This will be a curried function.) This function should take a test case, and return a function that will actually run the test. For the most part, you can ignore this, and just copy the template below; filling the necessary parts.

By having the test defined right after the test case, it is easy to see what the test is doing, and what each field in the test case means.

So, to test our fictitious raise function we would do the following:

	
fn := func(tc tcase) func(t *testing.T) {
	return func(t *testing.T) {

		/* Here is where we would add our test code, replace as needed for your own functions. */
		got := raise(tc.a, tc.b)
		if got != tc.expected {
			t.Errorf("invalid answer from raise, expected %v got %v", tc.expected, got)
		}

		/* rest of the function follows */
	}
}

Next, we define our actual test cases. Each test case has a unique name, which is the key in the map, followed by the values for the test case.

By using a map, we get two benefits:

  1. Each test will have a unique name, that we can use to run just that test.
  2. The tests are run in a random order every time ensuring that there aren't dependencies between test runs.
	
tests := map[string]tcase{
	"2^5": {
		a:        2,
		b:        5,
		expected: 32,
	},
	"5^2": {
		a:        5,
		b:        2,
		expected: 25,
	},
}

The last section is the boilerplate that actually runs the tests.

	
   for name, tc := range tests {
	t.Run(name, fn(tc))
   }
}
Clone this wiki locally