4 min read

Hello World in Go with Tests

Table of Contents

Welcome to your first Go adventure! 🎉

Today, we’re tackling the classic “Hello, World” program—but with tests.

Because we’re fancy like that.

Step 1: The Basics

Create a Simple Go Program

  1. Make a folder (anywhere you like).
  2. Add a file called hello.go with this code:
package main

import "fmt"

func main() {
    fmt.Println("Hello, world")
}

Run it with:

go run hello.go

Boom! You just printed “Hello, world” like it’s 1974.

How It Works

  • package main → Your program’s starting point.
  • import "fmt" → Brings in Go’s printing magic.
  • func main() → Where the fun begins.

Step 2: Let’s Test This Thing

Printing is cool, but tests are cooler. Let’s make our code testable by separating logic from side effects (like printing).

Refactor for Testability

Update hello.go:

package main

import "fmt"

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

func main() {
    fmt.Println(Hello())
}

Now, create 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)
    }
}

Run the test:

go test

Success! (Unless you broke it on purpose. You rebel.)

Wait… Go Modules?

If you’re on Go 1.16+, you might see:

go: cannot find main module; see 'go help modules'

Fix it with:

go mod init example.com/hello

This creates a go.mod file. Now go test should work!

Step 3: Make It Personal

Let’s greet people by name. Test first!

func TestHello(t *testing.T) {
    got := Hello("Chris")
    want := "Hello, Chris"

    if got != want {
        t.Errorf("got %q, want %q", got, want)
    }
}

The compiler yells at you (helpfully):

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

Update the Function

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

Run tests again. Green light! 🚦

Step 4: Handle Empty Names

What if someone doesn’t give a name? Default to “World”.

Test It

t.Run("empty string defaults to 'world'", func(t *testing.T) {
    got := Hello("")
    want := "Hello, World"
    assertCorrectMessage(t, got, want)
})

Implement It

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

Step 5: Multilingual Greetings

Now let’s say hello in Spanish! 🌍

Test First

t.Run("in Spanish", func(t *testing.T) {
    got := Hello("Elodie", "Spanish")
    want := "Hola, Elodie"
    assertCorrectMessage(t, got, want)
})

Update the Function

func Hello(name, language string) string {
    if name == "" {
        name = "World"
    }

    switch language {
    case "Spanish":
        return "Hola, " + name
    default:
        return "Hello, " + name
    }
}

Bonus: Add French ("Bonjour") or any language you want!

Step 6: Refactor Like a Pro

Clean up magic strings with constants:

const (
    spanish = "Spanish"
    french  = "French"

    englishPrefix = "Hello, "
    spanishPrefix = "Hola, "
    frenchPrefix  = "Bonjour, "
)

func Hello(name, language string) string {
    if name == "" {
        name = "World"
    }

    prefix := englishPrefix
    switch language {
    case spanish:
        prefix = spanishPrefix
    case french:
        prefix = frenchPrefix
    }

    return prefix + name
}

Key Takeaways

TDD Workflow:

  1. Write a failing test.
  2. Make it pass.
  3. Refactor.

Go Basics Learned:

  • Functions (func)
  • Variables & constants (:=, const)
  • Conditionals (if, switch)
  • Testing (testing package)

Best Practices:

  • Keep logic separate from side effects.
  • Use constants for magic strings.
  • Write small, focused tests.

Next Up: Integers.

Support my open source work 🚀

Let's Connect

I'm open to discussing new opportunities. Feel free to connect with me or send me an email at [email protected]