單元測(cè)試
單元測(cè)試在大型應(yīng)用開(kāi)發(fā)中是非常重要的一環(huán)。go 自身提供了單元測(cè)試框架,但是原生單元測(cè)試框架提供的功能太弱了,所以這里分享下最近研究搭建的單元測(cè)試環(huán)境
目標(biāo)
支持測(cè)試與數(shù)據(jù)庫(kù)交互,每個(gè)單元測(cè)試用例的數(shù)據(jù)庫(kù)環(huán)境必須都是要干凈的
支持?jǐn)嘌?/p>
簡(jiǎn)單的在測(cè)試中生成數(shù)據(jù)
為了達(dá)成這些目標(biāo),我們可以使用一些現(xiàn)成的go第三方包來(lái)幫我們
testify
https://github.com/stretchr/testify
testify用go實(shí)現(xiàn)的一個(gè)assert風(fēng)格的測(cè)試框架,這個(gè)包提供了我們需要的斷言的功能,提供了非常豐富的斷言方法。
assert.Equal(t, 123, 123, "they should be equal")
assert.NotNil(ret, "")
同時(shí)testify也提供了如mock這種的功能,如果有童鞋需要,可以自己去翻閱文檔https://godoc.org/github.com/stretchr/testify/mock
重點(diǎn)來(lái)了
testify提供了suite包提供了類(lèi)似rails minitest中可以給每個(gè)測(cè)試用例進(jìn)行前置操作和后置操作的功能,這個(gè)方便的功能,在前置操作和后置操作中去初始化和清空數(shù)據(jù)庫(kù),就可以幫助我們實(shí)現(xiàn)第一個(gè)目標(biāo)。
同時(shí),還可以聲明在這個(gè)測(cè)試用例周期內(nèi)都有效的全局變量
type ExampleTestSuite struct {
suite.Suite
VariableThatShouldStartAtFive int
}
// 每個(gè)測(cè)試用例執(zhí)行前都會(huì)調(diào)用
func (suite *ExampleTestSuite) SetupTest() {
test_helpers.Init(config.Cfg)
}
//其中一個(gè)測(cè)試用例
func (suite *ExampleTestSuite) TestExample() {
assert.Equal(suite.T(), 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))
}
// 每個(gè)測(cè)試用例執(zhí)行后都會(huì)調(diào)用
func (suite *ExecutorTestSuite) TearDownTest() {
test_helpers.CleanTables()
}
dbcleaner
https://github.com/khaiql/dbcleaner
有了前置操作和后置操作,我們就可以想辦法來(lái)保證每個(gè)用例的數(shù)據(jù)庫(kù)狀態(tài)都是干凈的。最簡(jiǎn)單的辦法,就是在SetupTest方法中聲明數(shù)據(jù)庫(kù),然后用例操作完數(shù)據(jù)庫(kù)玩后,在TearDownTest()方法中truncate數(shù)據(jù)庫(kù)。
但是,go test的時(shí)候,是多個(gè)協(xié)程一起跑的,如果簡(jiǎn)單這樣做,有可能導(dǎo)致測(cè)試時(shí)數(shù)據(jù)庫(kù)出錯(cuò), dbcleaner可以幫助我們避免這個(gè)問(wèn)題,這個(gè)包是模仿ruby中的database_cleaner的功能。使用方法如下
在測(cè)試中有可能用到的表,先聲明加鎖
Cleaner.Acquire("users")
然后在用例結(jié)束后
Cleaner.Clean("users")
配合suite,就可以保證數(shù)據(jù)狀態(tài)的干凈。
同時(shí)建議,為了減輕些測(cè)試的心智,最好全局定義有可能用到的所有的表,然后在每個(gè)測(cè)試用例,同意調(diào)用一個(gè)封裝好的函數(shù)
factory
https://github.com/nauyey/factory
有了上面兩個(gè)庫(kù),已經(jīng)完成我們第一和第二個(gè)目標(biāo)了,第三個(gè)目標(biāo)同樣非常重要,測(cè)試用例中,生成數(shù)據(jù)是非常重要的,如果,每次都調(diào)用model層的方法來(lái)生成數(shù)據(jù),非常繁瑣,因?yàn)樯傻臏y(cè)試數(shù)據(jù),有很多字段,并不是我們關(guān)注的,但是用model去生成,有很多時(shí)候,必須遵循驗(yàn)證規(guī)則,不得不去聲明一些字段,所以factory就非常重要了,同時(shí)factory也是各種測(cè)試體系中不可或缺的一部分.
factory這個(gè)庫(kù),參考的是ruby中factory_bot這個(gè)庫(kù),使用ruby寫(xiě)測(cè)試過(guò)的同學(xué),絕對(duì)都使用過(guò)這個(gè)庫(kù),go中的factory庫(kù),實(shí)現(xiàn)了大部分功能
userFactory := def.NewFactory(User{}, "db_table_users",
def.SequenceField("ID", 1, func(n int64) interface{} {
return n
}),
def.DynamicField("Name", func(user interface{}) (interface{}, error) {
return fmt.Sprintf("User Name %d", user.(*User).ID), nil
}),
def.Trait("boy",
def.Field("Gender", "male"),
),
)
定義好了factory后,使用就非常簡(jiǎn)單了
user := &User{}
Create(userFactory, WithTraits("boy")).To(user2) // saved to database
這樣,在測(cè)試用例中,非常的方便,能減輕很多工作量。
把factory定義在全局的某個(gè)地方,就可以配置一次,然后在多個(gè)測(cè)試用例中使用
go-randomdata
https://github.com/Pallinder/go-randomdata
這個(gè)庫(kù)可以生成一些隨機(jī)的假數(shù)據(jù),就是測(cè)試?yán)锍Uf(shuō)faker
總結(jié)
有了這些庫(kù),在加上適當(dāng)?shù)姆庋b,就趕快開(kāi)始愉快的寫(xiě)測(cè)試吧!??