06open 25 min

Testing in Go: from pytest fluency to table-driven habits

Translate pytest instincts into Go's standard testing package, table-driven tests, and subtests.

by the end of this lesson you can

  • Uses a slice of test cases
  • Names subtests clearly
  • Compares actual and expected values without relying on assertion magic

Overview

Python developers often arrive with strong pytest habits: compact assertions, fixtures, and a flexible testing style. Go gives you a smaller standard toolbox and expects more explicit structure in return.

In Python, you often

lean on pytest discovery, rich assertion introspection, and fixtures that make test setup feel lightweight and expressive.

In Go, the common pattern is

to use the standard testing package, write table-driven cases for related scenarios, and make inputs and expected outputs visible right in the test body.

why this difference matters

Testing is one of the fastest ways to feel at home in a new language. Once Go's table-driven style clicks, the tests feel plain rather than primitive.

Python

def test_slugify():
    assert slugify("Hello World") == "hello-world"

Go

func TestSlugify(t *testing.T) {
    got := slugify("Hello World")
    want := "hello-world"

    if got != want {
        t.Fatalf("got %q, want %q", got, want)
    }
}

Deeper comparison

Pytest style

import pytest

@pytest.mark.parametrize(
    "value,expected",
    [("Go Lang", "go-lang"), ("Test Case", "test-case")],
)
def test_slugify(value, expected):
    assert slugify(value) == expected

Go table-driven style

func TestSlugify(t *testing.T) {
    tests := []struct {
        name     string
        value    string
        expected string
    }{
        {name: "go lang", value: "Go Lang", expected: "go-lang"},
        {name: "test case", value: "Test Case", expected: "test-case"},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := slugify(tt.value)
            if got != tt.expected {
                t.Fatalf("got %q, want %q", got, tt.expected)
            }
        })
    }
}

Reflect

What does Go gain by making test cases more explicit, even when pytest feels shorter and more ergonomic?

what a strong answer notices

A strong answer mentions visibility of inputs, consistency with standard tooling, and easier scanning of scenarios in table-driven tests.

Rewrite

Rewrite this pytest-style parametrized test into Go using a table-driven test and subtests.

Rewrite this Python

@pytest.mark.parametrize("n,expected", [(2, 4), (3, 9), (4, 16)])
def test_square(n, expected):
    assert square(n) == expected

what good looks like

  • Uses a slice of test cases
  • Names subtests clearly
  • Compares actual and expected values without relying on assertion magic

Practice

Design a Go test for a function that validates usernames. Include success and failure cases, and explain whether you would use a table-driven pattern.

success criteria

  • Covers both valid and invalid inputs
  • Makes the test cases easy to scan
  • Uses failure messages that explain what went wrong without pytest-style introspection

Common mistakes

  • Looking for pytest-style fixtures and magic assertions before learning the standard testing shape.
  • Writing one-off tests repeatedly instead of recognizing when a table-driven structure is the clearer fit.
  • Expecting third-party testing helpers before getting comfortable with the standard library.

takeaways

  • Testing is one of the fastest ways to feel at home in a new language. Once Go's table-driven style clicks, the tests feel plain rather than primitive.
  • A strong answer mentions visibility of inputs, consistency with standard tooling, and easier scanning of scenarios in table-driven tests.
  • Covers both valid and invalid inputs