Golang测试中的固定装置

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

Fixtures in Golang testing

问题

从Python世界来看,fixtures非常有用(fixtures为可重用状态/支持逻辑定义了Python的契约,主要用于单元测试)。我想知道在Golang中是否有类似的支持,可以让我使用一些预定义的fixtures来运行我的测试,比如设置服务器、拆除服务器,在每次运行测试时执行一些重复的任务?有人可以给我指点一些在Golang中实现相同功能的示例吗?

英文:

Coming from the python world, fixtures are very useful (Fixtures defines a Python contract for reusable state / support logic, primarily for unit testing). I was wondering if there's similar support in Golang which can allow me to run my tests with some predefined fixtures like setting up server, tearing it down, doing some repeated tasks each time a test is run ? Can someone point me to some examples of doing the same in Golang ?

答案1

得分: 9

如果你想使用标准的Go测试工具,你可以定义一个带有签名TestMain(m *testing.M)的函数,并将你的固定代码放在其中。

根据testing包的文档

有时候,在测试之前或之后,测试程序需要进行额外的设置或拆卸。有时候,测试需要控制在主线程上运行的代码。为了支持这些和其他情况,如果一个测试文件包含一个函数:

func TestMain(m *testing.M)

那么生成的测试将调用TestMain(m)而不是直接运行测试。TestMain在主goroutine中运行,并且可以在调用m.Run之前进行任何必要的设置和拆卸。然后,它应该使用m.Run的结果调用os.Exit。当调用TestMain时,flag.Parse尚未运行。如果TestMain依赖于命令行标志,包括testing包的标志,它应该显式地调用flag.Parse。

一个简单的TestMain实现如下:

func TestMain(m *testing.M) {
flag.Parse()
os.Exit(m.Run())
}


<details>
<summary>英文:</summary>

If you want to use the standard Go testing tools, you can define a function with the signature `TestMain(m *testing.M)` and put your fixture code in there.

From the [testing package wiki](https://golang.org/pkg/testing/):

&gt; It is sometimes necessary for a test program to do extra setup or teardown before or after testing. It is also sometimes necessary for a test to control which code runs on the main thread. To support these and other cases, if a test file contains a function:

&gt;`func TestMain(m *testing.M)`

&gt;then the generated test will call TestMain(m) instead of running the tests directly. TestMain runs in the main goroutine and can do whatever setup and teardown is necessary around a call to m.Run. It should then call os.Exit with the result of m.Run. When TestMain is called, flag.Parse has not been run. If TestMain depends on command-line flags, including those of the testing package, it should call flag.Parse explicitly.

&gt; A simple implementation of TestMain is:

&gt;     func TestMain(m *testing.M) {
	    flag.Parse()
	    os.Exit(m.Run())
    }

</details>



# 答案2
**得分**: 8

我知道这是一个旧问题,但它仍然在搜索结果中出现,所以我想给出一个可能的答案。

你可以将代码隔离到返回一个“清理”函数的辅助函数中。下面是一种可能的方法,用于启动一个服务器并在测试用例结束时关闭它。

```go
func setUpServer() (string, func()) {
    h := func(w http.ResponseWriter, r *http.Request) {
        code := http.StatusTeapot
        http.Error(w, http.StatusText(code), code)
    }

    ts := httptest.NewServer(http.HandlerFunc(h))
    return ts.URL, ts.Close
}

func TestWithServer(t *testing.T) {
    u, close := setUpServer()
    defer close()

    rsp, err := http.Get(u)
    assert.Nil(t, err)
    assert.Equal(t, http.StatusTeapot, rsp.StatusCode)
}

这个示例使用net/http/httptest启动一个服务器,并返回其URL以及充当“清理”函数的函数。这个函数被添加到defer栈中,所以无论测试用例如何退出,它都会被调用。

如果你在*testing.T中有更复杂的设置,并且需要处理错误,你可以选择传入*testing.T。下面的示例显示了设置函数返回一个*url.URL而不是格式化的URL字符串,并且解析可能会返回一个错误。

func setUpServer(t *testing.T) (*url.URL, func()) {
    h := func(w http.ResponseWriter, r *http.Request) {
        code := http.StatusTeapot
        http.Error(w, http.StatusText(code), code)
    }

    ts := httptest.NewServer(http.HandlerFunc(h))
    u, err := url.Parse(ts.URL)
    assert.Nil(t, err)
    return u, ts.Close
}

func TestWithServer(t *testing.T) {
    u, close := setUpServer(t)
    defer close()

    u.Path = "/a/b/c/d"
    rsp, err := http.Get(u.String())
    assert.Nil(t, err)
    assert.Equal(t, http.StatusTeapot, rsp.StatusCode)
}

希望对你有所帮助!

英文:

I know this is an old question, but this still came up in a search result so I thought I'd give a possible answer.

You can isolate code out to helper functions that return a "teardown" function to clean up after itself. Here's one possible way to go about starting a server and have it close at the end of the test case.

func setUpServer() (string, func()) {
	h := func(w http.ResponseWriter, r *http.Request) {
		code := http.StatusTeapot
		http.Error(w, http.StatusText(code), code)
	}

	ts := httptest.NewServer(http.HandlerFunc(h))
	return ts.URL, ts.Close
}

func TestWithServer(t *testing.T) {
	u, close := setUpServer()
	defer close()

	rsp, err := http.Get(u)
	assert.Nil(t, err)
	assert.Equal(t, http.StatusTeapot, rsp.StatusCode)
}

This starts up a server with net/http/httptest and returns its URL along with a function that acts as the "teardown." This function is added to the defer stack so it's always called regardless of how the test case exits.

Optionally, you can pass in the *testing.T if you have a more complicated set up going on in there and you need to handle errors. This example shows the set up function returning a *url.URL instead of a URL formatted string, and the parse can possibly return an error.

func setUpServer(t *testing.T) (*url.URL, func()) {
	h := func(w http.ResponseWriter, r *http.Request) {
		code := http.StatusTeapot
		http.Error(w, http.StatusText(code), code)
	}

	ts := httptest.NewServer(http.HandlerFunc(h))
	u, err := url.Parse(ts.URL)
	assert.Nil(t, err)
	return u, ts.Close
}

func TestWithServer(t *testing.T) {
	u, close := setUpServer(t)
	defer close()

	u.Path = &quot;/a/b/c/d&quot;
	rsp, err := http.Get(u.String())
	assert.Nil(t, err)
	assert.Equal(t, http.StatusTeapot, rsp.StatusCode)
}

答案3

得分: -1

我为使用类似于pytest的固定装置编写了一个用于golang的引擎:
https://github.com/rekby/fixenv

使用示例:

package example

// db 创建数据库和db结构,每个包缓存一次-调用一次,所有测试共享同一个db
func db(e Env)*DB{...}

// DbCustomer - 使用随机个人数据创建具有固定名称的客户。夹具结果由测试和子测试共享,这意味着使用相同名称多次调用Customer将返回相同的客户对象。使用其他名称调用Customer将创建新的客户并返回其他对象。
func DbCustomer(e Env, name string) Customer {
	// ... 创建客户
	db(e).CustomerStore(cust)
	// ...
	return cust
}

// DbAccount 为具有给定名称的客户创建银行账户。
func DbAccount(e Env, customerName, accountName string)Account{
	cust := DbCustomer(e, customerName)
	// ... 创建账户
	db(e).AccountStore(acc)
	// ...
	return acc
}

func TestFirstOwnAccounts(t *testing.T){
	e := NewEnv(t)
	// 背景:
	// 创建数据库
	// 创建名为bob的客户
	// 从创建账户
	accFrom := DbAccount(e, "bob", "from")
	
	// 获取现有的db,获取现有的bob,创建账户to
	accTo := DbAccount(e, "bob", "to")
	
	PutMoney(accFrom, 100)
	SendMoney(accFrom, accTo, 20)
	if accFrom != 80 {
		t.Error()
	}
	if accTo != 20 {
		t.Error()   
	}
	
	// 背景:
	// 删除账户to
	// 删除账户from
	// 删除客户bob
}

func TestSecondTransferBetweenCustomers(t *testing.T){
	e := NewEnv(t)
	
	// 背景:
	// 获取db,从上一个测试中存在的db
	// 创建名为bob的客户
	// 为bob创建主账户
	accFrom := DbAccount(e, "bob", "main")
	
	// 背景:
	// 获取现有的db
	// 创建名为alice的客户
	// 为alice创建主账户
	accTo := DbAccount(e, "alice", "main")
	PutMoney(accFrom, 100)
	SendMoney(accFrom, accTo, 20)
	if accFrom != 80 {
		t.Error()
	}
	if accTo != 20 {
		t.Error()
	}
	
	// 背景:
	// 删除alice的账户
	// 删除alice的客户
	// 删除bob的账户
	// 删除bob的客户
}

// 背景:
// 所有测试完成后删除数据库

以上是您提供的代码的翻译。

英文:

I wrote golang engine for use fixtures similar to pytest:
https://github.com/rekby/fixenv

Usage example:

package example

// db create database abd db struct, cached per package - call
// once and same db shared with all tests
func db(e Env)*DB{...}

// DbCustomer - create customer with random personal data
// but fixed name. Fixture result shared by test and subtests, 
// then mean many calls Customer with same name will return same
// customer object.
// Call Customer with other name will create new customer
// and resurn other object.
func DbCustomer(e Env, name string) Customer {
	// ... create customer
	db(e).CustomerStore(cust)
	// ...
	return cust
}

// DbAccount create bank account for customer with given name.
func DbAccount(e Env, customerName, accountName string)Account{
	cust := DbCustomer(e, customerName)
	// ... create account
	db(e).AccountStore(acc)
	// ...
	return acc
}

func TestFirstOwnAccounts(t *testing.T){
	e := NewEnv(t)
	// background:
	// create database
	// create customer bob 
	// create account from
	accFrom := DbAccount(e, &quot;bob&quot;, &quot;from&quot;)
	
	// get existed db, get existed bob, create account to
	accTo := DbAccount(e, &quot;bob&quot;, &quot;to&quot;)
	
	PutMoney(accFrom, 100)
	SendMoney(accFrom, accTo, 20)
	if accFrom != 80 {
		t.Error()
	}
	if accTo != 20 {
		t.Error()   
	}
	
	// background:
	// delete account to
	// delete account from
	// delete customer bob
}

func TestSecondTransferBetweenCustomers(t *testing.T){
	e := NewEnv(t)
	
	// background:
	// get db, existed from prev test
	// create customer bob
	// create account main for bob
	accFrom := DbAccount(e, &quot;bob&quot;, &quot;main&quot;)
	
	// background:
	// get existed db
	// create customer alice
	// create account main for alice
	accTo := DbAccount(e, &quot;alice&quot;, &quot;main&quot;)
	PutMoney(accFrom, 100)
	SendMoney(accFrom, accTo, 20)
	if accFrom != 80 {
		t.Error()
	}
	if accTo != 20 {
		t.Error()
	}
	
	// background:
	// remove account of alice
	// remove customer alice
	// remove account of bob
	// remove customer bob
}

// background:
// after all test finished drop database

huangapple
  • 本文由 发表于 2016年1月20日 08:59:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/34889721.html
匿名

发表评论

匿名网友

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

确定