thrawn01.org

Grammar on my terms

Golang testing package encourages good package design

Feb 6, 2017 - 6 minute read - Comments

Software development disciplines teach us to minimize public functions and interfaces exported by our code. This forms what is known as the public surface of the code package. It is through this public surface that we proclaim to our users which interfaces and functions are available for use.

Making an interface public proclaims two things

  1. The interface is well tested and approved for use.
  2. The signature of this interface is guaranteed not to change for this major version.

Developers who are cognizant of their public surface usually err on the side of caution by making all new functions private first, and are very explicit in exposing a small subset of functions public. Developers new to golang and which fall into this category are often appalled to discover they are unable to access private functions when writing tests using the built in _test package.

The typical response is to place the public test functions in the public package, and avoid the _test package all together. The result is a littering of the public surface with hundreds of test functions! Precisely opposite of what we wanted to accomplish in making our functions private!

So what are we todo?

We get clues by examining the golang standard library. Take the golang standard fmt package as an example. It’s public surface is very clean and well tested with 36 tests in the fmt_test package. Yet closer inspection reveals many private functions of varying degrees of complexity within the package! How does a package as well tested and widely used as fmt get away with zero tests on private functions? The answer might surprise you, as this simple rule is one of the hallmarks of great software design and good testing.

Only test the public surface

One of the promises of the public surface is that the interface is well tested, as such; the public surface should be the focus of all our tests. If the public surface tests all possible execution paths within our public and private functions no private function testing is required. With thorough test coverage of the public surface, all possible code paths can be covered.

Put another way; if you’re testing private functions, you’re breaking the rules of encapsulation. Or if you feel the need to test a private method, it instead should be a public method, as private functions form the core of the encapsulation. No matter how many ways you say it, the result is the same. Testing private functions defeats the purpose of encapsulation.

When writing private functions first it is common to write tests for the private functions at the same time. This can result in less rigorous testing of the public surface, as the developer might reason the private function is well covered, no need to duplicate the same test for the public function, or at best results in a duplication of testing for both the public and private methods.

I saw this happen recently where the developer moved the existing tests out of the _test package and into the main package so he could add tests for a private method. He then neglected to add the same tests for the public function. This could not only leave a gap of untested code in the public function but also breaks encapsulation, causing headaches for future pull requests that don’t realize the testing gap or that now have to deal with a tested private function that is a detail of the encapsulation. Thus requiring future pull requests that modify the private function update tests for the private function instead of testing the thing that is actually useful to our users, the public surface.

If the initial developer correctly tested the public surface first, future pull requests should be judged successful if the public surface tests pass, regardless of what private functions were modified. As long as all code paths within the private functions are covered, the pull request can be judged a success. By just following the “test public surfaces only” rule we can avoid extra work in the future, increase the amount of code covered and reduce test overlap.

How do I ensure my private methods are covered?

To ensure our public surface tests cover all our private functions, golang comes with a built in code coverage tool to identify un-exercised code paths. For example, we can see all the test coverage for private functions in the fmt package by running the following in a terminal. (See fmt/format.go which contains many of the private functions)

 $ go test -coverprofile=coverage.out fmt
 $ go tool cover -html=coverage.out

This tool or one like it should be an important part of any CI setup.

How do I avoid crowding the public surface?

Inevitably you will write some general interfaces which are useful to many parts of our code, but are not strictly apart of the public surface you wish to present. These interfaces are perfect candidates for placement in a sub-package. In this way we create a testable public interface for our private code to use, but which is not strictly apart of our main packages public surface. golang supports sub packages naturally and can be a strong indicator to users which interface surfaces are specific to our package.

  github.com/thrawn01/package
  github.com/thrawn01/package/utils
  github.com/thrawn01/package/db

go doc supports this naturally by generating documentation for the current package and ignoring sub packages unless specified directly. Granted this does not keep users from using the sub package interfaces; but the generated go docs can help steer users toward the correct interfaces.

How do I know what interfaces to make public or private?

In general, functions and interfaces which are tightly coupled to the calling function are good candidates for privatization; all others should be public.

When deciding if a function should be private of public it can be helpful to ask the following questions:

  1. Is this function generally useful in other parts of the code base? (public/sub package)
  2. Could this function be useful to others? (public/sub package)
  3. Is this function tightly coupled to the package? (private)
  4. Can all code paths be covered by testing the public interface? (If yes make private else public)

Conclusion

Separation of package code and package testing is a wonderful feature of golang which forces the developer to make sound choices about how their package is used and tested. Over my many years of C++ and Java development I can’t tell you how many times I’ve come across useful and reusable code that was thoughtlessly left private and thus unasscessable. I’ve been at companies where entire classes were copied 10+ times in different locations of a vast code base because the original author never imagined the class would be useful to any one but himself. Because of this, I write my code public first and make thoughtful decisions about how my code is used.

My hope is that this post will encourage others to make their code public first, use the golang _test package, and encourage more code reuse.