Introduction

Go supports testing through two key elements:

  • The go test command
  • The testing package

To write a test suite, you need to create an xxx_test.go file containing TestXXX functions, and place this file in the same package as the package being tested.

When compiling the package being tested, this file will be excluded, but when executing go test, this file will be included.

Internal Package Testing

Since Go’s build toolchain automatically separates package source files from package test source files based on whether the filename has a _test.go suffix during package compilation, test code doesn’t enter the normal build scope of the package. Therefore, using the same package name for test code as the package being tested is a natural choice for internal package testing.

Internal package testing is essentially a white-box testing method. Since the test code is under the same package name as the source code being tested, the test code can access all symbols in that package, whether they are exported symbols or unexported symbols.

And because the internal implementation logic of the package is transparent to the test code, internal package testing can more directly construct test data and implement test logic, easily achieving higher test coverage. Therefore, for projects pursuing high test coverage, internal package testing is the best choice.

However, in actual use, you will encounter the following problems:

  • Due to higher test coverage, the coupling level of the test code is higher, so that changes to the code being tested will affect the test case code.
  • Package circular references, such as A referencing B, and B’s test cases referencing A, which will lead to circular references and ultimately fail to compile.

External Package Testing

To solve the circular reference problem of internal package testing, external package testing was introduced. Unlike internal package testing, which is essentially implementation-oriented white-box testing, external package testing is essentially interface-oriented black-box testing.

The “interface” here refers to the exported APIs of the package being tested, which are the contracts for interaction between the tested package and the outside world.

Once a contract is determined, it will remain stable for a long time. Regardless of how the internal implementation logic and data structure design of the tested package are adjusted and optimized, these contracts are generally not affected.

This essence allows the external package test code to be fully decoupled from the package being tested, making the external package test code that tests these exported APIs exhibit extremely robust characteristics, that is, it rarely needs to be adjusted and maintained as the internal implementation logic of the tested code is adjusted.

However, the shortcomings of external package testing are also obvious, that is, there are blind spots in testing. Since the test code and the test target are not under the same package name, the test code only has the right to access the exported symbols of the tested package, and can only verify the behavior of the tested package through the limited “window” of exported APIs combined with constructing specific data.

Under such constraints, it is easy to have insufficient test coverage of the package being tested.

The implementers of the Go standard library provide an idiomatic solution to this problem:

Install a backdoor, which is through the use of the export_test.go file. The code in this file is under the name of the package being tested, but it is neither included in the official product code (because it is in a _test.go file), nor does it contain any test code, but is only used to expose the internal symbols of the tested package to external test code during the testing phase.

// export_test.go
package repository

var Client = client
var IsAdmin = isAdmin

This way, you can access the internal symbols of the repository package in the repository_test package!

After the above comparison, we find that internal package testing and external package testing each have their own advantages and disadvantages. So which testing method should we choose in Go testing coding practice? There is no standard answer to this question at present.

But personally, I prefer external package testing, this loosely coupled approach, testing from the perspective of package users. If you must improve test coverage, then you can use the export_test.go approach!

I hope this is helpful, Happy hacking…