Test-Driven Development with Golang

Anwar Hidayat
Mar 10, 2019 · 4 min read

Hello, Anwar here. In this post, I will try to explain about Test-Driven Development (TDD) using the Go programming language. So, there will be a little technical stuff here.

Photo by Chris Liverani on Unsplash

TDD

Test-Driven Development or TDD for short is a process in software development that relies on the short development cycle: red, green, and refactor (optional). Red is the process where a developer adds a test that will fail when tested. Green is the process where a developer writes code to pass the test. And refactor is the process where a developer will refactor or reconstruct the code that’s already passed if needed. (source)

Why TDD?

TDD enables us to break the problem into smaller chunks. It will be easier to solve a small problem at once rather than solving a big problem at once. It also helps us finding defects code when we modify the existing code.


To illustrate, let’s create a simple project using the Go programming language. Why did I choose Go? There’s no particular reason. It is just my preference for this post.

Guess Age

The project we will create is called Guess Age. This is a simple project where the user will be asked to guess the age of a person until they guess correctly. The age will be random from 0 to 100 inclusive. There are three conditions:

  • when user guess too high, return the message “too high”
  • when user guess too low, return the message “too low”
  • when user guesses correctly, return message “correct”

1. Create Struct

Let’s create a struct named Person. This struct will be the main struct for our project. This code below will be in human.go file.

package humanimport (
"errors"
"math/rand"
)
var (
// TOOHIGHERROR will be the error message when guessed too high
TOOHIGHERROR = "too high"
// TOOLOWERROR will be the error message when guessed too low
TOOLOWERROR = "too low"
)
// Person will be the type of person
type Person struct {
hiddenAge int
}
// NewPerson will initialize person
func NewPerson() * Person {
age: = rand.Intn(101)
person: = Person {}
person.hiddenAge = age
return &person
}
// GuessAge will guess the age of the person
func(p * Person) GuessAge(age int) error {
return nil
}

In the code above, notice that we only have two functions. The first is NewPerson function to initialize Person while the other is GuessAge to guess the age. In the NewPerson function, we assign age with a random number. In the GuessAge, we only return nil value, for now.

2. Red Cycle

Now, let’s go to the read cycle, which is adding a test to make it fail. The code below will be in human_test.go file.

package humanimport (
"testing"
)
func TestGuessAgeTooHigh(t * testing.T) {
person: = NewPerson()
actual: = person.GuessAge(person.hiddenAge + 1)
expeced: = TOOHIGHERROR
if actual == nil {
t.Errorf("expected: %s - actual: nil", expeced)
return
}
if actual.Error() != expeced {
t.Errorf("expected: %s - actual: %s",
expeced, actual.Error())
}
}

In the code above, we expect that when the user input a higher number age than the hidden value, then the error expected is TOOHIGHERROR. If we run the test, it will fail. In order to make it pass, let’s go to the green cycle.

3. Green Cycle

To pass the test, let’s modify our GuessAge function in human.go file into the following.

// GuessAge will guess the age of the person
func(p * Person) GuessAge(age int) error {
if age > p.hiddenAge {
return errors.New(TOOHIGHERROR)
}
return nil
}

When the code is modified such as the above, then the test will pass. After repeating the process of adding a test and passing the test, the code will be like the following:

// GuessAge will guess the age of the person
func(p * Person) GuessAge(age int) error {
if age > p.hiddenAge {
return errors.New(TOOHIGHERROR)
}
if age < p.hiddenAge {
return errors.New(TOOLOWERROR)
}
return nil
}

And for the test file will be like the following:

package humanimport (
"testing"
)
func TestGuessAgeTooHigh(t * testing.T) {
person: = NewPerson()
actual: = person.GuessAge(person.hiddenAge + 1)
expeced: = TOOHIGHERROR
if actual == nil {
t.Errorf("expected: %s - actual: nil", expeced)
return
}
if actual.Error() != expeced {
t.Errorf("expected: %s - actual: %s",
expeced, actual.Error())
}
}
func TestGuessAgeTooLow(t * testing.T) {
person: = NewPerson()
actual: = person.GuessAge(person.hiddenAge - 1)
expeced: = TOOLOWERROR
if actual == nil {
t.Errorf("expected: %s - actual: nil", expeced)
return
}
if actual.Error() != expeced {
t.Errorf("expected: %s - actual: %s",
expeced, actual.Error())
}
}
func TestGuessAgeCorrect(t * testing.T) {
person: = NewPerson()
actual: = person.GuessAge(person.hiddenAge)
if actual != nil {
t.Errorf("expected: nil - actual: %s", actual.Error())
return
}
}

4. Refactor

Refactor process is optional. Since this project is really simple, we don’t really need it.

Wrapup

After finishing the process, we can add business logic in our project, such as in main.go file. And yes, that’s basically how to implement TDD using the Go programming language.

One more thing, you can go to this link for the detailed project.

Anwar Hidayat

Written by

I like artificial intelligence.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade