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.
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:
_test.goTest and take *testing.T as a parameterRun 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.
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.
When you write the test first, you’re forced to think about:
Hello() returns a string)You’re not staring at a blank file wondering “how do I start?” The test tells you exactly what to build next.
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.
Every time you want to learn a new Go concept, follow this pattern:
Write a test that uses the feature you want to learn
Watch it fail (and read the error carefully)
Write the simplest code to pass
Refactor if needed
You can test anything that has inputs and outputs:
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 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.
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.