测试驱动开发(Test Driven Development,TDD)用于检查涉及数据库查询的方法。

huangapple go评论95阅读模式
英文:

Test driven development to check database queries involved methods

问题

我想使用Golang创建一个数据库驱动的应用程序,并尝试以TDD的方式进行。当我尝试测试执行SQL查询的方法时,有哪些可用的包?

  • 我不想连接到我用于开发的默认数据库。我可以编写代码在运行测试时使用另一个测试数据库,但是否有任何Go库已经实现了这一功能?

  • 是否有任何库可以在不连接到数据库的情况下进行数据库测试?

使用Golang进行数据库测试的标准方法是什么?

英文:

I want to create a database driven application using Golang. I am trying to do it TDD way.
When I try to test methods that make Sql queries, What all are the packages available ?

  • I don't want to connect to the default database that I use for development. I can write code to take up another test database while running a test, but is there any go library that already does it.

  • Is there any library that does db tests without connecting to database at all ?

What is the standard way to do database test with golang ?

答案1

得分: 17

我不是一个翻译程序,但我可以帮你翻译这段代码。以下是翻译的结果:

我不久前在重构自己的一些测试时遇到了类似的问题,有几种方法可以解决:

a) 提供一个导出的类型和一个返回该类型的OpenConnect函数 - 例如:

type DB struct {
    db *sql.DB
}

// 在这个例子中使用 http://jmoiron.github.io/sqlx/,但它与 database/sql 有相同的接口
func Open(opts *Options) (*DB, error) {
    db, err := sqlx.Connect(opts.Driver, fmt.Sprintf("host=%s user=%s dbname=%s sslmode=%s", opts.Host, opts.User, opts.Name, opts.SSL))
    if err != nil {
        return nil, err
    }

    return &DB{db}, nil
}

然后,在每个测试中编写设置(setup)和拆卸(teardown)函数,这些函数返回你在其上定义数据库函数的*DB实例(作为方法 - 例如 func (db *DB) GetUser(user *User) (bool, error)):

// 设置测试环境
func setup() (*DB, error) {
    err := withTestDB()
    if err != nil {
        return nil, err
    }

    // 在这种情况下,testOptions 是一个全局变量,但你可以轻松地为每个测试创建一个
    db, err := Open(testOptions)
    if err != nil {
        return nil, err
    }

    // 加载我们的测试模式
    db.MustLoad()
    return db, nil
}

// 创建我们的测试数据库
func withTestDB() error {
    db, err := open()
    if err != nil {
        return err
    }
    defer db.Close()

    _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s;", testOptions.Name))
    if err != nil {
        return err
    }

    return nil
}

请注意,这在某种程度上是"集成"测试,但我强烈建议针对一个"真实"数据库进行测试,因为模拟接口无法帮助你捕捉到查询/查询语法的问题。

b) 另一种选择,虽然在应用程序方面不太灵活,但是可以在你的测试中使用全局的db *sql.DB变量,在init()函数中进行初始化 - 由于测试没有保证的顺序,你需要使用init()函数 - 然后从那里运行你的测试,例如:

var db *sql.DB

func init() {
    var err error
    // 注意 = 而不是赋值 - 我们不想遮蔽我们的全局变量
    db, err = sqlx.Connect(...)
    if err != nil {
        ...
    }

    err := db.loadTestSchema
    // 等等
}

func TestGetUser(t *testing.T) {
   user := User{}
   exists, err := db.GetUser(user)
   ...
}

你可以在 drone.io 的 GitHub 仓库 中找到一些实际的例子,我还推荐阅读 这篇关于 Go 应用程序结构的文章(特别是关于数据库的部分)。

英文:

I had a similar question not long ago when refactoring some of my own tests, and there's a couple of ways you can do it:

a) Provide an exported type and an Open or Connect function that returns it - e.g.

<!-- language: lang-go -->

type DB struct {
	db *sql.DB
}

// Using http://jmoiron.github.io/sqlx/ for this example, but
// it has the same interface as database/sql
func Open(opts *Options) (*DB, error) {
	db, err := sqlx.Connect(opts.Driver, fmt.Sprintf(&quot;host=%s user=%s dbname=%s sslmode=%s&quot;, opts.Host, opts.User, opts.Name, opts.SSL))
	if err != nil {
		return nil, err
	}

	return &amp;DB{db}, nil
}

... and then each of your tests, write setup & teardown functions that return an instance of *DB that you define your database functions on (as methods - i.e. func (db *DB) GetUser(user *User) (bool, error)):

<!-- language: lang-go -->

// Setup the test environment.
func setup() (*DB, error) {
	err := withTestDB()
	if err != nil {
		return nil, err
	}

    // testOptions is a global in this case, but you could easily
    // create one per-test
	db, err := Open(testOptions)
	if err != nil {
		return nil, err
	}

    // Loads our test schema
	db.MustLoad()
	return db, nil
}

// Create our test database.
func withTestDB() error {
	db, err := open()
	if err != nil {
		return err
	}
	defer db.Close()

	_, err = db.Exec(fmt.Sprintf(&quot;CREATE DATABASE %s;&quot;, testOptions.Name))
	if err != nil {
		return err
	}

	return nil
}

Note that this is somewhat "integration" testing, but I strongly prefer to test against a "real" database since mocking the interface won't help you catch issues with your queries/query syntax.

b) The alternative, although less extensible on the application side, is to have a global db *sql.DB variable that you initialise in init() within your tests—since tests have no guaranteed order you'll need to use init()—and then run your tests from there. i.e.

<!-- language: lang-go -->

var db *sql.DB

func init() {
    var err error
    // Note the = and *not* the assignment - we don&#39;t want to shadow our global
    db, err = sqlx.Connect(...)
    if err != nil {
        ...
    }

    err := db.loadTestSchema
    // etc.
}

func TestGetUser(t *testing.T) {
   user := User{}
   exists, err := db.GetUser(user)
   ...
}

You can find some practical examples in drone.io's GitHub repo, and I'd also recommend this article on structuring Go applications (especially the DB stuff).

答案2

得分: 1

我使用一个全局变量来存储当前数据库的数据源(或连接字符串),并在测试函数中将其设置为不同的值。由于我只需要操作一个数据库,所以我选择了最简单的方式。

英文:

I use a global variable to store the data source (or connection string) of current database and set to different value in test function. Since there is only one database I need to operate so I choose the easiest way.

huangapple
  • 本文由 发表于 2014年7月18日 18:52:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/24823307.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定