-
-
Notifications
You must be signed in to change notification settings - Fork 202
Developer Conventions: 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 test 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 take two parameters a, and b and returns a^b.
func raise(a, b int) float {
// ...
}
The test case should look like:
type testCase 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 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 testCase) 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:
- Each test will have a unique name, that we can use to run just that test.
- The test are run an a random order every time insuring that there aren't an dependencies between test runs.
tests := map[string]testCase{
"2^5": testCase{
a: 2,
b: 5,
expected: 32,
},
"5^2": testcase{
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))
}
}