An Introduction to Go Tooling
Tags: go
Published: 2021-02-17 00:57:50

*This article was originally written by [Ayooluwa Isaiah][1] on the [Honeybadger Developer Blog][2].*  

# An overview of Go tooling

Tooling is generally considered one of the stronger aspects of the Go ecosystem. The `go` command is the gateway to many of the tools that will be discussed in this post.

By learning about each of the tools discussed here, you'll become much more efficient when working on Go projects and perform common tasks quickly and reliably.

## Viewing environmental variables

The `go env` command is used to display information about the current Go environment.
Here's a sample of what this command outputs:

```go
GO111MODULE="on"
GOARCH="amd64"
GOBIN="/home/ayo/go/bin"
GOCACHE="/home/ayo/.cache/go-build"
GOENV="/home/ayo/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/ayo/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build145204762=/tmp/go-build -gno-record-gcc-switches"
```

If you want to view the value of a specific variable, you can pass them as arguments to the `go env` command:

```bash
$ go env GOPATH GOROOT GOBIN
/home/ayo/go
/usr/lib/go
/home/ayo/go/bin
```

The documentation for each of the variables can be accessed using the command below:

```bash
go help environmental
```

## Running code with go run

Assuming you have a `main.go` file with the following code,

```go
package main
import "fmt"
func main() { fmt.Println("Welcome to Go!") }
```

You can run it using the `go run` command, as we've already seen several times in this series:

```bash
$ go run main.go
Welcome to Go!
```

The `go run` command compiles the program, creates an executable in your `/tmp` directory, and executes this binary in one step. If you want to execute several files at once, you can pass them all as arguments to `go run`:

```bash
$ go run main.go time.go input.go
```

Or you can use a wildcard:

```bash
$ go run *.go
```

You can also run an entire package at once, as of Go v1.11:

```bash
$ go run ./foo # Run the package in the `foo` directory
$ go run .     # Run the package in the current directory
```

## Formatting code with gofmt

If you've been writing Go code for any length of time, you will know that there are strict conventions for how code should be formatted. The `gofmt` command is what enforces these conventions for all Go code in existence.

The code snippet shown in the previous section was not formatted properly, so let's format it with `gofmt`, as demonstrated below:

```bash
$ gofmt main.go
package main

import "fmt"

func main() { fmt.Println("Welcome to Go!") }
```

This formats the code in the source file and prints the result to the standard output. If you want to overwrite the source file with the formatted output, you need to add the `-w` flag.

```bash
$ gofmt -w main.go
```

To format Go source files recursively (current directory and subdirectories),
specify a `.` as the argument to `gofmt`.

```bash
gofmt .
```

## Fixing import statements

Before you can use a package in your code, you need to import it. If you fail to do so, the code will not compile, and an error will be displayed. Given the following code in your `main.go` file,

```go
package main

func main() {
	fmt.Println("Welcome to Go!")
}
```

You should see the following error if you attempt to execute the program:

```bash
$ go run main.go
# command-line-arguments
./main.go:4:2: undefined: fmt
```

Before the code can compile, the `fmt` package must be imported. You can add the necessary code manually or use the `goimports` command, which adds the necessary import statements for you.

```bash
$ goimports main.go
package main

import "fmt"

func main() {
	fmt.Println("Welcome to Go!")
}
```

The command also removes any imported packages that are no longer referenced and formats the code in the same style as `gofmt`. So, you can also think of `goimports` as a replacement for `gofmt`.

The value of `goimports` becomes apparent if you set your editor to run it on saving a Go source file. That way, you won't need to worry about importing a package before using it or importing statements that are no longer needed. It'll be done automatically for you as soon as you save the file. Most code editors have some sort of plugin or setting that should help with this.

## Building your project

To build an executable binary for your program, use the `go build` command. This will output a single binary in the current directory:

```bash
$ go build
$ ./demo
Welcome to Go!
```

The binary produced with `go build` is specific to your operating system architecture, and it contains everything you need to run the program. Therefore, you can transfer it to another machine with the same architecture, and it'll run in the same manner even if Go is not installed.

If you want to cross-compile a binary for an architecture other than your own, all you need to do is change the values of the `GOOS` and `GOARCH` environmental variables before running the `go build` command.

For example, the following command can be used to produce a binary for a 64-bit Windows machine:

```bash
$ GOOS=windows GOARCH=amd64 go build
```

To compile for Linux, macOS, ARM, Web Assembly, or other targets, please refer to the [Go docs](https://golang.org/doc/install/source#environment) to see the combinations of `GOOS` and `GOARCH` that are available to you.

## Installing Go binaries

The `go install` command is an alternative to `go build` if you want to be able to run the program from outside its source directory.

Assuming your `main.go` file is in a directory called `demo`, the following command will create a `demo` binary in your `$GOPATH/bin` directory.

```bash
$ go install
```

The `$GOPATH` should be `$HOME/go` on most computers. You can check it with the
`go env` command:

```bash
$ go env GOPATH
/home/ayo/go
```

If you list the contents of `$GOPATH/bin`, you should see a `demo` binary:

```bash
$ ls $GOPATH/bin
demo
```

This binary can be executed by running the `demo` command from any location on your filesystem. This only works as long as the `$GOPATH/bin` directory has been added to your `$PATH`.

```bash
$ demo
Welcome to Go!
```

## Listing package information

The default invocation of `go list` returns the name of the import path for the directory you are currently in or the provided package path:

```bash
$ go list
github.com/ayoisaiah/demo
$ go list github.com/joho/godotenv
github.com/joho/godotenv
```

We can customize the output of the `go list` command using the `-f` flag, which allows you to execute a Go template that has access to the internal data structures of the go tool. For example, you can list the name of the `fmt` using the command below:

```bash
$ go list -f "{{ .Name }}" fmt
fmt
```

That's not very interesting on its own, but there's more. You can print all the dependencies of a package using the `{{ .Imports }}` template. Here's the output for the `fmt` package:

```bash
$ go list -f "{{ .Imports }}" fmt
[errors internal/fmtsort io math os reflect strconv sync unicode/utf8]
```

Or, you can list the complete set of transitive dependencies for a package:

```bash
$ go list -f "{{ .Deps  }}" fmt
[errors internal/bytealg internal/cpu internal/fmtsort internal/oserror internal/poll internal/race internal/reflectlite internal/syscall/execenv internal/syscall/unix internal/testlog io math math/bits os reflect runtime runtime/internal/atomic runtime/internal/math runtime/internal/sys sort strconv sync sync/atomic syscall time unicode unicode/utf8 unsafe]
```

You can also use the `go list` command to check for updates to dependencies and subdependencies:

```bash
$ go list -m -u all
```

Or, check for updates to a specific dependency:

```bash
$ go list -m -u go.mongodb.org/mongo-driver
go.mongodb.org/mongo-driver v1.1.1 [v1.4.0]
```

There's a lot more you can do with the `go list` command. Make sure to check the [documentation](https://golang.org/pkg/cmd/go/internal/list/) for flags and other template variables that may be used with the command.

## Displaying documentation for a package or symbol

The `go doc` command prints the documentation comments associated with the item
identified by its arguments. It accepts zero, one, or two arguments.

To display the package documentation for the package in the current directory, use the command without any arguments:

```bash
$ go doc
```

If the package is a command (the `main` package), documentation for exported symbols are omitted in the output, except when the `-cmd` flag is provided.

We can use the `go doc` command to view the docs for any package by passing the import path for the package as an argument to the command:

```bash
$ go doc encoding/json
package json // import "encoding/json"

Package json implements encoding and decoding of JSON as defined in RFC 7159. The mapping between JSON and Go values is described in the documentation for the Marshal and Unmarshal functions.
[truncated for brevity]
```

If you want to view the documentation for a specific method in a package, simply pass it as a second argument to `go doc`:

```bash
$ go doc encoding/json Marshal
```

A second command, `godoc`, presents the documentation for all Go packages (including any third-party dependencies you have downloaded) as a webpage. Running the command will start a web server on port 6060 by default, but you can change the address with the `-http` flag.

```bash
$ godoc -http=:6060
```

![Local Go package documentation for fmt in Chrome](https://i.imgur.com/yLLrkt6.png)

## Performing static analysis

The `go vet` command is one that helps with detecting suspicious constructs in your code that may not be caught by the compiler. These are things that may not necessarily prevent your code from compiling but will affect code quality, such as unreachable code, unnecessary assignments, and malformed format string arguments.

```bash
$ go vet main.go # Run go vet on the `main.go` file
$ go vet .       # Run go vet on all the files in the current directory
$ go vet ./...   # Run go vet recursively on all the files in the current directory
```

This command is composed of several analyzer tools, which are [listed here](https://golang.org/cmd/vet/), with each one performing a different check on the
file.

All checks are performed by default when the `go vet` command is executed. If you only want to perform specific checks (ignoring all the others), include the name of the analyzer as a flag and set it to `true`. Here's an example that runs the `printf` check alone:

```bash
$ go vet -printf=true ./...
```

However, passing the `-printf=false` flag to `go vet` will run all checks except `printf`.

```bash
$ go vet -printf=false ./...
```

## Adding dependencies to your project

Assuming you have Go modules enabled, `go run`, `go build`, or `go install` will download any external dependencies needed to fulfill the import statements in your program. By default, the latest tagged release will be downloaded or the latest commit if no tagged releases are available.

If you need to download a specific version of a dependency other than the one Go fetches by default, you can use the `go get` command. You can target a specific version or commit hash:

```bash
$ go get github.com/joho/godotenv@v1.2.0
$ go get github.com/joho/godotenv@d6ee687
```

This method may be used to upgrade or downgrade dependencies as needed. Any downloaded dependencies are stored in the module cache located at `$GOPATH/pkg/mod`. You can use the `go clean` command to clear the module cache for all projects in one go:

```bash
$ go clean -modcache
```

## Working with Go modules

We covered Go modules in detail in [part 2](https://www.honeybadger.io/blog/golang-go-package-management/) of this series. Here's a summary of the commands you need to know to work with modules effectively:

- `go mod init` will initialize modules in your project.
- `go mod tidy` cleans up unused dependencies or adds missing ones. Make sure to
  run this command before committing to any changes to your code.
- `go mod download` will download all modules to the local cache.
- `go mod vendor` copies all third-party dependencies to a `vendor` folder in
  your project root.
- `go mod edit` can be used to replace a dependency in your go.mod file with a local or forked version. For example, if you need to use a fork until a patch is merged upstream, use the following code:

```bash
go mod edit -replace=github.com/gizak/termui=github.com/ayoisaiah/termui
```

## Testing and benchmarking your code

Go has a built-in testing command called `go test` and a `testing` package, which can be combined to provide a simple but complete unit testing experience. The `test` tool also includes benchmarking and code coverage options to help you profile your code even more.

Let's write a simple test to demonstrate some of the capabilities of the `test` tool. Modify the code in your `main.go` file, as shown below:

```go
package main

import "fmt"

func welcome() string {
	return "Welcome!"
}

func main() {
	fmt.Println(welcome())
}
```

Then, add the test in a separate `main_test.go` file in the same directory:

```go
package main

import "testing"

func TestWelcome(t *testing.T) {
	expected := "Welcome to Go!"
	str := welcome()
	if str != expected {
		t.Errorf("String was incorrect, got: %s, want: %s.", str, expected)
	}
}
```

If you run `go test` in the terminal, it should fail:

```bash
$ go test
--- FAIL: TestSum (0.00s)
    main_test.go:9: String was incorrect, got: Welcome!, want: Welcome to Go!.
FAIL
exit status 1
FAIL	github.com/ayoisaiah/demo	0.002s
```

We can make the test pass by modifying the return value of the `welcome` function in the `main.go` file.

```go
func welcome() string {
	return "Welcome to Go!"
}
```

Now, the test should pass successfully:

```bash
$ go test
PASS
ok  	github.com/ayoisaiah/demo	0.002s
```

If you have lots of test files with test functions but only want to selectively run a few of them, you can use the `-run` flag. It accepts a regular expression string to match the test functions that you want to run:

```bash
$ go test -run=^TestWelcome$ . # Run top-level tests matching "TestWelcome"
$ go test -run= String .       # Run top-level tests matching "String" such as "TestStringConcatenation"
```

You should also know the following test flags, which often come in handy when
testing Go programs:

- The `-v` flag enables the verbose mode so that the names of the tests are printed in the output.
- The `-short` flag skips long-running tests.
- The `-failfast` flag stops testing after the first failed test.
- The `-count` flag runs a test multiple times in succession, which is
  useful if you want to check for intermittent failures.

### Code coverage

To view your code coverage status, use the `-cover` flag, as shown below:

```bash
$ go test -cover
PASS
coverage: 50.0% of statements
ok  	github.com/ayoisaiah/demo	0.002s
```

You can also generate a coverage profile using the `-coverprofile` flag. This allows you to study the code coverage results in more detail:

```bash
$ go test -coverprofile=coverage.out
```

You will find a `coverage.out` file in the current directory after running the above command. The information contained in this file can be used to output an HTML file containing the exact lines of code that have been covered by existing tests.

```bash
$ go tool cover -html=coverage.out
```

When this command is run, a browser window pops up showing the covered lines in green and uncovered lines in red.

![Screenshot of code coverage information in Chrome](https://i.imgur.com/7AqdHdv.png)

### Benchmarking

The benchmarking tool in Go is widely accepted as a reliable way to measure the performance of Go code. Benchmarks are placed inside `_test.go` files, just like tests. Here's an example that compares the performance of different string concatenation methods in Go:

```go
// main_test.go
package main

import (
	"bytes"
	"strings"
	"testing"
)

var s1 = "random"

const LIMIT = 1000

func BenchmarkConcatenationOperator(b *testing.B) {
	var q string
	for i := 0; i < b.N; i++ {
		for j := 0; j < LIMIT; j++ {
			q = q + s1
		}
		q = ""
	}
	b.ReportAllocs()
}

func BenchmarkStringBuilder(b *testing.B) {
	var q strings.Builder
	for i := 0; i < b.N; i++ {
		for j := 0; j < LIMIT; j++ {
			q.WriteString(s1)
		}
		q.Reset()
	}
	b.ReportAllocs()
}

func BenchmarkBytesBuffer(b *testing.B) {
	var q bytes.Buffer
	for i := 0; i < b.N; i++ {
		for j := 0; j < LIMIT; j++ {
			q.WriteString(s1)
		}
		q.Reset()
	}
	b.ReportAllocs()
}

func BenchmarkByteSlice(b *testing.B) {
	var q []byte
	for i := 0; i < b.N; i++ {
		for j := 0; j < LIMIT; j++ {
			q = append(q, s1...)
		}
		q = nil
	}
	b.ReportAllocs()
}
```

This benchmark can be invoked using the `go test -bench=.` command:

```bash
$ go test -bench=.
goos: linux
goarch: amd64
pkg: github.com/ayoisaiah/demo
BenchmarkConcatenationOperator-4   	    1718	    655509 ns/op	 3212609 B/op	     999 allocs/op
BenchmarkStringBuilder-4           	  105122	     11625 ns/op	   21240 B/op	      13 allocs/op
BenchmarkBytesBuffer-4             	  121896	      9230 ns/op	       0 B/op	       0 allocs/op
BenchmarkByteSlice-4               	  131947	      9903 ns/op	   21240 B/op	      13 allocs/op
PASS
ok  	github.com/ayoisaiah/demo	5.166s
```

As you can see, the concatenation operator was the slowest of the bunch at 655509 nanoseconds per operation, while `bytes.Buffer` was the fastest at 9230 nanoseconds per operation. Writing benchmarks in this manner is the best way to determine performance improvements or regressions in a reproducible way.

## Detecting race conditions

A race detector is included with the `go` tool and can be activated with the `-race` option. This is useful for finding problems in concurrent systems, which may lead to crashes and memory corruption.

```bash
$ go test -race ./...
$ go run -race main.go
$ go build -race ./...
$ go install -race ./...
```

## Wrapping up

In this article, we covered several tools included in each Go environment and provided
short descriptions of how they can be used. This is only the tip of the iceberg, so be sure to check out the [full documentation](https://golang.org/pkg/cmd/go/) for a deeper explanation of the capabilities of each command.

Thanks for reading, and happy coding!
[1]: https://www.honeybadger.io/blog/ruby-to-go-5/#authorDetails
[2]: https://www.honeybadger.io/blog/ruby-to-go-5/