Your first Go test will fail. That's the point.

Most programming tutorials teach you to write code, then figure out if it works. Test-Driven Development flips this: you write the test first, watch it fail, then write just enough code to make it pass. This sounds backwards until you try it—then you realize you’re learning Go’s syntax, types, and patterns faster than any tutorial could teach you.

Go makes this easy because testing is built into the language. No frameworks to install, no configuration files to wrangle. You write tests in the same language as your code, run them with one command, and get immediate feedback.

Here’s how to start.

Your first test (before any real code)

Create a folder for your project and add two files:

hello.go

package main

func Hello() string {
    return ""
}

hello_test.go

package main

import "testing"

func TestHello(t *testing.T) {
    got := Hello()
    want := "Hello, World"
    
    if got != want {
        t.Errorf("got %q want %q", got, want)
    }
}

Three things to notice:

  1. Test files end with _test.go
  2. Test functions start with Test and take *testing.T as a parameter
  3. The test describes what you want, not what exists yet

Run go test in your terminal. You’ll see:

--- FAIL: TestHello (0.00s)
    hello_test.go:9: got "" want "Hello, World"
FAIL

Your test failed. Perfect. Now you know exactly what to fix.

Make it pass with the simplest code

Change hello.go:

package main

func Hello() string {
    return "Hello, World"
}

Run go test again:

PASS
ok      your-project    0.001s

You just completed one TDD cycle: Red (failing test) → Green (passing test) → Refactor (improve the code). This cycle becomes your learning rhythm.

Why this teaches you Go faster

When you write the test first, you’re forced to think about:

You’re not staring at a blank file wondering “how do I start?” The test tells you exactly what to build next.

Add a parameter: your second TDD cycle

Now you want Hello() to greet someone by name. Write the test first:

func TestHello(t *testing.T) {
    got := Hello("Chris")
    want := "Hello, Chris"
    
    if got != want {
        t.Errorf("got %q want %q", got, want)
    }
}

Run go test. It fails with a compilation error:

./hello_test.go:6:14: too many arguments in call to Hello

The test told you: Hello() needs to accept a parameter. Update your code:

func Hello(name string) string {
    return "Hello, " + name
}

Run go test. It passes. You just learned Go’s function parameter syntax by following what the test needed.

The TDD cycle as your learning guide

Every time you want to learn a new Go concept, follow this pattern:

  1. Write a test that uses the feature you want to learn

    • Want to learn maps? Write a test that expects a map
    • Want to learn interfaces? Write a test that accepts an interface
  2. Watch it fail (and read the error carefully)

    • Compilation errors teach you syntax
    • Test failures teach you behavior
  3. Write the simplest code to pass

    • Don’t solve tomorrow’s problems today
    • Just make this test pass
  4. Refactor if needed

    • Can you make it clearer?
    • Can you remove duplication?
    • The passing tests protect you while you clean up

What you can test

You can test anything that has inputs and outputs:

Commands you’ll use constantly

go test                    # Run all tests in current directory
go test -v                 # Verbose output showing each test
go test ./...              # Run tests in all subdirectories
go test -run TestHello     # Run only tests matching pattern

When you get stuck

Your test won’t compile? Good. Read the error. It tells you what’s missing.

Your test fails? Good. The error message shows what you got versus what you expected.

You don’t know how to test something? Write what you wish the code looked like in the test. The test will guide you to the implementation.

The real benefit

After a few hours of TDD, you’ll notice something: you understand Go’s syntax not from memorizing rules, but from solving problems. You know how structs work because you tested functions that return them. You understand interfaces because you tested code that accepts them.

The tests become your safety net. Want to refactor that messy function? Do it. If you break something, the tests will tell you immediately.

You’re not just learning Go. You’re learning how to write code you can trust.