dousha4804
2016-06-01 02:45 阅读 13

Golang测试中可重复使用的组件和固定装置

I'm just getting started with Golang and writing my first test suite.

I have a background in Rails, which has fantastic support for testing tools (Rspec, Cucumber, etc..), so I'm approaching my golang tests with a similar mindset (not sure if that's the right or wrong thing to do)

I have a User data model (basically a struct) that reads records from a users table in postgres and stores an array of them. (Essentially a really simple version of what ActiveRecord does in the Rails world)

I'd like to write a test that checks if the routine correctly reads from the DB and builds the models.

  1. In almost every test suite I'll be connecting to the DB so I have a helper named establish_db_connection. Where can I place this so that it's centrally available to all my tests?

  2. Building off #1 - is there an equivalent of a before block or some setup/teardown method where I can establish a connection before every test?

  3. Lastly, how do I handle fixtures? Right now before each test I call a clear_db function that resets all tables and inserts some static data rows. I'd love to move away from fixtures and use factories to build data as needed (very similar to FactoryGirl in Rails), but not sure how common that is in Golang.

  4. Is the built-in go test framework the best approach, or are there better alternatives?

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享

2条回答 默认 最新

  • 已采纳
    doumeng2637 doumeng2637 2016-06-01 04:04
    1. Go is based on strong package management, meaning a namespace is treated as one single file. If establish_db_connection is used within a single test package, it can begin with a lowercase letter to signify a private instance and use it in the test file with the same package as the code being tested (Note that naming convention in Go is establishDBConnection). However, most of the time, as in data/sql, you will want to obtain a DB connection once and keep that around until the test is finished (more like a factory and injection pattern).

    2. There is none in the standard testing package. If you like BDD, Goconvey use scopes to define fixtures and a reset function for teardown.

    3. You can use factory and dependency injections in your testing. I think that's pretty idiomatic.

    4. A few includes Goconvey, Ginkgo and Testify They all have pros and cons of their own. The first two often end up with too many nested scopes, but Goconvey has a great browser-based real-time testing server which can be used with Go standard testing.

    Since there's no global variables/functions in Go, you might design your project in interface-delegate pattern to help with importing functions cross packages and avoiding cyclic imports when dealing with cross-package testing.

    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)
    
    点赞 评论 复制链接分享
  • dtn51137 dtn51137 2016-06-03 13:29

    on fixtures: consider passing functions in your testcases:

    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()
        }
    }
    
    点赞 评论 复制链接分享

相关推荐