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

Posted

技术标签:

【中文标题】在 Go 中分离单元测试和集成测试【英文标题】:Separating unit tests and integration tests in Go 【发布时间】:2014-11-15 22:01:26 【问题描述】:

在 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 文档,看看是否有命名约定或为我完成此任务的东西,但没有找到任何东西。我错过了什么吗?

【问题讨论】:

我认为 stdlib 使用 -short 来禁用影响网络的测试(以及其他长期运行的东西)。否则,您的解决方案看起来不错。 -short 是一个不错的选择,您的自定义构建标志也是如此,但您的标志不必在 main 中。如果你在函数之外将 var 定义为 var integration = flag.Bool("integration", true, "Enable integration testing."),则该变量将显示在包范围内,并且标志将正常工作 【参考方案1】:

我看到了三种可能的解决方案。第一种是使用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 涉及正则表达式)。

【讨论】:

请注意,IDE(Atom、Sublime 等)通用的社区测试运行器(例如Tester-Go)具有使用-short 标志以及-coverage 和其他标志运行的内置选项.因此,我在测试名称中使用了两个 Integration 的组合,以及这些测试中的 if testing.Short() 检查。它让我可以两全其美:在 IDE 中使用 -short 运行,并明确地仅使用 go test -run "Integration" 运行集成测试【参考方案2】:

@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 cmets:

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

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

【讨论】:

我已经使用了一段时间了,这是迄今为止最合乎逻辑和最简单的方法。 如果你在同一个包中有单元测试,你需要在单元测试中设置// + build unit并使用-tag unit来运行测试 @Tyler.z.yang 您能否提供有关弃用标签的链接或更多详细信息?我没有找到这样的信息。我将 go1.8 的标签用于答案中描述的方式,也用于模拟测试中的类型和函数。我认为它是接口的不错替代品。 对于其他尝试使用构建标签的人,重要的是 // +build 测试注释是文件中的第一行,并且在注释后包含一个空行,否则 -tags命令将忽略该指令。此外,构建注释中使用的标签不能有破折号,尽管允许使用下划线。例如,// +build unit-tests 将不起作用,而 // +build unit_tests 如何处理通配符? go test -tags=integration ./... 不起作用,它会忽略标签【参考方案3】:

为了详细说明我对@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")
    
    ...

注意最后一个测试的约定是:

    在测试名称中使用Integration。 检查是否在-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 文件中即可。

【讨论】:

喜欢简单 您是否为此类测试创建单独的包以仅访问包的公共部分?还是全部混合? 好一个。对于仅运行集成测试,我必须使用 go regex go test -v -run ".Integration" ./... 这里 go regex 和 good example 决定研究这种使用-short标签+ Makefile的方法。我之前尝试过使用 VSCode 构建标签方法,并且遇到了来自 gopls 的 linting 错误。结帐github.com/golang/go/issues/29202。这似乎是一个已知问题,它占用了我太多时间来尝试调整 gopls 设置以识别构建标志。此外,这些设置对于所有 go 项目都是全局的。设法将 linting 错误降低到 1 个问题,go.mod 无法识别带有构建标志名称的包,然后放弃了。因此,使用这种方法可以避免项目上其他开发人员的挫败感。 @anon_dcs3spp 是的,我努力在严格和简单的 Makefile 上实现零掉毛问题。这就是我使用这种方法的原因。 :-)【参考方案4】:

我最近也在尝试寻找解决方案。 这些是我的标准:

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

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

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

【讨论】:

【参考方案5】:

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

【讨论】:

以上是关于在 Go 中分离单元测试和集成测试的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring Boot 应用程序中分离集成测试

如何在 Visual Studio 2008 中分析 MsTest 的单元测试?

为Go语言Web项目做集成测试

测试涨技术 | 如何为Go语言Web项目做集成测试?

Go 质量保证:集成测试 - 用 Docker 执行测试

如何在 pytest 中保持单元测试和集成测试分开