Tuesday 23 August 2016

Testing Your Go App: Get Started The Right Way

When learning anything new, it’s important to have a fresh state of mind.
If you’re fairly new to Go and are coming from languages such as JavaScript or Ruby, you are likely accustomed to using existing frameworks that help you mock, assert, and do other testing wizardry.
Now eradicate the idea of reliance on external dependencies or frameworks! Testing was the first impediment I stumbled upon when learning this remarkable programming language a couple of years ago, a time when there were far fewer resources available.
I now know that testing success in Go means traveling light on dependencies (as with all things Go), relying minimally on external libraries, and writing good re-usable code. This presentation of Blake Mizerany’s experiences venturing forth with third party testing libraries is a great start to adjusting your mindset. You will see some good arguments about using external libraries and frameworks versus doing it “the Go way”.
It may seem counter-intuitive to build your own testing framework and mocking concepts, but is easier than one would think, and a good starting point for learning the language. Plus, unlike when I was learning, you have this article to guide you through common testing scenarios as well as introduce techniques that I consider best practices for efficiently testing and keeping code clean.
Do things “the Go Way”, eradicate dependencies on external frameworks.

Table Testing in Go

The basic testing unit - of ‘unit testing’ fame - can be any component of a program in its simplest form which takes an input and returns an output. Let’s take a look at a simple function we’d like to write tests for. It is nowhere near perfect or complete, but it’s good enough for demonstration purposes:
avg.go
func Avg(nos ...int) int {
 sum := 0
 for _, n := range nos {
  sum += n
 }
 if sum == 0 {
  return 0
 }
 return sum / len(nos)
}
The function above, func Avg(nos ...int), returns either zero or the integer average of a series of numbers that are given to it. Now let’s write a test for it.
In Go, it is considered best practice to name a test file with the same name as the file which contains the code being tested, with the added suffix _test. For example, the above code is in a file named avg.go, so our test file will be named avg_test.go.
Note that these examples are only excerpts of actual files, as the package definition and imports have been omitted for simplicity.
Here’s a test for the Avg function:
avg__test.go
func TestAvg(t *testing.T) {
 for _, tt := range []struct {
  Nos    []int
  Result int
 }{
  {Nos: []int{2, 4}, Result: 3},
  {Nos: []int{1, 2, 5}, Result: 2},
  {Nos: []int{1}, Result: 1},
  {Nos: []int{}, Result: 0},
  {Nos: []int{2, -2}, Result: 0},
 } {
  if avg := Average(tt.Nos...); avg != tt.Result {
   t.Fatalf("expected average of %v to be %d, got %d\n", tt.Nos, tt.Result, avg)
  }
 }
}
There are several things to note about the function definition:
  • First, the prefix of ‘Test’ on the test function name. This is necessary so that the tool will pick it up as a valid test.
  • The latter part of the function name is generally the name of the function or method being tested, in this case Avg.
  • We also need to pass in the testing structure called testing.T, which allows for control of the test’s flow. For more details on this API, please visit the documentation page.
Now let’s talk about the form in which the example is written. A test suite (a series of tests) is being run through the function Avg(), and each test contains a specific input and the expected output. In our case, each test sends in a slice of integers (Nos) and expects a specific return value (Result).
Table testing gets its name from its structure, easily represented by a table with two columns: the input variable and the expected output variable.

Golang Interface Mocking

One of the greatest and most powerful feature that the Go language has to offer is called an interface. Besides the power and flexibility that we get from interfacing when architecting our programs, interfacing also gives us amazing opportunities to decouple our components and test them thoroughly at their meeting point.
An interface is a named collection of methods, but also a variable type.
Let’s take an imaginary scenario where we need to read the first N bytes from an io.Reader and return them as a string. It would look something like this:
readn.go
// readN reads at most n bytes from r and returns them as a string.
func readN(r io.Reader, n int) (string, error) {
 buf := make([]byte, n)
 m, err := r.Read(buf)
 if err != nil {
  return "", err
 }
 return string(buf[:m]), nil
}
Obviously, the main thing to test is that the function readN, when given various input, returns the correct output. This can be done with table testing. But there are two other non-trivial aspects we should cover, which are checking that:
  • r.Read is called with a buffer of size n.
  • r.Read returns an error if one is thrown.
In order to know the size of the buffer that is passed to r.Read, as well as take control of the error that it returns, we need to mock the r being passed to readN. If we look at the Go documentation on type Reader, we see what io.Reader looks like:
type Reader interface {
    Read(p []byte) (n int, err error)
}
That seems rather easy. All we have to do in order to satisfy io.Reader is have our mock own a Readmethod. So our ReaderMock can be as follows:
type ReaderMock struct {
 ReadMock func([]byte) (int, error)
}

func (m ReaderMock) Read(p []byte) (int, error) {
 return m.ReadMock(p)
}
Let’s analyze the above code for a little bit. Any instance of ReaderMock clearly satisfies the io.Readerinterface because it implements the necessary Read method. Our mock also contains the field ReadMock, allowing us to set the exact behavior of the mocked method, which makes it super easy for us to dynamically instantiate whatever we need.
A great memory-free trick for ensuring that the interface is satisfied at run time is to insert the following into our code:
var _ io.Reader = (*MockReader)(nil)
This checks the assertion but doesn’t allocate anything, which lets us make sure that the interface is correctly implemented at compile time, before the program actually runs into any functionality using it. An optional trick, but helpful.
Moving on, let’s write our first test, in which r.Read is called with a buffer of size n. To do this, we use our ReaderMock as follows:
func TestReadN_bufSize(t *testing.T) {
 total := 0
 mr := &MockReader{func(b []byte) (int, error) {
  total = len(b)
  return 0, nil
 }}
 readN(mr, 5)
 if total != 5 {
  t.Fatalf("expected 5, got %d", total)
 }
}
As you can see above, we’ve defined the behavior for the Read function of our “fake” io.Reader with a scope variable, which can be later used to assert the validity of our test. Easy enough.
Let’s look at the second scenario we need to test, which requires us to mock Read to return an error:
func TestReadN_error(t *testing.T) {
 expect := errors.New("some non-nil error")
 mr := &MockReader{func(b []byte) (int, error) {
  return 0, expect
 }}
 _, err := readN(mr, 5)
 if err != expect {
  t.Fatal("expected error")
 }
}
In the above testing, any call to mr.Read (our mocked Reader) will return the defined error, thus it is safe to assume that the correct functioning of readN will do the same.

Function Mocking with Go

It isn’t often that we need to mock a function, because we tend to use structures and interfaces instead. These are easier to control, but occasionally we can bump into this necessity, and I frequently see confusion around the topic. Some people have even asked how to mock things like log.Println. Although it is rarely the case that we need to test input given to log.Println, we will use this opportunity to demonstrate.
Consider this simple if statement below that logs output depending on the value of n:
func printSize(n int) {
 if n < 10 {
  log.Println("SMALL")
 } else {
  log.Println("LARGE")
 }
}
In the above example, we assume the ridiculous scenario where we specifically test that log.Println is called with the correct values. In order for us to mock this function, we have to wrap it inside our own first:
var show = func(v ...interface{}) {
 log.Println(v...)
}
Declaring the function in this manner - as a variable - allows us to overwrite it in our tests and assign whatever behavior we want to it. Implicitly, lines referring to log.Println are replaced with show, so our program becomes:
func printSize(n int) {
 if n < 10 {
  show("SMALL")
 } else {
  show("LARGE")
 }
}
Now we can test:
func TestPrintSize(t *testing.T) {
 var got string
 oldShow := show
 show = func(v ...interface{}) {
  if len(v) != 1 {
   t.Fatalf("expected show to be called with 1 param, got %d", len(v))
  }
  var ok bool
  got, ok = v[0].(string)
  if !ok {
   t.Fatal("expected show to be called with a string")
  }
 }

 for _, tt := range []struct{
  N int
  Out string
 }{
  {2, "SMALL"},
  {3, "SMALL"},
  {9, "SMALL"},
  {10, "LARGE"},
  {11, "LARGE"},
  {100, "LARGE"},
 } {
  got = ""
  printSize(tt.N)
  if got != tt.Out {
   t.Fatalf("on %d, expected '%s', got '%s'\n", tt.N, tt.Out, got)
  }
 }

 // careful though, we must not forget to restore it to its original value
 // before finishing the test, or it might interfere with other tests in our
 // suite, giving us unexpected and hard to trace behavior.
 show = oldShow
}
Our takeaway shouldn’t be ‘mock log.Println’, but that in those very occasional scenarios when we do need to mock a package-level function for legitimate reasons, the only way to do so (as far as I am aware) is by declaring it as a package-level variable so that we can take control of its value.
However, if we ever do need to mock things like log.Println, a much more elegant solution can be written if we were to use a custom logger.

Go Template Rendering Tests

Another fairly common scenario is to test that the output of a rendered template is according to expectations. Let’s consider a GET request to http://localhost:3999/welcome?name=Frank, which returns the following body:
<html>
 <head><title>Welcome page</title></head>
 <body>
  <h1 class="header-name">
   Welcome <span class="name">Frank</span>!
  </h1>
 </body>
</html>
In case it wasn’t obvious enough by now, it’s not a coincidence that the query parameter name matches the content of the span classed as “name”. In this case, the obvious test would be to verify that this happens correctly every time across multiple outputs. I found the GoQuery library to be immensely helpful here.
GoQuery uses a jQuery-like API to query an HTML structure, which is indispensable for testing the validity of the markup output of your programs.
Now we can write our test in this manner:
welcome__test.go
func TestWelcome_name(t *testing.T) {
 resp, err := http.Get("http://localhost:3999/welcome?name=Frank")
 if err != nil {
  t.Fatal(err)
 }
 if resp.StatusCode != http.StatusOK {
  t.Fatalf("expected 200, got %d", resp.StatusCode)
 }
 doc, err := goquery.NewDocumentFromResponse(resp)
 if err != nil {
  t.Fatal(err)
 }
 if v := doc.Find("h1.header-name span.name").Text(); v != "Frank" {
  t.Fatalf("expected markup to contain 'Frank', got '%s'", v)
 }
}
First, we check that the response code was 200/OK before proceeding.
I believe that it’s not too far-fetched to assume that the rest of the code snippet above is self-explanatory: we retrieve the URL using the http package and create a new goquery-compatible document from the response, which we then use to query the DOM that was returned. We check that the span.name inside h1.header-name encapsulates the text ‘Frank’.
Like what you're reading?
Get the latest updates first.
No spam. Just great engineering and design posts.

Testing JSON APIs

Go is frequently used to write APIs of some sort, so last but not least, let’s look into some high-level ways of testing JSON APIs.
Consider if the endpoint previously returned JSON instead of HTML, so from http://localhost:3999/welcome.json?name=Frank we would expect the response body to look something like:
{"Salutation": "Hello Frank!"}
Asserting JSON responses, as one might have already guessed, is not much different from asserting template responses, with the exception that we don’t need any external libraries or dependencies. Go’s standard libraries are sufficient. Here is our test confirming that the correct JSON is returned for the given parameters:
welcome__test.go
func TestWelcome_name_JSON(t *testing.T) {
 resp, err := http.Get("http://localhost:3999/welcome.json?name=Frank")
 if err != nil {
  t.Fatal(err)
 }
 if resp.StatusCode != 200 {
  t.Fatalf("expected 200, got %d", resp.StatusCode)
 }
 var dst struct{ Salutation string }
 if err := json.NewDecoder(resp.Body).Decode(&dst); err != nil {
  t.Fatal(err)
 }
 if dst.Salutation != "Hello Frank!" {
  t.Fatalf("expected 'Hello Frank!', got '%s'", dst.Salutation)
 }
}
If anything other than the structure that we decode against would be returned, json.NewDecoder will instead return an error and the test will fail. Considering that the response decodes against the structure successfully, we check that the contents of the field are as expected - in our case “Hello Frank!”.

Setup & Teardown

Testing with Go is easy, but there is one problem with both the JSON test above and the template rendering test before that. They both assume that the server is running, and this creates an unreliable dependency. Also, it’s not a great idea to go against a “live” server.
It’s never a good idea to test against “live” data on a “live” production server; spin up local or development copies so there’s no damage done with things go horribly wrong.
Luckily, Go offers the httptest package to create test servers. Tests spark up their own separate server, independent from our main one, and so testing won’t interfere with production.
In these cases it’s ideal to create generic setup and teardown functions to be called by all tests requiring a running server. Following this new, safer pattern, our tests would end up looking something like this:
func setup() *httptest.Server {
 return httptest.NewServer(app.Handler())
}

func teardown(s *httptest.Server) {
 s.Close()
}

func TestWelcome_name(t *testing.T) {
 srv := setup()

 url := fmt.Sprintf("%s/welcome.json?name=Frank", srv.URL)
 resp, err := http.Get(url)
 // verify errors & run assertions as usual

 teardown(srv)
}
Note the app.Handler() reference. This is a best practice function that returns the application’s http.Handler, which can instantiate either your production server or a test server.

Conclusion

Testing in Go is a great opportunity to assume the outer perspective of your program and take on the shoes of your visitors, or in most cases, the users of your API. It provides the great opportunity to make sure you are both delivering good code and a quality experience.
Whenever you’re unsure of the more complex functionalities in your code, testing comes in handy as a reassurance, and also guarantees that the pieces will continue to work together well when modifying parts of larger systems.
I hope this article was of use to you, and you’re welcome to comment if you know of any other testing tricks.

No comments:

Post a Comment