When testing, we often need some kind of test data. In Go, test data can often come in the form of structs. In this post, we explore various methods of creating reusable test data, and I will also introduce a library I am currently working on to assist you in the process of creating test data.
Creating Test Data
Let’s assume we have a structure as follows:
type Book struct{
ID int
Title string
}
type Author struct {
ID int
Name string
Books []Book
}
func GetAuthor() Author {
// return an author
}
In most common IDEs, you can generate unit tests for a function. In most cases, a table-driven test is then generated, similar to the following:
func TestGetAuthor(t *testing.T) {
tests := []struct {
name string
want Author
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetAuthor(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetAuthor() = %v, want %v", got, tt.want)
}
})
}
}
Now, you could just initialize the Author
in each test case, making it a simple and straightforward approach. But this is somewhat time-consuming and repetitive. And as soon as Author
changes, you have to fix every test.
Instead of initializing the struct directly within the test, we can create a builder function with functional parameters.
type Opts func(*Author)
func BuildAuthor(options ...opts) Author {
// we can set some default values here
author := &Author{ID: 1}
for _,option := range options {
option(author)
}
return *Author
}
// usage
someAuthor := BuildAuthor(func(a *Author){ a.Name="Name"})
With this option, we can create reusable test data and can easily call BuildAuthor
within the test cases.
Because I found myself creating similar code to the above, I’ve created a library to assist in creating the test data.
Introduction to the Library
The motivation behind testcraft was to remove some of the boilerplate needed during the test data creation and add a few little helper functions. A simple example as the above could look like this:
authorFactory := testcraft.NewFactory(Author{}).Attr(func(a *Author) error {
a.ID = 1
a.Name="Name"
return nil
})
someAuthor,err := authorFactory.Build()
// or use MustBuild, MustBuild panics on error
someAuthor := authorFactory.MustBuild()
This is basically the same code as the previously shown BuildAuthor
function.
But there is more you can do with testcraft. Let’s look at some of the helpers. Having always the same ID is somewhat unrealistic. To get a sequence of IDs, you can create a Sequence
:
// NewSequencer can take any of the following types
// int, int32, int64, int8, float32, float64
authorSeq := NewSequencer(1)
// in the previously declared factory, we can now use
a.ID = authorSeq.Next()
Anytime Build
is called, the ID will now increase by 1. The increment can be changed with userSeq.SetIncrement(2)
.
If you just need some data and don’t care about the values, there is a Randomize
function, which initializes the struct with random values:
authorSeq := NewSequencer(1)
authorFactory := testcraft.NewFactory(Author{}).Attr(func(a *Author) error {
a.ID = authorSeq.Next()
a.Name="Name"
return nil
})
// Randomize ignores the previously set values
randomAuthor, err := authorFactory.Randomize()
// RandomizeWithAttrs will apply the previously set attributes and randomize the rest.
randomAuthor2, err := authorFactory.RandomizeWithAttrs()
Next, let's have a look at how to combine factories and use the Multiple
function:
// As a reminder, here are the structs again.
type Book struct{
ID int
Title string
}
type Author struct {
ID int
Name string
Books []Book
}
// create sequence for books
bookSeq := NewSequencer(1)
// define book factory
bookFactory := testcraft.NewFactory(Book{}).Attr(func(b *Book) error{
b.ID = bookSeq.Next()
})
// create sequence for author
authorSeq := NewSequencer(1)
// define author factory
authorFactory := testcraft.NewFactory(Author{}).Attr(func(a *Author) error {
a.ID = authorSeq.Next()
// // AlphanumericBetween returns a random string of alphanumeric characters between min(3) and max(10).
a.Name = datagen.AlphanumericBetween(3,10)
// Multiple creates a slice of Book with 5 elements.
a.Books = testcraft.Multiple(5, func(i int) Book {
return bookFactory.MustRandomizeWithAttrs()
// functions prefixed with Must panic on error
})
return nil
})
In the above code, you also get a first glimpse of the datagen
package as we generate an alphanumeric string between 3 and 10 characters.
I think the datagen
package could still use some work, but for some basic work it’s good to go.
I am still actively working on testcraft and add a few more features and improve the data generation package.
Top comments (0)