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:

  1. Basic knowledge of programming
  2. Go installed on your system (you can find an installation guide here)
  3. A CircleCI account
  4. 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: a struct type that defines an article in the blog. It contains the properties Title and Body with json bindings to title and body respectively.
  • Blog: a struct type representing the blog itself. It consists an array of Articles.
  • New(): a method used to instantiate a new Blog. Returns a new instance of Blog.
  • SaveArticle: a method used to add a new article to a blog’s article collection.
  • FetchAll: a method used to retrieve all articles in a Blog instance. Returns an array of Articles.

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.

Blog Test - 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.

Local Test - 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.

Add Project - CircleCI

Click Set Up Project to begin.

Add Config - CircleCI

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.

Build Prompt - CircleCI

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.

Build Successful - CircleCI

Click the SUCCESS label to view the details of the deployment.

Build Details - CircleCI

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.