Basic TDD: Red-Green-Refactor Pattern

Hi, guys, welcome to my first post in Medium!

In this post, I want to share about one of basic pattern in TDD, the Red-Green-Refactor pattern, using Golang. Hope you can enjoy and learn something from it!

About Red-Green-Refactor

The Red stage

The Green stage

Refactor

This pattern is more of a cycle. We may repeat the cycle every time we add more functionality in our code.

Example: An Article Model

Let’s say we want to develop a kind of Blog where we can write posts/articles in it, just like in Medium but very much simpler. In doing so, we will need the model of the posts/articles. We will try to develop this model, let’s call it Article model, using TDD and its Red-Green-Refactor pattern.

First, we define functionality we want to have upon this Article model, or in Golang, Article struct:

  1. An empty constructor for the struct
  2. It stores values related to a post/article, like the title, the author, and the content. We also want to add some technical values in it such as the ID, createdAt, etc. These values will be represented as fields in the model.
  3. Some getters and setters for some fields

Now let’s get started! We will do it step by step, start from functionality number one, two, then three.

Let’s create a new file called article_test.go in a model package. First, we want to write the first test for our functionality number one: the constructor. The test for it will look like much more as follow:

func TestArticleModel(t *testing.T) {
var article *model.Article

t.Run("test_constructor", func(t *testing.T) {
article = model.NewArticle()

assert.NotNil(t, article, "article should not be nil")
})
}

The code above will result in a compile error as we don’t have model.Article and model.NewArticle() yet. Don’t worry, it is fine. In fact, this is what we wanted to have. Remember, the first stage in the pattern is the Red Stage. In this stage, we are free to define how we want to call/use the functions/models we are about to have. Sometimes, it will cause some reds, so that’s okay.

In order to achieve the next stage, that is the Green Stage, we need to do something so that our reds become green. How’d we do that? Of course, we start to do the implementation of model.Article and model.NewArticle().

We create a new file article.go and write our implementation code there.

type Article struct {
}

func NewArticle() *Article {
return &Article{}
}

Let’s stop right here for a second.

The implementation looks silly, right? We don’t have any field in the Article model. Remember, we are doing this step by step and we aimed to complete the functionality number one first, that is for the constructor. This code is just enough for that. Our previous test that was red, now it is green. That is all it takes for the Green Stage.

After achieving the Green Stage, it’s time to do some refactoring. In this step, what I notice is we can’t do any. That’s okay, we’ll just move the next functionality. As per this step, we have completed one cycle of Red-Green-Refactor in TDD. Congratulations!

Take a short break and put check mark upon our first functionality as it is done!

Let’s move to functionality number two: the fields. Let’s add a new test for fields in Article. Put this new test inside TestArticleModel.

t.Run("test_fields", func(t *testing.T) {
article = model.NewArticle()

_ = article.Id
_ = article.Title
_ = article.Content
_ = article.Author
_ = article.CreatedAt
_ = article.CreatedBy
_ = article.UpdatedAt
_ = article.UpdatedBy

assert.NotNil(t, article, "article should not be nil")
})

Again, this test will be red unless we add the fields in our Article struct. Let’s add them!

type Article struct {
Id int64
Title string
Content string
Author string
CreatedAt int64
CreatedBy string
UpdatedAt int64
UpdatedBy string
}

Now the test should be green. We can also do a bit refactoring in the test code. We can move out the initialization of article.

func TestArticleModel(t *testing.T) {
article := model.NewArticle()

t.Run("test_constructor", func(t *testing.T) {
assert.NotNil(t, article, "article should not be nil")
})

t.Run("test_fields", func(t *testing.T) {
_ = article.Id
_ = article.Title
_ = article.Content
_ = article.Author
_ = article.CreatedAt
_ = article.CreatedBy
_ = article.UpdatedAt
_ = article.UpdatedBy

assert.NotNil(t, article, "article should not be nil")
})
}

That’s it. We have completed another Red-Green-Refactor cycle just in a snap. Put another check mark upon functionality number two!

Finally, let’s move to our last functionality: the getters and setters!

Just like before, we add the tests first (red stage):

t.Run("test_getter", func(t *testing.T) {
assert.Empty(t, article.Id(), "id should be empty")
assert.Empty(t, article.Title(), "title should be empty")
assert.Empty(t, article.Content(), "content should be empty")
assert.Empty(t, article.Author(), "author should be empty")
assert.Empty(t, article.CreatedAt(), "createdAt should be empty")
assert.Empty(t, article.CreatedBy(), "createdBy should be empty")
assert.Empty(t, article.UpdatedAt(), "updatedAt should be empty")
assert.Empty(t, article.UpdatedBy(), "updatedBy should be empty")
})

t.Run("test_setter", func(t *testing.T) {
article.SetTitle("a title")
article.SetContent("some contents")
article.SetAuthor("an author")
article.SetCreatedBy("a creator")
article.SetUpdatedBy("an updater")

assert.Equal(t, "a title", article.Title(), "title should be set")
assert.Equal(t, "some contents", article.Content(), "content should be set")
assert.Equal(t, "an author", article.Author(), "author should be set")
assert.Equal(t, "a creator", article.CreatedBy(), "createdBy should be set")
assert.Equal(t, "an updater", article.UpdatedBy(), "updatedBy should be set")
})

and then add the implementation code in article.go (green stage):

// ....previous implementation codefunc (a Article) Id() int64 {
return a.id
}

func (a Article) Title() string {
return a.title
}

func (a *Article) SetTitle(title string) {
a.title = title
}

func (a Article) Content() string {
return a.content
}

func (a *Article) SetContent(content string) {
a.content = content
}

func (a Article) Author() string {
return a.author
}

func (a *Article) SetAuthor(author string) {
a.author = author
}

func (a Article) CreatedAt() int64 {
return a.createdAt
}

func (a Article) CreatedBy() string {
return a.createdBy
}

func (a *Article) SetCreatedBy(createdBy string) {
a.createdBy = createdBy
}

func (a Article) UpdatedAt() int64 {
return a.updatedAt
}

func (a Article) UpdatedBy() string {
return a.updatedBy
}

func (a *Article) SetUpdatedBy(updatedBy string) {
a.updatedBy = updatedBy
}

This time we can do some refactoring to keep our code clean. I notice that because we have getters and setters, we no longer need our fields to be public. Thus the test_fields we previously created is no longer needed and can be safely removed after we set our fields in Article to be private:

type Article struct {
id int64
title string
content string
author string
createdAt int64
createdBy string
updatedAt int64
updatedBy string
}

Our final code will look like this:

article_test.go

package model_test

import (
"testing"

"github.com/pegasus37/blogpost-backend/app/model"
"github.com/stretchr/testify/assert"
)

func TestArticleModel(t *testing.T) {
article := model.NewArticle()

t.Run("test_new_article", func(t *testing.T) {
assert.NotNil(t, article, "article should not be nil")
})

t.Run("test_getter", func(t *testing.T) {
assert.Empty(t, article.Id(), "id should be empty")
assert.Empty(t, article.Title(), "title should be empty")
assert.Empty(t, article.Content(), "content should be empty")
assert.Empty(t, article.Author(), "author should be empty")
assert.Empty(t, article.CreatedAt(), "createdAt should be empty")
assert.Empty(t, article.CreatedBy(), "createdBy should be empty")
assert.Empty(t, article.UpdatedAt(), "updatedAt should be empty")
assert.Empty(t, article.UpdatedBy(), "updatedBy should be empty")
})

t.Run("test_setter", func(t *testing.T) {
article.SetTitle("a title")
article.SetContent("some contents")
article.SetAuthor("an author")
article.SetCreatedBy("a creator")
article.SetUpdatedBy("an updater")

assert.Equal(t, "a title", article.Title(), "title should be set")
assert.Equal(t, "some contents", article.Content(), "content should be set")
assert.Equal(t, "an author", article.Author(), "author should be set")
assert.Equal(t, "a creator", article.CreatedBy(), "createdBy should be set")
assert.Equal(t, "an updater", article.UpdatedBy(), "updatedBy should be set")
})
}

article.go

package model

type Article struct {
id int64
title string
content string
author string
createdAt int64
createdBy string
updatedAt int64
updatedBy string
}

func NewArticle() *Article {
return &Article{}
}

func (a Article) Id() int64 {
return a.id
}

func (a Article) Title() string {
return a.title
}

func (a *Article) SetTitle(title string) {
a.title = title
}

func (a Article) Content() string {
return a.content
}

func (a *Article) SetContent(content string) {
a.content = content
}

func (a Article) Author() string {
return a.author
}

func (a *Article) SetAuthor(author string) {
a.author = author
}

func (a Article) CreatedAt() int64 {
return a.createdAt
}

func (a Article) CreatedBy() string {
return a.createdBy
}

func (a *Article) SetCreatedBy(createdBy string) {
a.createdBy = createdBy
}

func (a Article) UpdatedAt() int64 {
return a.updatedAt
}

func (a Article) UpdatedBy() string {
return a.updatedBy
}

func (a *Article) SetUpdatedBy(updatedBy string) {
a.updatedBy = updatedBy
}

Conclusion

If you have any feedback, please kindly put them below. See you in my next post!

Written by

Spending most time as Software Engineer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store