Пишем поддерживаемый код модульного тестирования в Golang
Эта статья посвящена ремонтопригодности кода модульного тестирования, не знаю, писали ли вы когда-нибудь модульный тест в spaghetti-style, который структурирован похожим образом. Честно говоря, я написал довольно много.
func TestFoo(t *testing.T) {
// test get
resp, err := GET(blabalbal)
assert.Nil(err)
...
// test post
resp, err = POST(blabalbal)
assert.Nil(err)
...
// test update
resp, err = PUT(blabalbal)
assert.Nil(err)
...
}
Большинство людей это пишут для удобства: для инициализации переменных и их повторного использования. Но когда код варианта использования слишком длинный, а единственный текст завершается неудачей, трудно найти конкретную причину, а ее поиск занимает много времени при отладке.
Решения
Платформа тестирования сообщества Go предоставила два более зрелых решения:
- GoConvey
- Go testify assert suite
И давайте рассмотрим их по отдельности.
GoConvey
package package_name
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestSpec(t *testing.T) {
// Only pass t into top-level Convey calls
Convey("Given some integer with a starting value", t, func() {
x := 1
Convey("When the integer is incremented", func() {
x++
Convey("The value should be greater by one", func() {
So(x, ShouldEqual, 2)
})
})
Convey("When the integer is incremented again", func() {
x++
Convey("The value should be greater by one", func() {
So(x, ShouldEqual, 2)
})
})
})
}
Код, как указан выше, возможен.
Одна из особенностей GoConvey является то, что он выполняется в виде дерева, а не сверху вниз. То есть это пересечение depth-first и не использует общие переменные, так что при выполнении When the integer is incremented
(Когда целое число увеличивается) и When the integer is incremented again
(Когда целое число увеличивается снова), значение x равно значению 1, присвоенному верхним уровнем.
Приведенный выше код выполняется в следующем порядке.
Given some integer...
->When the integer is incremented
->The value should be....
Given some integer...
->When the integer is incremented again
->The value should be....
testify assert suite
// Basic imports
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
// Define the suite, and absorb the built-in basic suite
// functionality from testify - including a T() method which
// returns the current testing context
type ExampleTestSuite struct {
suite.Suite
VariableThatShouldStartAtFive int
}
// Make sure that VariableThatShouldStartAtFive is set to five
// before each test
func (suite *ExampleTestSuite) SetupTest() {
suite.VariableThatShouldStartAtFive = 5
}
// All methods that begin with "Test" are run as tests within a
// suite.
func (suite *ExampleTestSuite) TestExample() {
assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
suite.Equal(5, suite.VariableThatShouldStartAtFive)
}
// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestExampleTestSuite(t *testing.T) {
suite.Run(t, new(ExampleTestSuite))
}
набор в основном с помощью следующих hook функций.
// SetupAllSuite has a SetupSuite method, which will run before the
// tests in the suite are run.
type SetupAllSuite interface {
SetupSuite()
}
// SetupTestSuite has a SetupTest method, which will run before each
// test in the suite.
type SetupTestSuite interface {
SetupTest()
}
// TearDownAllSuite has a TearDownSuite method, which will run after
// all the tests in the suite have been run.
type TearDownAllSuite interface {
TearDownSuite()
}
// TearDownTestSuite has a TearDownTest method, which will run after
// each test in the suite.
type TearDownTestSuite interface {
TearDownTest()
}
Таким образом, общие переменные, уничтожение и т. д., могут быть помещены в соответствующие функции для обработки, таким образом интегрируя ряд функций в набор тестов.
Заключение
Лично я предпочитаю использовать convey
, поскольку, если вы понимаете его древовидный шаблон выполнения, вы обнаружите, что общий тестовый код может быть написан намного меньше, а структура очень понятна. Благодаря древовидной организации можете поместить тестовые примеры одной и той же темы в одну и ту же функцию TestXXX
, а затем уточнить их в каждой функции Convey
в соответствии с условиями слой за слоем и, наконец, предать утверждения через So
для проверки.
В этой статье не говорится о конкретных технических вещах, а в основном кратко представлены две среды модульного тестирования, но самое главное - показать, что код модульного тестирования также должен быть ценным кодом, но также должна быть хорошо организована структура кода и варианты использования, модульные тесты используются для обеспечения выполнения из самого кода, обычно написанного после того, как частота изменений не слишком высока, если использовать организацию в стиле спагетти, после длительного времени очень трудно и долго отлаживать.
С помощью convey
вы можете разделить код тестового примера на разные функции, не мешая друг другу, что очень полезно для удобства обслуживания.