As seen, the creation of unit tests and benchmark tests within Go is very simple and intuitive. The built in go test command allows various coverage capabilities. A little known capability of the go test tool is the ability to specify which packages you wish to generate coverage information about with the -coverpkg flag. Armed with this information, we can formulate a go test command that will be able to run against the main package in our program but collect coverage information about our handlers package:
go test -coverprofile=cov.txt -coverpkg ./handlers -run TestRunMain ./cmd/service/
The preceding command is saying run tests against the main package but collect the coverage information from handlers that in turn will use the cover tool to instrument the handlers code to keep track of coverage numbers in the handlers package. If we take this concept to the next logical step, what happens if we have one test in cmd/service/main_test.go that merely runs the main entry point of our web application? For our thought experiment, here are the contents of cmd/service/main_test.go followed by the modifications to $GOPATH/src/github.com/PacktPublishing/Echo-Essentials/chapter7/cmd/service/main_test.go:
package main import ( "testing" ) func TestRunMain(t *testing.T) { TestRun = true go main() <-StopTestServer TestRun = false }
Next, you will see how we alter main.go to include the TestRun variable and the StopTestServer channel which are used to tell main that this is a test version of the server, and when to stop processing:
var ( StopTestServer = make(chan bool) TestRun = false ) func main() { //... if TestRun { e.POST("/stop-test-server", func(ctx echo.Context) error { StopTestServer <- true return nil }) } //… }
As you can see within our main application, we flag the addition of a POST handler to the endpoint /stop-test-server that is handled by the anonymous function shown, which merely informs our test when it is time to stop the test server. This may seem silly, but now when we run the following command, the go test command starts up our main function and allows us to run external tests against it, and allows us to externally stop the processing of the tests which show us the coverage information for our handlers package:
go test ./cmd/service/ -v -coverpkg ./handlers -coverprofile cov.txt -run=TestRunMain === RUN TestRunMain ____ __ / __/___/ / ___ / _// __/ _ / _ /___/\__/_//_/\___/ v3.2.6 High performance, minimalist Go web framework https://echo.labstack.com ____________________________________O/_______ O ⇨ http server started on [::]:8080
At this point, you are able to perform HTTP calls against this using external tools, such as curl seen here:
curl http://localhost:8080/health-check {"message":"Everything is good!"} curl -XPOST http://localhost:8080/stop-test-server
After performing these two curl commands, if you look at the output from your test run, you will notice that we get some logging information about the requests performed, as well as finally, the coverage information for our handler functions, which is pasted here:
--- PASS: TestRunMain (67.31s) PASS coverage: 7.9% of statements in ./handlers ok github.com/PacktPublishing/Echo-Essentials/chapter7/cmd/service 67.320s coverage: 7.9% of statements in ./handlers
This capability will present real coverage numbers for our handlers package from external testing. External testing frameworks such as cucumber allow you to perform behavior driven testing with a fully featured framework that is not written in Go. This means you will be able to have team members, such as quality engineers write tests for your service in whatever framework they want to, and still get the coverage numbers for your code. As you can see here, with the coverage tool, you can even drill down and see individual function coverage numbers which can help with coverage reports:
go tool cover -func=cov.txt github.com/PacktPublishing/Echo-Essentials/chapter7/handlers/err.go:10: Error 0.0% github.com/PacktPublishing/Echo-Essentials/chapter7/handlers/health-check.go:13: HealthCheck 100.0% github.com/PacktPublishing/Echo-Essentials/chapter7/handlers/login.go:19: Login 0.0% github.com/PacktPublishing/Echo-Essentials/chapter7/handlers/logout.go:5: Logout 0.0% github.com/PacktPublishing/Echo-Essentials/chapter7/handlers/reminder.go:5: CreateReminder 0.0% github.com/PacktPublishing/Echo-Essentials/chapter7/handlers/reminder.go:9: GetReminder 0.0% total: (statements) 7.9%
It is fairly easy to see how this could benefit an organization if implemented within a Continuous integration and Continuous Deployment pipeline. Tests do not need to be exclusively written in Go, and you can have real integration tested by running the instrumented test server next to a real database and other integrated systems.