Developer's Perception

Odd thoughts from a developer.

TDD in Go

| Comments

Lately I have been playing around with the go language. One of the first things I look for in a new language or framework is how tests can be written and whether it is feasible to do test driven development (TDD).

Assume that I am building a math package for go called funkymath (I chose the name to be fairly certain I do not conflict with any existing math packages).

To run tests for a package in go you run a simple command

1
go test funkymath

Not surprisingly I get an error results telling me that it cannot load the funkymath package, which makes sense since I did not create it:

1
2
3
can't load package: package funkymath: cannot find package "funkymath" in any of:
  /usr/local/go/src/pkg/funkymath (from $GOROOT)
  /home/nkn/Dropbox/SourceCode/go/src/funkymath (from $GOPATH)

To create a package you put the code in one of the defined paths for packages, which is also conveniently presented in the error message. It suggests the default location for packages from the go installation and an extra one in my dropbox folder I have configured in .profile.

Thus, I create a folder named funkymath and put an addition.go file in there and run the test command once again.

1
2
can't load package: package funkymath: 
addition.go:1:1: expected 'package', found 'EOF'

Again not suprisingly I get an error, as I did not put anything in the file. In many languages you would throw an exception to indicate that a method was not implemented yet.

As far as I have been able to find it seems to be idiomatic go to return both the result and an error (using the fact that go allows multiple return values) instead (maybe I am mistaken).

Thus, I created the following basic ‘not implemented’ implementation of a Plus function:

1
2
3
4
5
6
7
8
9
package funkymath

import (
    "fmt"
)

func Plus(a int, b int) (int, error) {
  return 0, fmt.Errorf("not implemented yet")
}

And run the test command again, this time with the information that it does not contain any tests.

1
?     funkymath   [no test files]

So the next step is to create a test file. Test files are located inside the package folder and are identified by go using the naming convention of the file name underscore test.

Thus, I create an addition_test.go file next to addition.go and put in the first test:

1
2
3
4
5
6
7
8
9
10
11
12
package funkymath

import "testing"

func TestPlus_TwoNumbers_ShouldNotReturnAnError(t *testing.T) {    
  a := 2
  b := 2

  if result, err := Plus(a, b); err != nil {
      t.Errorf("Plus(%v, %v) produces error %v, expected result %v", a, b, err, 4)
  }
}

Test functions are identified by the naming convention that they start with ‘Test’, and are automatically parsed a reference to the testing object. Will get back to the specifics of the test function a bit later.

1
./addition_test.go:10: result declared and not used

Ofcourse I cannot run this test function as I am not using result for anything. In general I really like this feature of the language, but sometimes it catches me ofguard. Thus, I inform go that I am not really interested in the first return value:

1
2
3
4
5
6
7
8
9
10
11
12
package funkymath

import "testing"

func TestPlus_TwoNumbers_ShouldNotReturnAnError(t *testing.T) {    
  a := 2
  b := 2

  if _, err := Plus(a, b); err != nil {
      t.Errorf("Plus(%v, %v) produces error %v, expected result %v", a, b, err, 4)
  }
}

And finally get the test error I was looking for:

1
2
3
4
--- FAIL: TestPlus_TwoNumbers_ShouldNotReturnAnError (0.00 seconds)
  addition_test.go:11: Plus(2, 2) produces error not implemented yet, expected result 4
FAIL
FAIL  funkymath   0.013s

Next step is then to implement the functionality. It can always be argued whether to use triangulation or obvious implementation, but I choose to just return a constant.

1
2
3
4
5
package funkymath

func Plus(a int, b int) (int, error) {
  return 4, nil
}

And the result of running the tests:

1
ok     funkymath   0.006s

YAY! I managed to complete the RED-GREEN TDD cycle in go.

This is an extremely simple example, but there is still a couple of valid points to make.

The convention for tests in go is to locate test files inside the package folder and name them filename_test.go. Test functions inside those files are named TestSomething.

The language does not have a rich DSL for writing tests like you would find in .NET using NUnit (or others) or in javascript using jasmine. You have to test values using built in language constructs like ‘if’ and even format the error messages yourself.

This is a bit cumbersome, but I guess that it is fine that the default way works like this, as 3rd party packages can built DSL’s on top of this like Asserts for go.

Next step is to play some more with building a webserver in go. If it is not on the web it did not happen.

Comments