I have a standard table-test unit-test in Go. How can I make it simpler, easy to read and succinct?
I'm looking suggestions for: Structural refactoring. Less and simple code to achieve the same thing.
test.go
func Test_Parses_Books(t *testing.T) {
books := xmlDoc.Book
type tester struct {
texts []Text
bookID int
categoryLen int
}
getBookTest := func(i int) tester {
book := books[i]
return tester{
texts: book.BookNames,
bookID: book.BookID,
categoryLen: len(book.Categories),
}
}
booklen := len(books)
if booklen != 4 {
t.Errorf("expected: 4. got: %d", booklen)
}
var testTable = []struct {
expected tester
actual tester
}{{
expected: tester{
texts: []Text{
{"BOOK", "TV"},
{"en", "TV"}},
bookID: 1,
categoryLen: 2},
actual: getBookTest(0),
}, {
expected: tester{
texts: []Text{
{"BOOK", "PS4"},
{"en", "PS4"}},
bookID: 5,
categoryLen: 2},
actual: getBookTest(1),
}, {
expected: tester{
texts: []Text{
{"BOOK", "XBOX"},
{"en", "XBOX"}},
bookID: 22,
categoryLen: 1},
actual: getBookTest(2),
}, {
expected: tester{
texts: []Text{
{"BOOK", "PS3"},
{"en", "PS3"}},
bookID: 17,
categoryLen: 1},
actual: getBookTest(3),
}}
for i, test := range testTable {
if !reflect.DeepEqual(test.expected, test.actual) {
t.Errorf("\nexpected: [%d]\n\t%#v.\ngot:\n\t%#v",
i, test.expected, test.actual)
}
}
}
2 Answers 2
The actual values should not be part of the table since it is much easier to refer to them as books[i]
in the loop.
The type tester
has a bad name since that name sounds active, as if the type could actually test something. Choose a more passive name, such as BookInfo
.
The field tester.categoryLen
should be named categoriesLen
since asking for the length of a single category doesn't make sense.
The function getBookTest
should be named extractInfo
or something similar.
After renaming and reorganizing the code, it looks like this. But since you did not provide any real test data, I can only hope that it runs fine, I could not actually test it.
import (
"reflect"
"testing"
)
type Document struct {
Book []Book
}
type Book struct {
BookNames []string
BookID int
Categories []Category
}
type Text string
type Category struct{}
var xmlDoc Document
func Test_Parse_Books(t *testing.T) {
books := xmlDoc.Book
type BookInfo struct {
Texts []Text
ID int
CategoriesLen int
}
extractInfo := func(book Book) BookInfo {
return BookInfo{
Texts: book.BookNames,
ID: book.BookID,
CategoriesLen: len(book.Categories),
}
}
newBookInfo := func(title string, id int, categoriesLen int) BookInfo {
return BookInfo{
Texts: []Text{
{"BOOK", title},
{"en", title}},
ID: id,
CategoriesLen: categoriesLen}
}
booklen := len(books)
if booklen != 4 {
t.Errorf("expected: 4. got: %d", booklen)
}
var expectedData = []BookInfo{
newBookInfo("TV", 1, 2),
newBookInfo("PS4", 5, 2),
newBookInfo("XBOX", 22, 1),
newBookInfo("PS3", 17, 1)}
for i, expected := range expectedData {
actual := extractInfo(books[i])
if !reflect.DeepEqual(expected, actual) {
t.Errorf("wrong value for book %d\nexp: %#v\ngot: %#v",
i, expected, actual)
}
}
}
I'm going to use the following refactoring for the: Ease-of-read, extensibility and brevity.
Because, I believe, defining new types and function__s__ (single, short one seems ok_) inside tests make readability and one-look understandability harder. Tests are meant for documentation as well. They should be clear instantly.
refactored_test.go
// asserts use testify for brevity
func Test_Parse_Books(t *testing.T) {
assert := assert.New(t)
texts := func(text string) []Text {
return []Text{{"BOOK", text}, {"en", text}}
}
exps := []Book{
{
Texts: texts("TV"),
Categories: make([]Category, 2),
ID: 1},
{
Texts: texts("PS4"),
Categories: make([]Category, 2),
ID: 5},
{
Texts: texts("XBOX"),
Categories: make([]Category, 1),
ID: 22},
{
Texts: texts("PS3"),
Categories: make([]Category, 1),
ID: 17}}
acts := xmlDoc.Books
assert.Equal(4, len(acts))
for i, exp := range exps {
act := acts[i]
assert.Equal(len(act.Categories), len(exp.Categories))
assert.Equal(act.ID, exp.ID)
assert.Equal(act.Texts, exp.Texts)
}
}