t.Cleanup有什么用处?

20

问题

我想知道Go1.14中引入的 t.Cleanup 的用例。相比使用defer,t.Cleanup有哪些便利之处?

https://golang.org/pkg/testing/#T.Cleanup.

  • 示例

例如,假设我们创建了一个临时目录,在测试结束后,我们希望删除我们创建的临时目录。

可以使用t.Cleanup编写如下测试,但它也可以像defer os.RemoveAll(tempDir)一样工作。

package mypkg

import (
    "io/ioutil"
    "os"
    "testing"
)

func TestDirwalk(t *testing.T) {
    tempDir, err := ioutil.TempDir(".", "temp")
    if err != nil {
        t.Errorf("create tempDir: %v", err)
    }
    t.Cleanup(func() { os.RemoveAll(tempDir) })

    // something...
}
3个回答

29

如果你的测试出现异常,清理函数也会被调用,所以在你的情况下,两者都可以工作。

使用T.Cleanup()的优势在于,如果你的测试调用其他函数并传递testing.T,那么显然使用defer在这些函数中执行将在这些函数返回之前进行。但是,如果你使用T.Cleanup()注册清理函数,则它们只会在测试结束时被调用。

T.Cleanup()视为“改进”的和扩展版本的defer。它还记录了传递的函数是用于清理目的。


3
显然,在这些函数中使用defer将在这些函数返回之前执行,但是如果您使用T.Cleanup()注册清理函数,则它们仅在测试结束时调用。 - Tsuji Daishiro
1
在测试中,如果您的测试出现 panic,defer 也会被调用。t.Cleanup() 很有用,因为您可以将其传递给辅助函数,并让它们指定自己的清理操作。 - Francesco Casula

15

t.Cleanup函数在测试不关心资源本身时,用于清理由辅助函数分配的资源。

示例

考虑测试服务层。该服务使用*sql.DB,但不是自己创建的。

package testutils

import (
  "testing"

  "my/db"
  "my/domain"
)

func NewTestSubject(t *testing.T) *domain.Service {
  t.Helper()  
  sqldb := newDatabase(t)
  s, _ := domain.NewService(sqldb)
  return s
}

func newDatabase(t *testing.T) *sql.DB {
  t.Helper()
  d, _ := db.Create()
  t.Cleanup(func() {
    d.Close()
  })
}

没有 t.CleanupnewTestSubject 将不得不返回 (*domain.Service, *sql.DB),泄漏关于 domain.Service 构造的细节。

2
t.Cleanup在处理Helper函数内的资源时确实非常有用。谢谢。 - Tsuji Daishiro
2
抱歉重新提出这个老问题。只是想知道,您是否认为调用t.Cleanup(d.Close)更符合惯用语,还是像您在这里所做的那样使用额外的函数? - Nic
在这种情况下,我更喜欢使用 t.Cleanup(d.Close)。但是,我认为现有的示例对于新手更容易理解。 - J. Random Coder

7
除了其他人指出的内容之外,t.Cleanup() 在处理并行子测试时也非常有用,其中清理应该在所有子测试完成后才运行。考虑以下示例:
func TestSomething(t *testing.T){
   setup()
   defer cleanup()
   t.Run("parallel subtest 1", func(t *testing.T){
      t.Parallel()
      (...)
   })
   t.Run("parallel subtest 2", func(t *testing.T){
      t.Parallel()
      (...)
   })
}

该方法无法正常工作,因为测试函数在子测试仍在运行时就已经返回,这会导致子测试所需的资源被defer cleanup()清除。

在使用t.Cleanup()之前,解决此问题的方法是将子测试包装在另一个测试中。

func TestSomething(t *testing.T){
   setup()
   defer cleanup()
   t.Run("parallel tests", func(t *testing.T){
      t.Run("subtest 1", func(t *testing.T){
         t.Parallel()
         (...)
      })
      t.Run("subtest 2", func(t *testing.T){
         t.Parallel()
         (...)
      })
   })
}

这看起来还好,但是加上 t.Cleanup() 后就变得更好了。

func TestSomething(t *testing.T){
   setup()
   t.Cleanup(cleanup)
   t.Run("parallel subtest 1", func(t *testing.T){
      t.Parallel()
      (...)
   })
   t.Run("parallel subtest 2", func(t *testing.T){
      t.Parallel()
      (...)
   })
}

这绝对是最重要的答案需要阅读。那是一个非常难以发现的错误,也是一个重要的错误需要避免。提醒其他人,您可以使用 goerr113 作为自动化 linter,以查找使用 defer 而不是 t.Cleanup 的测试。 - michaelsnowden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接