Introduction: Why TDD is Not a Religion, But a Lifeline
\n\nImagine you're fixing the wiring in an old house. You flip the main switch, but you're still afraid to put your hands in the panel. That's roughly how a developer feels when making changes to a project without tests. Especially in Go — a language where static typing gives a false sense of security.
\n\nTest-Driven Development (TDD) in Go is not a trendy fad or a cult ritual. It's a methodology that turns the fear of refactoring into confidence. According to an IBM study, projects using TDD show 40% fewer defects. And in Go, where the compiler already eliminates half the errors, TDD finishes off the remaining 50%.
\n\nIn this article, we won't be giving lectures. We'll take a real task: we'll write a small HTTP service for URL shortening. And we'll go through the entire TDD journey — from a red test to green and refactoring. Let's go!
\n\nWhat is TDD Really?
\n\nIf you've heard of Red-Green-Refactor — you're already in the know. If not, remember three simple steps:
\n\n- \n
- Red — write a test that is expected to fail (because the functionality doesn't exist yet). \n
- Green — write the minimum code to make the test pass. Not perfect, just working. \n
- Refactor — improve the code without changing its behavior. Tests are our safety net. \n
In Go, this cycle is especially pleasant thanks to the built-in testing package. You don't need to install jest or pytest — everything is already in the standard library. But many beginners ignore tests, thinking Go is "safe" anyway. And that's a mistake.
Why Does TDD in Go Work Better Than in Other Languages?
\n\nThe Go community loves simplicity. TDD here doesn't turn into bureaucracy with mocks and stubs. Instead, you write table-driven tests — compact, readable, understandable. One test case — one line. And yes, Go tests are fast. Milliseconds for hundreds of cases is normal.
\n\nBy the way, the official Go blog has emphasized many times: "Tests are documentation." If your test reads like a specification — you're doing everything right.
\n\nSetting Up the Environment: What You Need to Start
\n\nI assume you already have Go installed. If not — brew install go or apt install golang-go. Check the version: go version. Go 1.21+ is sufficient for this article.
Let's create a project:
\n\nmkdir url-shortener-tdd\ncd url-shortener-tdd\ngo mod init url-shortener\n\nDone. No frameworks — just the standard library and test-driven development in Go.
\n\nWriting the First Test: Red
\n\nLet's say we want a function Shorten(url string) string that turns a long URL into a short hash. It doesn't exist yet — and that's perfect. Let's write a test:
// shortener_test.go\npackage main\n\nimport "testing"\n\nfunc TestShorten(t *testing.T) {\n url := "https://pythonlib.ru/very/long/path?with=params&and=stuff"\n result := Shorten(url)\n\n if len(result) == 0 {\n t.Error("Shorten returned an empty string")\n }\n\n if len(result) > 10 {\n t.Errorf("Expected a short hash, got %d characters", len(result))\n }\n}\n\nRun it: go test -v. We see red (well, technically — a compilation error). The Shorten function doesn't exist. Great, we are at the Red stage.
Minimum Code: Green
\n\nNow we write just enough code to make the test pass. No cryptography, just a hash:
\n\n// shortener.go\npackage main\n\nimport (\n "crypto/sha256"\n "fmt"\n)\n\nfunc Shorten(url string) string {\n hash := sha256.Sum256([]byte(url))\n return fmt.Sprintf("%x", hash[:4]) // take the first 4 bytes\n}\n\nRun the test again. Green? Excellent. But the code is terrible — the hash is collision-prone, and we don't even check if it's