127

是否有在 GoLang(作证)中分离单元测试和集成测试的既定最佳实践?我混合了单元测试(不依赖任何外部资源,因此运行速度非常快)和集成测试(确实依赖任何外部资源,因此运行速度较慢)。所以,当我说go test.

最直接的技术似乎是在 main 中定义一个 -integrate 标志:

var runIntegrationTests = flag.Bool("integration", false
    , "Run the integration tests (in addition to the unit tests)")

然后在每个集成测试的顶部添加一个 if 语句:

if !*runIntegrationTests {
    this.T().Skip("To run this test, use: go test -integration")
}

这是我能做的最好的吗?我搜索了 testify 文档,看看是否有命名约定或为我完成此任务的东西,但没有找到任何东西。我错过了什么吗?

4

5 回答 5

184

@Ainar-G 建议了几个很好的模式来分离测试。

SoundCloud 的这组 Go 实践建议使用构建标签(在构建包的“构建约束”部分中描述)来选择要运行的测试:

编写一个 integration_test.go,并给它一个集成的构建标签。为服务地址和连接字符串等定义(全局)标志,并在测试中使用它们。

// +build integration

var fooAddr = flag.String(...)

func TestToo(t *testing.T) {
    f, err := foo.Connect(*fooAddr)
    // ...
}

go test 像 go build 一样接受构建标签,所以你可以调用go test -tags=integration. 它还合成了一个调用 flag.Parse 的包 main,因此任何声明和可见的标志都将被处理并可供您的测试使用。

作为一个类似的选项,您还可以使用构建条件默认运行集成测试// +build !unit,然后通过运行按需禁用它们go test -tags=unit

@adamc 评论:

对于其他尝试使用构建标签的人,重要的是// +build test注释是文件中的第一行,并且在注释之后包含一个空行,否则该-tags命令将忽略该指令。

此外,构建注释中使用的标签不能有破折号,尽管允许使用下划线。例如,// +build unit-tests将不起作用,而// +build unit_tests将。

于 2015-01-18T06:10:03.300 回答
81

为了详细说明我对@Ainar-G 出色答案的评论,在过去的一年中,我一直在使用-shortIntegration命名约定的组合来实现两全其美。

单元和集成测试和谐,在同一个文件中

以前的构建标志迫使我拥有多个文件(services_test.go,services_integration_test.go等)。

相反,请看下面这个例子,前两个是单元测试,最后我有一个集成测试:

package services

import "testing"

func TestServiceFunc(t *testing.T) {
    t.Parallel()
    ...
}

func TestInvalidServiceFunc3(t *testing.T) {
    t.Parallel()
    ...
}

func TestPostgresVersionIntegration(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping integration test")
    }
    ...
}

请注意,最后一个测试具有以下约定:

  1. Integration在测试名称中使用。
  2. 检查是否在-short标志指令下运行。

基本上,规范是这样写的:“正常编写所有测试。如果是长时间运行的测试或集成测试,请遵循此命名约定并检查是否-short对您的同行友好。”

仅运行单元测试:

go test -v -short

这为您提供了一组不错的消息,例如:

=== RUN   TestPostgresVersionIntegration
--- SKIP: TestPostgresVersionIntegration (0.00s)
        service_test.go:138: skipping integration test

仅运行集成测试:

go test -run Integration

这仅运行集成测试。用于在生产中测试金丝雀的烟雾。

显然,这种方法的缺点是如果有人运行go test,没有-short标志,它将默认运行所有测试 - 单元测试和集成测试。

实际上,如果您的项目足够大,可以进行单元和集成测试,那么您很可能正在使用一个Makefile可以在其中使用简单指令go test -short的地方。或者,只需将其放入您的README.md文件中,然后就可以结束了。

于 2016-12-31T09:22:15.733 回答
58

我看到了三种可能的解决方案。第一种是使用短模式进行单元测试。因此,您将使用go test -short与单元测试相同但没有-short标志来运行您的集成测试。标准库使用短模式来跳过长时间运行的测试,或者通过提供更简单的数据使它们运行得更快。

第二种是使用约定并调用您的测试或者TestUnitFoo然后TestIntegrationFoo使用-run测试标志来表示要运行的测试。因此,您将go test -run 'Unit'用于单元测试和go test -run 'Integration'集成测试。

第三个选项是使用环境变量,并在您的测试设置中使用os.Getenv. 然后,您将使用 simplego test进行单元测试和FOO_TEST_INTEGRATION=true go test集成测试。

我个人更喜欢这个-short解决方案,因为它更简单并且在标准库中使用,所以它似乎是分离/简化长时间运行测试的事实上的方式。但是-runandos.Getenv解决方案提供了更大的灵活性(也需要更加谨慎,因为 regexp 涉及到-run)。

于 2014-09-22T09:27:56.063 回答
11

我最近试图找到同样的解决方案。这些是我的标准:

  • 解决方案必须是通用的
  • 没有用于集成测试的单独包
  • 分离应该是完整的(我应该只能运行集成测试
  • 集成测试没有特殊的命名约定
  • 它应该可以在没有额外工具的情况下正常工作

上述解决方案(自定义标志、自定义构建标签、环境变量)并没有真正满足上述所有条件,所以经过一番挖掘和玩,我想出了这个解决方案:

package main

import (
    "flag"
    "regexp"
    "testing"
)

func TestIntegration(t *testing.T) {
    if m := flag.Lookup("test.run").Value.String(); m == "" || !regexp.MustCompile(m).MatchString(t.Name()) {
        t.Skip("skipping as execution was not requested explicitly using go test -run")
    }

    t.Parallel()

    t.Run("HelloWorld", testHelloWorld)
    t.Run("SayHello", testSayHello)
}

实现简单且最小化。虽然它需要一个简单的测试约定,但它不太容易出错。进一步的改进可能是将代码导出到辅助函数。

用法

仅对项目中的所有包运行集成测试:

go test -v ./... -run ^TestIntegration$

运行所有测试(常规和集成):

go test -v ./... -run .\*

只运行常规测试:

go test -v ./...

此解决方案无需工具即可正常工作,但 Makefile 或一些别名可以使其更易于用户使用。它还可以轻松集成到任何支持运行 go 测试的 IDE 中。

完整的例子可以在这里找到:https ://github.com/sagikazarmark/modern-go-application

于 2018-11-23T16:37:31.307 回答
2

我鼓励您查看 Peter Bourgons 的方法,它很简单,并且避免了其他答案中的建议的一些问题:https ://peter.bourgon.org/blog/2021/04/02/dont-use-build-tags -for-integration-tests.html

于 2021-04-23T23:38:15.263 回答