liveBook · Manning

Test

As explained in Learn Go with Pocket-Sized Projects

A test case is a defined test scenario containing inputs and the expected result of execution.

A test case defines an input or situation and the result expected when code runs. An automated test executes that case and reports whether the result matches; looking at output once is not enough. A visually subtle difference, such as an unexpected space character, can otherwise go unnoticed. Repeatable tests help catch changes that break earlier behavior. section 2.1.2 section 2.3.2

Start with one scenario: prepare the input and expected result, call the function, then compare what you got with what you wanted. In a Go Test function, report a mismatch with t.Errorf; run it with go test. Include unsupported or invalid inputs as well as successful ones—those cases check the safety nets. section 2.2.2 section 2.1.2

Listing 2.13 main_internal_test.go: Table-driven tests
func TestGreet(t *testing.T) {
    type testCase struct {
        lang language
        want string
    }

    var tests = map[string]testCase{ #1
        "English": {
            lang: "en",
            want: "Hello world",
        },
        "French": {
            lang: "fr",
            want: "Bonjour le monde",
        },
        "Akkadian, not supported": {
            lang: "akk",
            want: `unsupported language: "akk"`,
        },
        "Greek": {
            lang: "el",
            want: "Χαίρετε Κόσμε",
        },
        ...
        "Empty": {
            lang: "",
            want: `unsupported language: ""`,
        },
    }

    // range over all the scenarios
    for name, tc := range tests {
        t.Run(name, func(t *testing.T) {
            got := greet(tc.lang) #2

            if got != tc.want { #3
                t.Errorf("expected: %q, got: %q", tc.want, got)
            }
        })
    }
}

The greeting test stores each scenario’s language and expected message together, then runs named subtests through the same comparison. Adding a case requires another table entry rather than another copy of the test body. section 2.3.2

Choose the shape that keeps each case clear. A table works when scenarios share an execution and assertion pattern. When assertions differ substantially, use separate named functions or named subtests. For values generated during execution, assert the stable property you require—for example, that an ID or creation time is not empty—rather than expecting a fixed value. section 2.3.2 section 10.3.1

Test the edge, not an accidental detail

For a maze solver, tiny image fixtures represent a cross, branches, dead ends, and a path to treasure. Each case checks how many paths the solver publishes. A buffered channel holds those messages while the test runs; the test does not assume an order for neighboring paths that the implementation does not guarantee. section 9.3.6

Keep the test’s scope deliberate. A handler unit test can supply a prepared URL path value and check its response; testing the full request flow would require an integration test. A service handler can receive a small interface and a stub instead of starting an external database. Conversely, if a unit test would merely duplicate an implementation detail, an integration test across operations may be more useful. A passing test establishes the checked behavior, not the absence of every bug. section 11.3.1 section 8.4.3 section 7.1.3 section 7.3

How Learn Go with Pocket-Sized Projects covers it

How deep it goes
Works through assertions, named scenarios, table-driven tests, stubs, and unit-test boundaries; also cautions against implementation-bound or order-dependent checks.
Examples
Uses greeting languages, maze image fixtures, habit validation, and an HTTP handler to demonstrate different inputs and assertions.
What it assumes
Assumes a working Go development environment; teaches automated checks and the first Go test patterns before moving to tables and service tests.
What you can do after
Practice/perform Go tests by defining scenarios, running named subtests, checking results, and choosing assertions that match each case.
How it connects
Connects test cases to subtests, integration testing, dependency injection, interfaces, concurrency, and continuous integration.

Also explained in

Compare how 53 books explain test →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Go with Pocket-Sized Projects. The section links point to the parts of the book it draws on.