在Go中分离单元测试和集成测试

时间:2014-09-22 01:30:06

标签: unit-testing go integration-testing testify

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文档,看看是否有一个命名约定或某些东西可以为我完成这个,但是没有找到任何东西。我错过了什么吗?

5 个答案:

答案 0 :(得分:110)

@ Ainar-G提出了几种分离测试的好方法。

This set of Go practices from SoundCloud建议使用构建代码(described in the "Build Constraints" section of the build package)来选择要运行的测试:

  

编写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评论是您文件中的第一行,并且在评论后包含空行,这一点非常重要,否则{{1命令将忽略该指令。

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

答案 1 :(得分:40)

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

第二种方法是使用约定并调用TestUnitFooTestIntegrationFoo测试,然后使用-run testing flag表示要运行的测试。因此,您可以使用go test -run 'Unit'进行单元测试,使用go test -run 'Integration'进行集成测试。

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

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

答案 2 :(得分:29)

详细说明我对@ Ainar-G的优秀答案的评论,在过去一年中,我一直在使用-shortIntegration命名惯例的组合来实现两全其美。

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

构建标志以前强迫我拥有多个文件(services_test.goservices_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 flag指令下运行。
  3. 基本上,规范是:“正常编写所有测试。如果是长时间运行的测试或集成测试,请遵循此命名约定并检查-short对您的同行是否好。”< / p>

    仅运行单元测试:

    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文件中并在当天调用即可。

答案 3 :(得分:2)

最近我正试图找到一种解决方案。 这些是我的标准:

  • 解决方案必须是通用的
  • 没有用于集成测试的单独软件包
  • 分离应该完成(我应该能够仅 运行集成测试)
  • 集成测试没有特殊的命名约定
  • 无需其他工具即可正常运行

上述解决方案(自定义标志,自定义构建标签,环境变量)并不能真正满足上述所有条件,因此,经过一番挖掘和探索之后,我想到了以下解决方案:

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

答案 4 :(得分:1)

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