liveBook · Manning

Unit test

As explained in Shipping Go: Develop, deliver, discuss, design, and go again

Unit testing evaluates individual units of code independently and focuses on a small portion of a program.

A unit test checks a small portion of code independently. Think of inspecting the boards and screws before assembling a bridge: a failed unit test is easier to investigate than a failure in the finished structure. Run these fast checks before moving to tests that involve other components or external dependencies. section 3.4 section 3.8

Start with a small behavior and build a feedback loop:

  • State the expected behavior as given-when-then cases. For a translation function, cover supported languages, unsupported input, capitalization, and extra spaces. Check the list with teammates against the business requirement. section 3.1
  • Call the function with each input and compare its result with the expected value. Add cases that fail, change the implementation, then run the tests again. section 3.3
  • Assert observable behavior rather than implementation details. Keep useful edge cases, but avoid fragile tests that break constantly; high coverage need not mean 100% coverage. section 3.3 section 3.8

An input-normalization bug

Suppose translating “hello” into German returns “hallo,” but “Hello” or a language written as “German” fails. Add those inputs to the test cases first. Normalize the word and language with lowercase conversion and whitespace trimming, then rerun the tests. Also test unsupported words and languages: their expected result is an empty string. section 3.1 section 3.3

Listing 3.12 translator.go
package translation
 
import "strings"
 
func Translate(word string, language string) string {
    word = sanitizeInput(word)                          #1
    language = sanitizeInput(language)                  #2
 
    if word != "hello" {
        return ""
    }
 
    switch language {
    case "english":
        return "hello"
    case "finnish":
        return "hei"
    case "german":
        return "hallo"
    default:
        return ""
    }
 
}
 
func sanitizeInput(w string) string {                   #3
    w = strings.ToLower(w)
    return strings.TrimSpace(w)
}

The Go implementation shows how the failing capitalization and whitespace cases lead to a shared input-sanitization function. section 3.3

Make automated unit tests an early quality check, especially around deterministic functions where the same input should produce the same result. Keep checks close to implementation so defects and unintended business-rule changes surface quickly. As the product grows, add integration and broader system tests where they earn their cost; if unit tests become an obstacle, reconsider which behaviors they assert. section 1.3.2 section 11.4.2

How Shipping Go: Develop, deliver, discuss, design, and go again covers it

How deep it goes
Works through selecting cases, asserting results, repairing failures, and refactoring; distinguishes unit tests from broader tests without developing full end-to-end testing.
Examples
Tests a Go translation function for supported languages, unsupported input, capitalization, and whitespace; also suggests deterministic balance and error-rate calculations.
What it assumes
Assumes familiarity with basic Go functions while starting the testing workflow from a minimal package and explicit expected behaviors.
What you can do after
Build it from scratch: write Go function tests, use failures to correct behavior, and automate the checks before broader testing.
How it connects
Connects unit tests to behavior-driven development, input sanitation, the testing pyramid, integration testing, and test-driven development.

Also explained in

Compare how 9 books explain unit test →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Shipping Go: Develop, deliver, discuss, design, and go again. The section links point to the parts of the book it draws on.