Go , an open-source programming language backed by Google, makes it easy to build simple, reliable, and efficient software. Go’s efficiency with network servers and its friendly syntax make it a useful alternative to Node.js. All network applications need well-tested features, and those developed in Go are no different. In this tutorial, we will be building and testing a simple Go blog.
Prerequisites
To follow this tutorial, a few things are required:
- Basic knowledge of programming
- Go installed on your system (you can find an installation guide here)
- A CircleCI account
- A GitHub account
Once all these requirements are met, we can begin the tutorial.
Setting up a simple Go blog project
To set up a Go project, create the project folder, and then navigate to the root of the folder:
mkdir go-testing
cd go-testing
Next, use the go mod
command to initialize the project at the root:
go mod init go-testing
This will initialize the project with the package name go-testing
. You can use any package name you want, as long as it follows standard package naming conventions.
Now, create a main.go
file for the entry point of the application:
package main
import "fmt"
func main(){
fmt.Println("Good to Go!")
}
In the previous code, we print the string Good to Go!
to the CLI interface. Run this code by running this command on the CLI:
go run main.go
Creating the blog model
In this step, we will create a model (in Object-Oriented Programming terms) for a fictitious blog. This model creates a new instance of a Blog
and adds articles to it.
At the root of the project, create a new Go
file, blog.go
, and paste in the following:
package main
type Article struct {
Title string `json:"title"`
Body string `json:"body"`
}
type Blog struct {
Articles []Article
}
func New() *Blog {
return &Blog{}
}
func (b *Blog) SaveArticle(article Article) {
b.Articles = append(b.Articles, article)
}
func (b *Blog) FetchAll() []Article {
return b.Articles
}
This code contains two data types and three functions that work like this:
Article
: astruct
type that defines an article in the blog. It contains the propertiesTitle
andBody
withjson
bindings totitle
andbody
respectively.Blog
: astruct
type representing the blog itself. It consists an array ofArticle
s.New()
: a method used to instantiate a newBlog
. Returns a new instance ofBlog
.SaveArticle
: a method used to add a new article to a blog’s article collection.FetchAll
: a method used to retrieve all articles in aBlog
instance. Returns an array ofArticle
s.
To test the blog model we just created, replace everything in main.go
with this:
package main
import (
"fmt"
)
func main() {
blog := New()
fmt.Println(blog)
blog.SaveArticle(Article{"My first Blog post", "Today, we will be talking about blogging"})
fmt.Println(blog)
}
This code creates a new blog instance printed on stdout
. The SaveArticle
method saves the new article instance and the blog is once again printed to stdout
. Now that you have more than one Go
file in the project, use the following command to run the two files:
go run blog.go main.go
You will see the following printed out on your CLI.
The first printout of the blog
instance is empty. The second printout takes place after we added an article, and so it contains that article.
Adding tests to the Blog Project
Time to add some tests. You will be adding test scripts that will assert that the SaveArticle
and FetchAll
methods work as expected.
Create a test file, blog_test.go
, at the root of the project and paste this in:
package main
import "testing"
func TestSaveArticle(t *testing.T) {
blog := New()
blog.SaveArticle(Article{"My title", "My Post Body"})
if blog.Articles[0].Title != "My title" {
t.Errorf("Item was not added")
}
}
func TestFetchAllArticles(t *testing.T) {
blog := New()
blog.SaveArticle(Article{"My title", "My Post Body"})
articles := blog.FetchAll()
if len(articles) == 0 {
t.Errorf("Fetch All fails")
}
}
In this code, the testing
package (which can be found in Go
’s standard library) is imported. Two functions are then written to test the SaveArticle
and FetchAll
methods respectively.
TestSaveArticle
creates a new Blog
instance and saves an Article
. It then checks the title to make sure the saved article is contained in the blog. If there is no saved article, an error causes the test to fail.
TestFetchAllArticles
creates a new Blog
instance and saves an Article
. Then it calls FetchAll
to retrieve the articles and checks that the blog contains any article. If there is no article, an error indicates that FetchAll
failed to return the new article.
Running tests locally
Run these tests using the verbose flag -v
to get more information. Enter this:
go test -v
Once the command completes, you will get this message on your CLI.
This screenshot shows that the two tests ran successfully and passed testing. Adding the -v
flag displays specific feedback on how each test ran instead of the usual summary.
Automating testing with CircleCI
Our next task is automating the testing process so tests are run once code is pushed to a remote repository. Setting up test automation with CircleCI consists of three steps.
- Create a remote repository and push the project code there
- Add the repository as a project on CircleCI
- Add the CircleCI pipeline configuration to automate the testing process
Begin by pushing your project to GitHub.
Next, go to the Add Projects page on the CircleCI dashboard.
Click Set Up Project to begin.
On the Set Up Project page, click Use Existing Config to instruct CircleCI that we will be adding a configuration file manually and not using the sample displayed. Next, you get a prompt to either download a configuration file for the pipeline or start building.
Click Start Building. This build will fail because we haWe will set that up later on.
The last step is to create the build pipeline configuration file to contain our configuration script.
At the root of the project, create a folder named .circleci
and a config.yml
file within it. Inside config.yml
enter the following configuration:
version: 2.1
jobs:
build:
working_directory: ~/repo
docker:
- image: circleci/golang:1.14.6
steps:
- checkout
- restore_cache:
keys:
- go-mod-v4-{{ checksum "go.sum" }}
- run:
name: Install Dependencies
command: go get ./...
- save_cache:
key: go-mod-v4-{{ checksum "go.sum" }}
paths:
- "/go/pkg/mod"
- run:
name: Run tests
command: go test -v
In the above script, the CircleCI Docker image for Go
is pulled in as the testing environment. The project code is then checked out of the remote repository for the project. After dependencies are installed and cached, the test script (go test -v
) runs.
Commit the changes to your project and push to the remote repository to run the script.
Click the SUCCESS label to view the details of the deployment.
Awesome!
Conclusion
Go
provides a testing suite out of the box, as we have shown in this tutorial. Go
may make it easier to practice test-driven development (TDD) by optimizing the benefits and reducing workload costs.
Happy coding!
Fikayo is a fullstack developer and author with over a decade of experience developing web and mobile solutions. He is currently the Software Lead at Tech Specialist Consulting and develops courses for Packt and Udemy. He has a strong passion for teaching and hopes to become a full-time author.