tests-unitarios-go-testing

Unit tests in Go with testing and key conventions

  • 3 min

A unit test in Go is a function that automatically checks a small piece of code.

Go includes testing support in the standard library. You don’t need to install a framework to get started, although we can add external utilities if the project requires them.

Let’s look at the idiomatic way, starting with a simple function.

Code to test

Suppose we have this file calculadora.go:

package calculadora

func Sumar(a, b int) int {
    return a + b
}
Copied!

We want to check that Sumar does what it says. The example is small, but it allows us to focus on the conventions of the testing package.

Create the test file

Tests live in files ending with _test.go.

We create calculadora_test.go:

package calculadora

import "testing"

func TestSumar(t *testing.T) {
    resultado := Sumar(2, 3)
    esperado := 5

    if resultado != esperado {
        t.Errorf("Sumar(2, 3) = %d; esperado %d", resultado, esperado)
    }
}
Copied!

There are three important conventions:

  1. The file ends with _test.go.
  2. The function starts with Test.
  3. The function receives t *testing.T.

Run the tests

From the module folder:

go test
Copied!

If everything goes well, you’ll see something like:

PASS
ok      ejemplo/calculadora    0.003s
Copied!

To run all packages in the project:

go test ./...
Copied!

That ./... means: this package and all subpackages.

Failing a test

If we change the expected value:

esperado := 6
Copied!

The test will fail:

--- FAIL: TestSumar (0.00s)
    calculadora_test.go:10: Sumar(2, 3) = 5; esperado 6
FAIL
Copied!

The message should explain what happened. A test that fails with a clear message is much more useful than one that just yells “wrong”.

t.Fatal and t.Error

t.Error marks the test as failed but continues executing the function.

t.Fatal marks the test as failed and stops that test immediately.

func TestDividir(t *testing.T) {
    resultado, err := Dividir(10, 2)
    if err != nil {
        t.Fatalf("no se esperaba error: %v", err)
    }

    if resultado != 5 {
        t.Errorf("resultado = %d; esperado 5", resultado)
    }
}
Copied!

We use Fatal when continuing makes no sense. For example, if we don’t have a valid result because the function returned an error.

Tests in the same package or in an external one

We can write tests in the same package:

package calculadora
Copied!

Or in an external package:

package calculadora_test
Copied!

The second option forces testing the package as someone would use it from the outside, only with its exported API. It is stricter and usually works well for behavior tests. Tests in the same package, on the other hand, can access non-exported details.

Coverage and race detector

We can combine full execution with coverage and race detection:

go test -cover ./...
go test -race ./...
Copied!

Coverage indicates which code the tests executed, not whether the checks are good. The race detector only finds conflicts that occur in the paths executed during the test.