Go Unit Test Tutorial - CodeBeaver

Go Unit Test tutorial: Your first setup


Let’s face it: we all know testing is important, but when deadlines loom and features need shipping, comprehensive testing often slides to the bottom of the priority list. Go developers are no exception to this universal challenge – despite having a built-in testing framework, it’s not super simple to get started with go unit test.

This is where automated testing tools like CodeBeaver can transform your development workflow. In this tutorial, I’ll show you how to set up a complete testing pipeline that automatically generates, runs, and maintains tests for your Go codebase, all with minimal effort on your part.

Without thorough testing, these edge cases remain hidden until they cause problems in production. And manually writing tests to cover all these scenarios? That’s time-consuming work that often gets postponed.

Table of Contents

Who This Tutorial Is For

This guide is perfect for:

  • Developers who want to ship code faster with fewer bugs
  • Teams looking to improve code quality without hiring dedicated QA
  • Engineers who know they should be writing more tests but struggle to find the time
  • Anyone new to Go who wants to start with best practices from day one

Whether you’re working on a brand new project or maintaining a legacy codebase, the approach outlined here will help you establish a sustainable testing workflow.

What You’ll Learn about Go Unit Tests

By the end of this tutorial, you’ll have:

  • A complete Go testing environment that automatically generates tests for new code
  • An understanding of how to structure your Go code for maximum testability
  • A pipeline that maintains and updates tests as your code evolves
  • The ability to catch bugs before they reach production
  • More time to focus on writing features instead of tests

The best part? Once set up, this system largely runs itself. You’ll spend less time writing boilerplate test code and more time delivering value to your users.

Why Testing Matters for Go Projects

Go’s simplicity and focus on readability make it an excellent language for building reliable systems, but these qualities don’t make your code immune to bugs. In fact, Go’s concurrency features and error handling patterns introduce unique testing challenges:

// This looks simple, but what happens when the channel is full?
// Or when the goroutine panics?
func processData(data []string) {
    results := make(chan string, 10)

    go func() {
        for _, item := range data {
            results <- process(item) // What if this blocks forever?
        }
        close(results)
    }()

    // More code here...
}

How CodeBeaver Fits Into Your Go Workflow

CodeBeaver is an AI-powered testing assistant that integrates seamlessly with your existing Git workflow. When you open a pull request, CodeBeaver:

  1. Analyzes your Go code changes to understand their behavior and requirements
  2. Generates comprehensive test cases, including edge cases you might not have considered
  3. Executes these tests and measures coverage
  4. Identifies and fixes any failing tests
  5. Opens a pull request with the new or updated tests

Think of it as having a testing expert on your team who works autonomously, never sleeps, and has an encyclopedic knowledge of Go testing patterns and Go Unit Test in general.

Prerequisites

Before we dive into setting up our automated testing pipeline, let’s ensure you have everything needed to follow along successfully.

At a glance, you’ll need:

  • Go 1.18 or newer installed
  • Git installed
  • A GitHub, GitLab, or Bitbucket account
  • A code editor with Go support
  • Basic understanding of Go syntax and Git operations
  • Either an existing Go project or willingness to create one from scratch

If you already have all of this, you ca skip to the next section.

Required Tools

Go Installation

First, you’ll need Go installed on your machine. This tutorial targets Go 1.18 or newer, as we’ll be using some of the more recent testing features.

# Check your Go version
go version

If you don’t have Go installed or need to upgrade, visit the official Go download page and follow the installation instructions for your operating system.

Git and GitHub Account

You’ll need Git installed on your local machine and a GitHub account (alternatively, GitLab or Bitbucket will work too). CodeBeaver integrates with these platforms to monitor pull requests and deliver test improvements.

# Check your Git version
git version

If you don’t have Git, you can download it from the Git website.

Code Editor

Any code editor with Go support will work. Popular choices include:

  • VS Code with the Go extension
  • GoLand
  • Vim/Neovim with Go plugins
  • Sublime Text with Go packages

Optional: Existing Go Project

You can follow this tutorial with a brand new Go project (we’ll create one), or you can apply these steps to an existing project. If you’re using your own project, here’s what you should have:

  • A Go module initialized with go mod init
  • Some actual Go code to test (even simple functions are fine)
  • The project hosted on GitHub, GitLab, or Bitbucket

If you don’t have an existing project or want to start fresh, we’ll create a simple Go application that’s perfect for demonstrating automated testing.

Let’s Verify Everything

Let’s run a quick check to make sure you have the necessary components. In your terminal:

# Check Go installation
go version
# Should output something like: go version go1.21.1 darwin/amd64

# Check Git installation
git version
# Should output something like: git version 2.39.3

# Create a test directory
mkdir -p ~/go-test-project
cd ~/go-test-project

# Initialize a Go module
go mod init example.com/go-test-project

# Create a simple Go file
echo 'package main

import "fmt"

func main() {
    fmt.Println("Hello, testing world!")
}

func Add(a, b int) int {
    return a + b
}' > main.go

# Try to build it
go build

If everything runs without errors, you’re ready to move on to the next section where we’ll discuss Go testing fundamentals and set up our project structure.

Understanding the Go Unit Test fundamentals

Before we dive into automating your testing process with CodeBeaver, let’s take a moment to understand what makes Go’s testing approach unique. Go includes a built-in testing framework that is lightweight, yet powerful, and understanding these fundamentals will help you get the most out of your automated testing pipeline, as breeze to your Go Unit Test eldorado.

Go’s Built-in Testing Framework

Unlike many languages that require external testing libraries, Go includes testing capabilities in its standard library through the testing package. This approach aligns with Go’s philosophy of simplicity and minimalism.

A basic Go test file follows these conventions:

  1. Test files end with _test.go
  2. Test files are part of the same package as the code they test
  3. Test functions start with Test followed by a name that describes what’s being tested
  4. Test functions accept a single parameter of type *testing.T

Here’s a simple example testing our Add function:

// main_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

To run this test, you would use:

go test

It’s incredibly simple, but this simplicity is deceptive. Go’s testing framework is designed to be extended without becoming bloated.

Table-Driven Tests: The Go Testing Pattern

A hallmark of Go testing is the “table-driven” approach. Instead of writing multiple similar test functions, Go developers typically define a slice of test cases and iterate through them:

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -2, -3, -5},
        {"mixed signs", -2, 3, 1},
        {"zeros", 0, 0, 0},
    }

    for _, tc := range tests {
        t.Run(tc.name, func(t *testing.T) {
            result := Add(tc.a, tc.b)
            if result != tc.expected {
                t.Errorf("Add(%d, %d) = %d; want %d", tc.a, tc.b, result, tc.expected)
            }
        })
    }
}

This pattern has several advantages:

  1. Tests are more maintainable and easier to extend
  2. Test input and expected output are clearly visible
  3. Adding new test cases is as simple as adding a new entry to the slice
  4. Test names provide documentation on function behavior

These table-driven tests are a perfect match for AI-generated testing, as CodeBeaver can easily identify patterns and generate comprehensive test cases using this approach.

Understanding Test Coverage in Go

Test coverage is a metric that measures how much of your code is executed by your Go Unit Tests. In Go, you can check coverage with:

go test -cover

For more detailed coverage information, Go provides:

go test -coverprofile=coverage.out
go tool cover -html=coverage.out

This generates an HTML report showing which lines are covered and which aren’t. Understanding coverage is crucial for effective testing, but merely achieving high coverage doesn’t guarantee quality tests. The goal isn’t just to execute each line, but to verify correct behavior under various conditions.

Setting Up Your Go Project Structure

A well-organized Go project makes testing easier and more effective. In this section, we’ll create a project structure that’s optimized for testability and works well with CodeBeaver.

If you already have a project in place, you can skip to Quick Improvements For Existing Projects.

Standard Go Project Layout

Let’s set up a project with a standard Go layout:

mkdir -p go-test-project/{cmd,pkg,internal}
cd go-test-project
go mod init github.com/yourusername/go-test-project

This creates:

  • cmd/ – Main applications
  • pkg/ – Code that can be used by external applications
  • internal/ – Private code that can’t be imported by other projects

Creating Sample Code To Test

Let’s add a simple calculator package:

mkdir -p pkg/calculator

Create pkg/calculator/calculator.go:

package calculator

// Add returns the sum of two integers
func Add(a, b int) int {
    return a + b
}

// Subtract returns the difference between two integers
func Subtract(a, b int) int {
    return a - b
}

// Multiply returns the product of two integers
func Multiply(a, b int) int {
    return a * b
}

// Divide returns the quotient of two integers
// Returns an error if division by zero is attempted
func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, ErrDivideByZero
    }
    return a / b, nil
}

// ErrDivideByZero is returned when division by zero is attempted
var ErrDivideByZero = DivisionError{"cannot divide by zero"}

// DivisionError represents an error during division
type DivisionError struct {
    message string
}

func (e DivisionError) Error() string {
    return e.message
}

Now create a main application in cmd/calc/main.go:

package main

import (
    "fmt"
    "os"
    "strconv"

    "github.com/yourusername/go-test-project/pkg/calculator"
)

func main() {
    if len(os.Args) != 4 {
        fmt.Println("Usage: calc <number> <operation> <number>")
        os.Exit(1)
    }

    a, err := strconv.Atoi(os.Args[1])
    if err != nil {
        fmt.Println("First argument must be an integer")
        os.Exit(1)
    }

    b, err := strconv.Atoi(os.Args[3])
    if err != nil {
        fmt.Println("Third argument must be an integer")
        os.Exit(1)
    }

    var result int
    op := os.Args[2]

    switch op {
    case "+":
        result = calculator.Add(a, b)
    case "-":
        result = calculator.Subtract(a, b)
    case "*":
        result = calculator.Multiply(a, b)
    case "/":
        result, err = calculator.Divide(a, b)
        if err != nil {
            fmt.Println("Error:", err)
            os.Exit(1)
        }
    default:
        fmt.Println("Operation must be +, -, *, or /")
        os.Exit(1)
    }

    fmt.Printf("%d %s %d = %d\n", a, op, b, result)
}

Quick Improvements For Existing Projects

You can make your existing code more testable with small changes:

// Before: Hard to test
func processData() {
    data := loadFromDatabase()
    result := transform(data)
    saveToFile(result)
}

// After: Testable
func processData(loader DataLoader, saver FileSaver) error {
    data, err := loader.Load()
    if err != nil {
        return err
    }
    result := transform(data)
    return saver.Save(result)
}

These small changes make your code much easier to test without major restructuring.

Installing and Configuring CodeBeaver

Now that we have our Go project set up with a good structure, it’s time to integrate CodeBeaver to automate our testing process. The setup is straightforward and takes just a few minutes, transforming your test workflow from manual to automated. CodeBeaver will also maintain your Go Unit Test files!

Step 1: Creating a GitHub Repository

First, let’s create a GitHub repository for our project if you don’t have one already:

# Initialize git in your project directory
git init

# Add all files
git add .

# Create initial commit
git commit -m "feat: initial project setup with calculator package"

Now, create a new repository on GitHub (or GitLab/Bitbucket):

  1. Go to GitHub and click “New repository”
  2. Name it “go-test-project” (or whatever you chose earlier)
  3. Keep it public if you want to use CodeBeaver’s free tier for open source projects
  4. Don’t initialize with README since we already have our local project
  5. Click “Create repository”

Connect your local repository to GitHub:

# Add the remote repository (replace with your repo URL)
git remote add origin https://github.com/yourusername/go-test-project.git

# Push your code to main branch
git push -u origin main

Step 2: Authenticating with CodeBeaver

Now that your repository is on GitHub, let’s connect it to CodeBeaver:

  1. Navigate to codebeaver.ai and select “Sign up with GitHub” (or GitLab/Bitbucket, depending on where you hosted your repository)
  2. Authorize CodeBeaver to access your repositories when prompted
CodeBeaver Login Page

After authenticating, you’ll need to give CodeBeaver permission to access your repositories:

CodeBeaver Authorization

Click “Install CodeBeaver” to proceed. You can choose to give access to all repositories or just specific ones. Don’t worry about getting the permissions exactly right—you can always modify these settings later.

Step 3: Selecting Your Go Repository

Once authorized, you’ll see a dashboard showing your available repositories. Select your Go project repository:

CodeBeaver Repository Selection

The interface provides a clear list of your repositories, with options to search and filter if you manage many projects. Select your go-test-project repository to proceed.

Step 4: Auto-Configuring for Go Unit Test

CodeBeaver will now analyze your repository to determine:

  • That you’re using Go
  • The project structure and dependencies
  • Any existing test configurations

Based on this analysis, CodeBeaver will attempt to auto-configure itself. For a standard Go project, this process should complete successfully.

CodeBeaver Auto-Configuration

If auto-configuration succeeds, you’ll see options for how you’d like to proceed with CodeBeaver.

Customizing Configuration (Optional)

If you need to customize how CodeBeaver works with your Go project (for example, if you’re using a less common test framework or have special test requirements), you can create a codebeaver.yml file in your repository. It allows for a more nuanced configuration of your Go Unit test pipeline. For example:

# Basic go configuration
from: go

# Add custom environment variables if needed
environment:
  - GO111MODULE=on
  - CGO_ENABLED=0

# For monorepos or projects with Go code in subdirectories
# Define workspaces - this is useful if your Go code is part of a larger project
# workspaces:
#   - name: go-backend
#     path: backend
#     from: go

For most Go projects, the default configuration is sufficient, and you won’t need to manually create this file. Check out the CodeBeaver documentation on configuration for more information.

What If Auto-Configuration Fails?

If CodeBeaver can’t auto-configure your Go project (which is rare for standard Go projects), you might see an error. This usually happens when:

  • Your project has an unusual structure
  • You’re using custom test runners not recognized by CodeBeaver
  • The Go code is part of a larger, mixed-language project

In these cases, you can:

  1. Check the troubleshooting guide in the CodeBeaver documentation
  2. Add a custom codebeaver.yml configuration file to your repository, as shown above
  3. Contact CodeBeaver support for assistance

Go Unit Test: Creating Your First Test-Driven PR

Now that we have our Go project set up and CodeBeaver installed, let’s see how it all works together by creating a new feature branch and opening a pull request. This is where the magic of automated testing really shines – you’ll focus on writing your code while CodeBeaver handles the testing.

Step 1: Create a New Feature Branch

First, let’s create a new branch to implement a new feature in our calculator package:

# Make sure you're on the main branch and up to date
git checkout main
git pull

# Create and checkout a new feature branch
git checkout -b feat/advanced-calculator

Step 2: Add New Functionality

Let’s add some more advanced mathematical operations to our calculator package. Open pkg/calculator/calculator.go and add these new functions:

// Power returns a raised to the power of b
func Power(a, b int) int {
    if b < 0 {
        // For simplicity, we'll return 0 for negative exponents
        return 0
    }
    if b == 0 {
        return 1
    }
    result := 1
    for i := 0; i < b; i++ {
        result *= a
    }
    return result
}

// Factorial returns the factorial of n
func Factorial(n int) (int, error) {
    if n < 0 {
        return 0, fmt.Errorf("factorial not defined for negative numbers")
    }
    if n == 0 || n == 1 {
        return 1, nil
    }
    result := 1
    for i := 2; i <= n; i++ {
        result *= i
    }
    return result, nil
}

Don’t forget to add the import for fmt if it’s not already there:

import "fmt"

Step 3: Update the Main Application

Now let’s modify our main application to use these new functions. Open cmd/calc/main.go and add new cases to the switch statement:

switch op {
case "+":
    result = calculator.Add(a, b)
case "-":
    result = calculator.Subtract(a, b)
case "*":
    result = calculator.Multiply(a, b)
case "/":
    result, err = calculator.Divide(a, b)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(1)
    }
case "^":
    result = calculator.Power(a, b)
case "!":
    if b != 0 {
        fmt.Println("Factorial operation only requires one number")
        os.Exit(1)
    }
    result, err = calculator.Factorial(a)
    if err != nil {
        fmt.Println("Error:", err)
        os.Exit(1)
    }
default:
    fmt.Println("Operation must be +, -, *, /, ^, or !")
    os.Exit(1)
}

Step 4: Commit and Push Your Changes

Now, let’s commit these changes and push them to GitHub:

# Add the modified files
git add pkg/calculator/calculator.go cmd/calc/main.go

# Commit the changes
git commit -m "feat: add power and factorial functions to calculator"

# Push the branch to GitHub
git push -u origin feat/advanced-calculator

Step 5: Open a Pull Request

Go to your repository on GitHub and you should see a banner suggesting to create a pull request for your recently pushed branch. Click on “Compare & pull request” to open a new PR.

Fill in a descriptive title and description for your PR.

Click “Create pull request” to open the PR.

Step 6: Watch CodeBeaver in Action

Now that your PR is open, CodeBeaver will automatically start analyzing your changes. You’ll see CodeBeaver working in the GitHub PR checks section:

CodeBeaver Working

What’s happening behind the scenes:

  1. CodeBeaver detects your new PR
  2. It analyzes the changes, identifying the new functions you’ve added
  3. It recognizes that these functions need tests
  4. It starts generating appropriate test cases for the new code

Step 7: Review CodeBeaver’s PR

After a few minutes, CodeBeaver will add a comment to your PR with a link to a new PR it has created. It will look like this:

Go Unit Test - CodeBeaver comment example screenshot

This new PR contains the tests for your new functionality:

CodeBeaver Comment

Click on the PR link to see what CodeBeaver has created. You’ll see something like this:

CodeBeaver PR

This PR is opened on top of your original PR, containing the tests for your new functions.

Step 8: Examine the Generated Tests

Click on “Files Changed” to see the tests CodeBeaver has written:

CodeBeaver Tests

You will see something like this:

Go Unit Test Code Review Example

Notice how CodeBeaver has:

  1. Created table-driven tests following Go best practices
  2. Generated multiple test cases covering various edge cases
  3. Properly handled error checking
  4. Named tests in a descriptive way

Step 9: Merge the Tests

After reviewing the tests and ensuring they cover the functionality properly, merge CodeBeaver’s PR by clicking on “Merge pull request”:

Merge CodeBeaver PR

This adds the tests to your original PR. Now, when reviewers look at your feature PR, they’ll see both your new code and the corresponding tests.

Step 10: Complete Your PR Lifecycle

At this point, you can proceed with your normal PR review process. When everyone is satisfied with both your feature code and the tests that CodeBeaver generated, you can merge your PR into the main branch.

Optional: Triggering CodeBeaver with GitHub Actions

While CodeBeaver automatically responds to your pull requests as we’ve seen, you can take your workflow to the next level by integrating CodeBeaver directly into your GitHub Actions CI/CD pipeline. This gives you more control over when and how tests are generated, and allows you to customize the test generation process based on your specific needs.

Step 1: Create a GitHub Actions Workflow File

First, let’s create a GitHub Actions workflow file in your repository. Create a new directory for your workflows if it doesn’t already exist:

mkdir -p .github/workflows

Now, create a new file called codebeaver-testing.yml in this directory:

touch .github/workflows/codebeaver-testing.yml

Step 2: Configure the Workflow

Open the codebeaver-testing.yml file and add the following configuration:

name: CodeBeaver Automated Testing

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]
  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

jobs:
  test-generation:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0 # Fetches all history for all branches and tags

      - name: Set up Go
        uses: actions/setup-go@v4
        with:
          go-version: "1.21"

      - name: Trigger CodeBeaver Test Generation
        uses: codebeaver-io/codebeaver-action@v1
        with:
          api-key: ${{ secrets.CODEBEAVER_API_KEY }}
          repository: ${{ github.repository }}
          ref: ${{ github.ref }}
          action: "generate-tests"

      - name: Run Generated Tests
        run: go test -v ./...

This workflow will:

  1. Run whenever a pull request is opened or updated against the main branch
  2. Run whenever code is pushed to the main branch
  3. Allow manual triggering from the GitHub Actions tab
  4. Check out your code
  5. Set up Go in the workflow environment
  6. Trigger CodeBeaver to generate tests for any new or modified code
  7. Run the generated tests to verify they pass

Step 3: Set Up Your API Key

To use the CodeBeaver GitHub Action, you’ll need to obtain an API key:

  1. Log in to your CodeBeaver account at codebeaver.ai
  2. Navigate to your team view by clicking on Team Settings in the sidebar
  3. Look for the “API Keys” section
  4. Click on the icon next to your key to copy it

Now, add this API key as a secret in your GitHub repository:

  1. Go to your GitHub repository
  2. Click on “Settings”
  3. Click on “Secrets and variables” in the left sidebar, then “Actions”
  4. Click “New repository secret”
  5. Name the secret CODEBEAVER_API_KEY
  6. Paste your API key as the value
  7. Click “Add secret”

Step 4: Commit and Push the Workflow

Commit your new workflow file to the repository:

git add .github/workflows/codebeaver-testing.yml
git commit -m "ci: add CodeBeaver test generation workflow"
git push origin main

Example of a Composite Workflow

For larger Go projects, you might want to combine CodeBeaver test generation with other testing tools. Here’s an example of a more comprehensive workflow:

name: Go Testing Pipeline

on:
  pull_request:
    branches: [main]
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Go
        uses: actions/setup-go@v4
        with:
          go-version: "1.21"

      - name: Install dependencies
        run: go mod download

      # Static analysis with golangci-lint
      - name: Lint code
        uses: golangci/golangci-lint-action@v3

      # Generate tests with CodeBeaver
      - name: Generate tests
        uses: codebeaver-io/codebeaver-action@v1
        with:
          api-key: ${{ secrets.CODEBEAVER_API_KEY }}
          repository: ${{ github.repository }}
          ref: ${{ github.ref }}
          action: "generate-tests"

      # Run tests with race detection
      - name: Run tests
        run: go test -race -v ./...

      # Generate coverage report
      - name: Generate coverage
        run: go test -coverprofile=coverage.out ./...

      # Upload coverage to a service like Codecov
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage.out

Conclusion: Learning Go Unit Test

Congratulations! You’ve successfully set up an automated testing pipeline for your Go Unit Test using CodeBeaver, transforming testing from a manual chore into an integrated part of your development workflow.

Go Unit Test tutorial graphic

What We’ve Accomplished

Throughout this tutorial, we’ve:

  • Created a well-structured Go project following best practices
  • Integrated CodeBeaver with your GitHub repository
  • Implemented a feature branch workflow with automated test generation
  • Set up GitHub Actions to formalize the testing process

Benefits You’re Now Enjoying

  • Increased productivity: Focus on features while CodeBeaver handles testing
  • Improved code quality: Catch bugs earlier with comprehensive test coverage
  • Better documentation: Generated tests document your code’s behavior
  • Faster onboarding: New team members understand code through tests

Next Steps

To further enhance your testing pipeline:

  1. Apply CodeBeaver to existing projects
  2. Make test review part of your code review process
  3. Explore advanced testing approaches like property-based and mutation testing

Final Thoughts

By automating test creation with CodeBeaver, you’ve removed one of the biggest barriers to maintaining a well-tested codebase. While CodeBeaver generates comprehensive tests, your expertise ensures they test the right behaviors for your users.

Continue Your Testing Journey

Ready to take your testing skills to the next level? Check out our other resources:

Happy testing!

Table of Contents

Table of Contents