Let’s say you want to write an API module in Go for the Slotocash website. But before you release your API, you need to do testing, create some example code, and of course, perform some benchmarks to ensure your API performs better than the competitor.
Most people know how to use go run, to run the program without compiling the program, but there are a lot of other go commands that are very useful to know and understand in order to make the development process easier with fewer headaches.
Look at the appropriate Go documentation to see the full description of these features (and more).
See also:
Go mod init <module name>
If you are coming from a C/C++ background, you are probably very used to compiling source code using a Makefile and the make command. So although you can technically use a Makefile to compile go code by hand, using the go mod command, it will allow go to handle keeping the program dependencies up to date. Since everything is stored in the file go.mod, it is easy to view and edit the go.mod file in order to understand all the dependencies.
The go mod init command initializes and writes a new go.mod file in the current directory. The go.mod file is stored at the root directory of your project. So you only run go mod init once, not in every subdirectory of your project.
The go.mod file should not already exist. If you need to update the go.mod file, you need to run go tidy.
Go mod tidy [-e] [-v] [-go=version] [-compat=version]
go mod tidy ensures that the go.mod file matches the source code in the module. It will add any missing module requirements necessary to build the current modules packages and dependencies. It will also remove requirements that are not necessary or relevant to the package. Missing entries will be added to go.sum.
The -e flag causes go mod tidy to attempt to proceed despite errors encountered while loading packages.
The -v flag causes go mod tidy to print information about removed modules to standard error.
Go mod vendor [-e] [-v] [-o]
This is a command that is very useful, and I wish I was aware of sooner.
The go mod vendor command constructs a directory named vendor in the main module’s root directory that contains copies of all packages needed to support builds and tests of packages in the main module. When vendoring is enabled, the go command will load packages from the vendor directory instead of downloading modules from their sources into the module cache and using packages those downloaded copies.
go mod vendor also creates the file vendor/modules.txt that contains a list of vendored packages and the module versions they were copied from. When vendoring is enabled, this manifest is used as a source of module version information, as reported by go list -m and go version -m. When the go command reads vendor/modules.txt, it checks that the module versions are consistent with go.mod. If go.mod changed since vendor/modules.txt was generated, go mod vendor should be run again.
The -e flag causes go mod vendor to attempt to proceed despite errors encountered while loading packages.
The -v flag causes go mod vendor to print the names of vendored modules and packages to standard error.
The -o flag causes go mod vendor to output the vendor tree at the specified directory instead of vendor. The argument can be either an absolute path or a path relative to the module root.
Usually, modules that are imported are copied into the cache. For the most part, this is fine. But when you are trying to understand the modules or you plan to modify modules, it make things easier to have these libraries as a subdirectory of your current project. This can also help to ensure that the code that you are writing will, in the future, match the libraries your program is referencing.
If the vendor directory is present in the main module’s root directory, it will be used automatically if the go version in the main module’s go.mod file is 1.14 or higher. To explicitly enable vendoring, invoke the go command with the flag -mod=vendor. To disable vendoring, use the flag -mod=readonly or -mod=mod.
When vendoring is enabled, build commands like go build and go test load packages from the vendor directory instead of accessing the network or the local module cache. The go list -m command only prints information about modules listed in go.mod. go mod commands such as go mod download and go mod tidy do not work differently when vendoring is enabled and will still download modules and access the module cache. go get also does not work differently when vendoring is enabled.
Go mod verify
go mod verify checks that dependencies of the main module stored in the module cache have not been modified since they were downloaded.
Go build
go build compiles the module or package. You can technically run a package without compiling it using go run, but my personal preference is to compile packages before running them.
Go vet
go vet is similar to lint for C/C++. The purpose of go vet is to check the source code for logic errors that can still be compiled.
Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string.
go vet
go vet <my project>
The following is a list of checks that go vet can implement.
- asmdecl report mismatches between assembly files and Go declarations
- assign check for useless assignments
- atomic check for common mistakes using the sync/atomic package
- bools check for common mistakes involving boolean operators
- buildtag check that +build tags are well-formed and correctly located
- cgocall detect some violations of the cgo pointer passing rules
- composites check for unkeyed composite literals
- copylocks check for locks erroneously passed by value
- httpresponse check for mistakes using HTTP responses
- loopclosure check references to loop variables from within nested functions
- lostcancel check cancel func returned by context.WithCancel is called
- nilfunc check for useless comparisons between functions and nil
- printf check consistency of Printf format strings and arguments
- shift check for shifts that equal or exceed the width of the integer
- stdmethods check signature of methods of well-known interfaces
- structtag check that struct field tags conform to reflect.StructTag.Get
- tests check for common mistaken usages of tests and examples
- unmarshal report passing non-pointer or non-interface values to unmarshal
- unreachable check for unreachable code
- unsafeptr check for invalid conversions of uintptr to unsafe.Pointer
- unusedresult check for unused results of calls to some functions
-c=N
display offending line plus N lines of surrounding context
-json
emit analysis diagnostics (and errors) in JSON format
Using go vet will help you easily find simple logic errors in your code. But most of the time, in order to ensure that you are writing solid code, you will need to create actual format tests.
Equivalence class testing
Equivalence class testing is also called equivalence class partitioning. It is a software testing technique that divides the input data of a software unit into partitions of equivalent data from which test cases can be derived. In principle, test cases are designed to cover each partition at least once. This technique tries to define test cases that uncover classes of errors, thereby reducing the total number of test cases that must be developed. An advantage of this approach is reduction in the time required for testing software due to lesser number of test cases.
To put things in simpler terms, inside your function, you have a loop. In order to fully test the loop, you do not need to test every interaction of the loop. You need to test the end cases on each end, and then one or two in the middle of the loop. For example,
for i := 1; i <= 5; i++ {
fmt.Println(i)
}
In this example, you would need to test
- 0 – out of range on the lower end
- 1 – valid inside of the range on the lower end
- 5 – valid inside of the range on the upper end
- 6 – out of range on the upper end.
- 3 – valid value in the middle of the range.
When you actually go to create your tests, there are two categories of tests. The first category is “valid tests”, where you do not expect any errors to occur. With these tests, you can run multiple tests at the same time, and if the final result is the expected result, you can assume that all the intermediate tests are valid. The second set of tests are the expected error conditions. With these tests, you know that there is something wrong with the data. Since you expect every test to fail, you cannot realistically combine tests, because you will not know which test is actually triggering the failure.
Go test (testing errors)
The go test command executes test functions (whose names begin with Test) in test files (whose names end with _test.go). You can add the -v flag to get verbose output that lists all the tests and their results.
If you want more information about the go test command, read the Golang documentation on the go test command.
So our test file will be called main_test.go
package greetings
import (
“testing”
“regexp”
)
// TestHelloEmpty calls greetings.Hello with an empty string,
// checking for an error.
func TestHelloEmpty(t *testing.T) {
msg, err := Hello(“”)
if msg != “” || err == nil {
t.Fatalf(`Hello(“”) = %q, %v, want “”, error`, msg, err)
}
}
$ go test -v
By running the go test command, go automatically runs all of your tests that are included in main_test.go.
Test functions should be of the format TestXxx, for example, TestAPI_AgentServices and TestAPI_AgentServicesWithFilter.
Go test (example code)
Did you ever want to just play around with what a standard module (or open source module) can do without having to write a full program? You just need a function. Well, the go test command with a main_test.go file and example functions will do what you need to do.
func ExampleHello() {
fmt.Println(“hello”)
// Output: hello
}
func ExampleSalutations() {
fmt.Println(“hello, and”)
fmt.Println(“goodbye”)
// Output:
// hello, and
// goodbye
}
Example functions can be of the following format:
- func Example_suffix() { … }
- func ExampleF_suffix() { … } (example of a function)
- func ExampleT_suffix() { … } (example of a type)
- func ExampleT_M_suffix() { … } (example of a method)
As I explained previously, for valid code where you are not expecting any errors, you can run multiple tests at the same time. The advantage of these example functions, as opposed to test functions, is that this code can be used as is within documentation and tutorials.
The // comment lines is what Go compares with the standard output of the function when the tests are run
Go test –bench (benchmark code)
The benchmark function must run the target code b.N times. During benchmark execution, b.N is adjusted until the benchmark function lasts long enough to be timed reliably.
The naming of benchmark functions follow the same pattern as the Test and Example functions,
func BenchmarkRandInt(b *testing.B) {
for i := 0; i < b.N; i++ {
rand.Int()
}
}
Go test -coverprofile=coverage.out
When writing tests, it is often important to know how much of your actual code the tests cover. This is generally referred to as coverage. That is what this feature will tell you.
Go tool cover -html=coverage.out
The coverage.out file can be viewed in a web browser. In the web browser, you will see your source code. The green text indicates test coverage of your source code, whereas the red text indicates that your tests did not do any coverage.
Project directory structure
- ./HelloWorld/
- ./HelloWorld/go.mod (auto generated)
- ./HelloWorld/main.go
- ./HelloWorld/main_test.go
- ./HelloWorld/vendor/ (directory, vendor files stored here)
- ./HelloWorld/testdata/ (directory, testing related datafiles)
Summary
From the very beginning, get into the habit of writing code using multiple files and, if appropriate, multiple packages. Init your projects. Setup a vendor directory, so you have easy access to the outside modules you use. Use go vet to validate your code for logic errors. Finally, use go test with testing functions, example functions, and benchmark functions.