For a beginner coming from languages like Ruby or JS, Go can be tricky to setup for unit tests. The module system is more opinonated than the other two languages, so it’s unintuitive to learn if you’re used to Ruby or JS. Here’s how to get started with unit tests & debugging in Golang with VS Code.
Setup a Project Workspace
First step is to setup a folder for you Go project or exercises.
It’s important to know that Go started with a module system called Go Workspaces/GOPATH. With Go 1.11, a new module system called Go Modules was released aimed at replacing Go Workspaces. This page serves a great entry point to documentation/guides related to Go Modules.
This guide will use Go Modules for creating our workspace and importing packages, since Go Modules makes it easy to version dependencies and import external packages. See this article for more context behind why Go Modules was created and what problems it solves.
To setup your workspace:
1. Create an empty folder. Note it should be outside of$GOPATH/src
.
2. cd
into it and enter go mod init MODULENAME
. It will create a go.mod
file for you. Consider this file a bit like package.json
or Gemfile
in JS or Ruby. Except you won’t add your dependencies in it. Dependencies are automatically imported by the compiler from import
statements at the top of your Go files.
That’s it for this step! For example, I named my project folder go-exercises
since I’ll write small exercises with unit tests in it. I named my module go-exercises
too, so the go.mod
file looks like this:
module go-exercisesgo 1.17
What are modules & packages?
In JS & Ruby, the module systems are very flexible. You put a file over there, import it over here, there are no rules unless you want to publish your package. Go is far more strict, I’ll introduce you to how it organizes packages/modules.
First, let’s get clear on the terms module and package.
A module system is a means of breaking programs down into self-contained pieces that can be imported and reused by other pieces. The modules system generally have rules for how to expose an interface (or what is provided by the module) and for expressing the dependencies required by the module.
A package can mean generally a self-contained unit of software that can be re-distributed on package registries like NPM or RubyGems.
However, both terms refer to specific concepts in Golang.
In Go, a module is a “collection of related Go packages that are versioned together as a single unit”. In other words, the equivalent of an NPM package or RubyGem.
A package on the other hand is a self-contained piece of your program. A “module” from the earlier definition.
In Go, pieces of your program are always organized in packages. A package is one or more Go files in a folder that share the same scope.
For more context on this, this part of the Go wiki entry on Modules explains these terms.
Creating A Package
Now that we’ve cleared-up the concepts “module” and “package”, let’s create a package with a unit test.
Create a folder within the module we initialized before (the folder with go.mod
). This will be your new package.
For example, I created a folder named bubble_sort
. Inside bubble_sort
, you can create Go files with any name, but the package
declaration at the top of each file must match the name of the folder. The folder defines the name of the package.
For consistency, I just named the file bubble_sort.go
, the same as the package.
Let’s say you’re not doing TDD and implemented the bubble sort first. This is what it would look like:
Since the first letter of the function name is capitalized, if I import the bubble_sort
package (note I didn’t say the file, you import packages in Go), the function will be part of the public interface of that package. I’ll show an example later.
Add Unit Tests
For writing unit tests, we’ll use the standard library package testing
. There are a lot of details to it’s usage. This Digital Ocean guide is a good starting point.
Create a file named something_test.go
in the package you want to test. In this case I create bubble_sort_test.go
next to bubble_sort.go
. At the top of the file, I must declare the same package name as the package the file is a part of: bubble_sort
. Since both files belong to the same package, theBubbleSort
function declared in the bubble_sort.go
is available to be called here.
Inside the test file, import testing
and declare a function that starts with Test
and has one parameter t
of type *testing.T
. You can now use t
to indicate if the test failed and print an error message:
Now, cd
into your package folder and run go test
and you should see the test run!
Add Package for Assertions
Comparing the expected result and printing an error in each unit test is a lot of duplication.
Therefore, it makes sense to create a package for assertions that can be reused across tests. You can use the popular assert
package that’s part of the Testify suit of packages for writing tests. However, in my case I create my own simple assert
package to get started easily.
Create a new folder for the assert
package next to the bubble_sort
folder. Create an assert.go
file inside and declare a function Equals
which will compare the results of a test and print a message.
Here’s my implementation:
Now, import that package into your test file and replace the manual assertion with a function call:
Note the import path: go-exercises/assert
. The import path starts with the name of the module go-exercises
, followed by the name of the package assert
. Since the function Equals
is declared with a capital letter first, it’s part of the package’s public interface and is available to be called when imported in bubble_sort_test.go
.
Linting & Debugging in VS Code
Unit tests are a convenient way to run your code and verify if it’s correct. But what about when your results aren’t what you expect and you don’t understand your code’s behaviour? In that case, a debugging tool that lets you step through your code is handy.
I use VS Code as my IDE. Here’s how I configured it to lint and debug Golang.
First, install the Go VS Code extension by the Go Team. It adds lots of features related to the Go language to your IDE, like IntelliSense auto-complete, lint on save and more.
Second, you will want to set the “Go: Lint On Save” configuration option to “package” instead of “file”. This will eliminate a warning when you save your tests files. In your test file, you use a function declared in the same package. If the linter builds only the test file, it will miss it and warn that you’re using an undeclared function.
Third, you will need to setup a launch configuration for debugging, since by default it won’t work as expected. Create a .vscode
folder in your module folder. Create a launch.json
file inside and copy this configuration:
When you launch the debugger in this configuration by clicking the start button in the “Run And Debug” tab or pressing F5.
VS Code will run go test
with the debugger attached. It will target the directory of the file opened in the active tab, so it’s package: ${workspaceFolder}/${relativeFileDirname}
.
So, just open the bubble_sort.go
or bubble_sort_test.go
file, add a breakpoint and press F5. That will run the test with the debugger attached for that single package:
Conclusion
We covered Go Modules concepts, the testing
package, using a package for assertions, and linting & debugging Go in VS Code. I hope this helps you get started!
You can view thego-exercises
repo referenced in this guide here.