Golang测试中可重复使用的组件和夹具

时间:2016-06-01 02:45:09

标签: postgresql testing go

我刚开始使用Golang并编写我的第一个测试套件。

我有Rails的背景,它对测试工具(Rspec,Cucumber等等)提供了极好的支持,所以我正在接近我的golang测试,他们有类似的心态(不确定这是对的还是错的)做)

我有一个establish_db_connection数据模型(基本上是一个结构),它从postgres中的before表读取记录并存储它们的数组。 (基本上是ActiveRecord在Rails世界中所做的非常简单的版本)

我想编写一个测试,检查例程是否正确地从DB读取并构建模型。

  1. 在几乎每个测试套件中,我都将连接到数据库,因此我有一个名为clear_db的帮助程序。我可以在哪里放置它以便集中供我的所有测试使用?

  2. 建立#1 - 是否有相当于FactoryGirl块或某些设置/拆卸方法,我可以在每次测试之前建立连接?

  3. 最后,我该如何处理灯具?就在每次测试之前,我调用go test函数来重置所有表并插入一些静态数据行。我喜欢离开灯具并使用工厂根据需要构建数据(非常类似于Rails中的{{1}}),但不确定Golang的常见程度。

  4. 内置{{1}}框架是最好的方法,还是有更好的选择?

4 个答案:

答案 0 :(得分:2)

关于类似于Rails中的FactoryGirl的测试夹具库,有一些选择。

那两个图书馆最受好评。

而且我还实现了测试夹具库,与上述库相比,它是类型安全,DRY和灵活的!

答案 1 :(得分:1)

  1. Go基于强大的包管理,这意味着命名空间被视为一个单独的文件。如果在单个测试包中使用establish_db_connection,它可以以小写字母开头,表示私有实例,并在测试文件中使用它,其包含与正在测试的代码相同的包(请注意,Go中的命名约定是establishDBConnection)。 但是,大多数情况下,如data/sql中所示,您需要获取一次数据库连接并保持这种状态,直到测试结束(更像是工厂和注入模式)。

  2. 标准testing包中没有。如果您喜欢BDD,Goconvey使用范围来定义装置,并使用reset功能进行拆卸。

  3. 您可以在测试中使用工厂和依赖注入。我认为这非常惯用。

  4. 一些包括GoconveyGinkgoTestify他们都有自己的优点和缺点。前两个通常最终会有太多的嵌套作用域,但Goconvey有一个很好的基于浏览器的实时测试服务器,可以与Go标准测试一起使用。

  5. 由于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/gtesthttps://github.com/houqp/gtest/blob/master/example_test.go