Golang 测试中的夹具

Posted

技术标签:

【中文标题】Golang 测试中的夹具【英文标题】:Fixtures in Golang testing 【发布时间】:2016-04-25 16:33:05 【问题描述】:

来自 python 世界,fixtures 非常有用(Fixtures 定义了一个 Python 合约用于可重用的状态/支持逻辑,主要用于单元测试)。我想知道 Golang 中是否有类似的支持,它可以让我使用一些预定义的装置来运行我的测试,比如设置服务器、拆除它、每次运行测试时执行一些重复的任务?有人可以指出一些在 Golang 中做同样事情的例子吗?

【问题讨论】:

查看Ginkgo,其中有BeforeEachAfterEach 【参考方案1】:

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

来自testing package wiki:

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

func TestMain(m *testing.M)

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

TestMain 的一个简单实现是:

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

【讨论】:

注意你不需要flag.Parse()。仅当您需要访问 TestMain 中的命令行标志时才需要它。【参考方案2】:

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

您可以将代码隔离到返回“teardown”函数以自行清理的辅助函数。这是启动服务器并在测试用例结束时关闭它的一种可能方法。

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 以及充当“拆卸”的函数。该函数被添加到延迟堆栈中,因此无论测试用例如何退出,它都会被调用。

如果您在其中进行更复杂的设置并且需要处理错误,您可以传入*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)

【讨论】:

不错!我认为这与您使用的 pytest 固定装置差不多。【参考方案3】:

我为使用类似于 pytest 的夹具编写了 golang 引擎: https://github.com/rekby/fixenv

使用示例:

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, "bob", "from")
    
    // get existed db, get existed bob, create account to
    accTo := DbAccount(e, "bob", "to")
    
    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, "bob", "main")
    
    // background:
    // get existed db
    // create customer alice
    // create account main for alice
    accTo := DbAccount(e, "alice", "main")
    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

【讨论】:

以上是关于Golang 测试中的夹具的主要内容,如果未能解决你的问题,请参考以下文章

golang 性能测试pprof

golang:interface{}类型测试

我应该使用哪种方法来测试golang中的func main()?

golang之单元测试

golang在gitlab中的工作流

golang golang频道测试