liveBook · Manning

Test-driven development

As explained in Go in Action, Second Edition

A test-driven workflow is a development process that begins with failing automated tests and incrementally implements code until the tests pass.

Test-driven development makes tests the design specification: begin with measurable behavioral targets, then implement code until those tests pass. This turns testing from defensive verification into a tool for shaping APIs and enforcing contracts. section 8.0 section 8.8

A practical Go workflow proceeds in small, testable steps:

  • Define the API and its required behavior before implementing the underlying type. section 8.5
  • Create a compilable code skeleton, initialize the module, and write tests against the intended behavior. section 8.5
  • Run go test with deliberate failures, then implement behavior incrementally and use passing tests as feedback. section 8.5.2 section 8.5.6

Calculator with memory

Design a Calculator with Calculate, Store, and Recall methods. Specify addition, subtraction, multiplication, division, remembered results, unknown-operation errors, and division-by-zero errors. Start with an unimplemented skeleton; tests should assert both the expected error and a zero result for invalid calculations. Implement fields for memory, the latest result, and the latest error, then finish the methods until go test passes. section 8.5 section 8.5.2 section 8.5.6

Listing 8.10 Calculator skeleton
package basicmath

import "errors"

var (
	ErrDivByZero        = errors.New("divide by zero")
	ErrUnknownOperation = errors.New("unknown operation")
)

type Calculator struct{}

func NewCalculator() *Calculator {
	return &Calculator{}
}

func (c *Calculator) Calculate(operation string, a, b int) (int, error) {
	// not implemented
	return 0, errors.New("unimplemented")
}

func (c *Calculator) Store() {
	// not implemented
}

func (c *Calculator) Recall() int {
	// not implemented
	return 0
}

Inspect the compilable Calculator skeleton that provides the API surface before behavior is implemented. section 8.5

Listing 8.12 Testing divide by zero
func TestErrDivideByZero(t *testing.T) {
	c := basicmath.NewCalculator()
	got, err := c.Calculate("div", 1, 0)
	if !errors.Is(err, basicmath.ErrDivByZero) {
		t.Errorf("expected ErrDivByZero - got: %v", err)
	}
	if got != 0 {
		t.Errorf("error result should be 0 - got: %d", got)
	}
}

Inspect a focused test that checks both the sentinel error and returned value for division by zero. section 8.5.2

How Go in Action, Second Edition covers it

How deep it goes
Demonstrates API design, failure-first tests, implementation steps, and passing verification, while stopping short of broader TDD theory.
Examples
Uses a Go Calculator, invalid operations, division by zero, stored results, and go test failure and success output.
What it assumes
Assumes basic Go project and testing familiarity while demonstrating module setup, test functions, errors, and method design in context.
What you can do after
Build it from scratch by specifying a Go API, writing failing tests, implementing behavior, and confirming the result with go test.
How it connects
Connects TDD with unit testing, API design, test coverage, benchmarking, fuzz testing, and Go tooling.

Also explained in

Compare how 15 books explain test-driven development →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Go in Action, Second Edition. The section links point to the parts of the book it draws on.