我刚开始使用Golang并编写我的第一个测试套件。
我有Rails的背景,它对测试工具(Rspec,Cucumber等等)提供了极好的支持,所以我正在接近我的golang测试,他们有类似的心态(不确定这是对的还是错的)做)
我有一个establish_db_connection
数据模型(基本上是一个结构),它从postgres中的before
表读取记录并存储它们的数组。 (基本上是ActiveRecord在Rails世界中所做的非常简单的版本)
我想编写一个测试,检查例程是否正确地从DB读取并构建模型。
在几乎每个测试套件中,我都将连接到数据库,因此我有一个名为clear_db
的帮助程序。我可以在哪里放置它以便集中供我的所有测试使用?
建立#1 - 是否有相当于FactoryGirl
块或某些设置/拆卸方法,我可以在每次测试之前建立连接?
最后,我该如何处理灯具?就在每次测试之前,我调用go test
函数来重置所有表并插入一些静态数据行。我喜欢离开灯具并使用工厂根据需要构建数据(非常类似于Rails中的{{1}}),但不确定Golang的常见程度。
内置{{1}}框架是最好的方法,还是有更好的选择?
答案 0 :(得分:2)
关于类似于Rails中的FactoryGirl的测试夹具库,有一些选择。
那两个图书馆最受好评。
而且我还实现了测试夹具库,与上述库相比,它是类型安全,DRY和灵活的!
答案 1 :(得分:1)
Go基于强大的包管理,这意味着命名空间被视为一个单独的文件。如果在单个测试包中使用establish_db_connection
,它可以以小写字母开头,表示私有实例,并在测试文件中使用它,其包含与正在测试的代码相同的包(请注意,Go中的命名约定是establishDBConnection
)。
但是,大多数情况下,如data/sql中所示,您需要获取一次数据库连接并保持这种状态,直到测试结束(更像是工厂和注入模式)。
标准testing
包中没有。如果您喜欢BDD,Goconvey使用范围来定义装置,并使用reset
功能进行拆卸。
您可以在测试中使用工厂和依赖注入。我认为这非常惯用。
一些包括Goconvey,Ginkgo和Testify他们都有自己的优点和缺点。前两个通常最终会有太多的嵌套作用域,但Goconvey有一个很好的基于浏览器的实时测试服务器,可以与Go标准测试一起使用。
由于Go中没有全局变量/函数,您可以在interface-delegate pattern中设计项目,以帮助在处理跨包测试时跨函数导入函数并避免循环导入。
mypackage
type DBOptions struct {
Name, Credentials string
}
func aFunc(db *sql.DB) error {
// do something
return nil
}
func bFunc(db *sql.DB) int, error {
// do something
return 0, nil
}
func establishConn(opts *DBOptions) (*sql.DB, error) {
db, err := sql.Open(opts.Name, opts.Credentials)
if err != nil {
return nil, err
}
return db, nil
}
func destroyConn(conn *sql.DB) {
conn.Close()
}
// test file
mypackage
import "testing"
var myOpt = &DBOptions{
Name: "mysql",
Credentials: "user:password@tcp(127.0.0.1:3306)/hello",
}
var conn, _ = establishConn(myOpt)
func TestAFunc(t *testing.T) {
err := aFunc(conn)
if err != nil {
t.Error(err)
}
}
func TestBFunc(t *testing.T) {
err := aFunc(conn)
if err != nil {
t.Error(err)
}
}
// use `conn` in other tests ...
destroyConn(conn)
答案 2 :(得分:1)
关于灯具:考虑在测试用例中传递函数:
package main
import "testing"
type testcase struct {
scenario string
before func(string)
after func()
input string
expOutput string
}
var state = ""
func setup(s string) {
state = s
}
func nilSetup(s string) {}
func reset() {
state = ""
}
func execute(s string) string {
return state
}
func TestSetupTeardown(t *testing.T) {
tcs := []testcase{
{
scenario: "blank output when initial state is wrong",
before: nilSetup,
after: reset,
input: "foo",
expOutput: "",
},
{
scenario: "correct output when initial state is right",
before: setup,
after: reset,
input: "foo",
expOutput: "foo",
},
}
for _, tc := range tcs {
tc.before(tc.input)
if out := execute(tc.input); out != tc.expOutput {
t.Fatal(tc.scenario)
}
tc.after()
}
}
答案 3 :(得分:0)
我建立了一个很小的实用程序库,以轻松创建可重复使用的固定装置以进行测试。查看https://github.com/houqp/gtest,看看它是否可以解决您的问题。
下面是一个简单的示例,说明如何为测试组中的每个测试创建数据库事务处理夹具:
type TransactionFixture struct{}
// Construct can take other fixtures as input parameter as well
func (s TransactionFixture) Construct(t *testing.T, fixtures struct{}) (*sqlx.Tx, *sqlx.Tx) {
tx := // create db transaction here
return tx, tx
}
func (s TransactionFixture) Destruct(t *testing.T, tx *sqlx.Tx) {
tx.Rollback()
}
func init() {
// register and make fixture available to all tests
gtest.MustRegisterFixture(
"Transaction", &TransactionFixture{}, gtest.ScopeSubTest)
}
// begin of test definition
type SampleTests struct{}
func (s *SampleTests) Setup(t *testing.T) {
// you can create/initialize DB in this method
// DB instance can also be implemented as a fixture and get injected into Transanction fixture.
}
func (s *SampleTests) Teardown(t *testing.T) {
// you can clean up all DB resources in this method
}
func (s *SampleTests) BeforeEach(t *testing.T) {}
func (s *SampleTests) AfterEach(t *testing.T) {}
func (s *SampleTests) SubTestFoo(t *testing.T, fixtures struct {
Tx sqlx.Tx `fixture:"Transaction"`
}) {
// transaction is available as fixtures.Tx in this test
}
func TestSampleTests(t *testing.T) {
gtest.RunSubTests(t, &SampleTests{})
}
有关更多高级示例和最新示例,请参见https://godoc.org/github.com/houqp/gtest和https://github.com/houqp/gtest/blob/master/example_test.go。