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

sc4hvdpw  于 2023-06-19  发布在  Go
关注(0)|答案(6)|浏览(179)

在GoLang(testify)中是否有一个建立的最佳实践来分离单元测试和集成测试?我混合了单元测试(不依赖任何外部资源,因此运行速度非常快)和集成测试(依赖任何外部资源,因此运行速度较慢)。因此,我希望能够控制在使用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文档,看看是否有命名约定或其他什么东西可以为我完成这一点,但没有找到任何东西。我错过了什么吗?

3lxsmp7m

3lxsmp7m1#

@Ainar-G提出了几个很好的模式来分开测试。
SoundCloud的这组Go实践建议使用构建标签(described in the "Build Constraints" section of the build package)来选择要运行的测试:
编写一个integration_test.go,并给予它一个integration的构建标记。为服务地址和连接字符串等定义(全局)标志,并在测试中使用它们。

// +build integration

var fooAddr = flag.String(...)

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

go test和go build一样接受build标签,所以你可以调用go test -tags=integration。它还合成了一个调用flag.Parse的包main,因此任何声明的和可见的标志都将被处理并可用于您的测试。
作为一个类似的选项,您还可以通过使用构建条件// +build !unit来默认运行集成测试,然后通过运行go test -tags=unit来根据需要禁用它们。
@adamc评论:
对于其他任何尝试使用构建标记的人来说,// +build test注解是文件中的第一行,并且在注解之后包含一个空行是很重要的,否则-tags命令将忽略该指令。
此外,构建注解中使用的标记不能有破折号,尽管允许使用下划线。例如,// +build unit-tests将不工作,而// +build unit_tests将工作。

wfypjpf4

wfypjpf42#

为了详细说明我对@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
1.检查是否在-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文件中,然后就可以了。

6ie5vjzr

6ie5vjzr3#

我认为有三种可能的解决办法。第一种是使用短模式进行单元测试。因此,您可以在单元测试中使用go test -short,也可以在不使用-short标志的情况下运行集成测试。标准库使用短模式来跳过长时间运行的测试,或者通过提供更简单的数据使它们运行得更快。
第二种是使用约定,调用测试TestUnitFooTestIntegrationFoo,然后使用-run测试标志来表示要运行哪些测试。因此,您可以使用go test -run 'Unit'进行单元测试,使用go test -run 'Integration'进行集成测试。
第三种选择是使用一个环境变量,并在您的测试设置中使用os.Getenv获取它。然后,您可以使用简单的go test进行单元测试,使用FOO_TEST_INTEGRATION=true go test进行集成测试。
我个人更喜欢-short解决方案,因为它更简单,并且在标准库中使用,所以它看起来像是一种分离/简化长时间运行的测试的实际方法。但是-runos.Getenv解决方案提供了更大的灵活性(由于-run涉及regexp,因此也需要更加谨慎)。

ezykj2lf

ezykj2lf4#

我最近也在寻找解决办法。这些是我的标准:

  • 解决办法必须是普遍的
  • 集成测试没有单独的包
  • 分离应该完成(我应该能够运行集成测试 * 仅 *)
  • 集成测试没有特殊的命名约定
  • 它应该工作得很好,没有额外的工具

前面提到的解决方案(自定义标志,自定义构建标签,环境变量)并没有真正满足上述所有标准,所以经过一点挖掘和发挥,我想出了这个解决方案:

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)
}

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

用法

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

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

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

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

仅运行 * 常规 * 测试:

go test -v ./...

这个解决方案在没有工具的情况下工作得很好,但是Makefile或一些别名可以使用户更容易使用它。它还可以轻松集成到任何支持运行go测试的IDE中。
完整的例子可以在这里找到:https://github.com/sagikazarmark/modern-go-application

3vpjnl9f

3vpjnl9f5#

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

deikduxw

deikduxw6#

使用构建标记、短模式或标志有很多缺点,请参阅here
我建议使用环境变量和一个可以导入到单个包中的测试助手:

func IntegrationTest(t *testing.T) {
    t.Helper()
    if os.Getenv("INTEGRATION") == "" {
        t.Skip("skipping integration tests, set environment variable INTEGRATION")
    }
}

在你的测试中,你现在可以很容易地在测试函数的开始调用它:

func TestPostgresQuery(t *testing.T) {
    IntegrationTest(t)
    // ...
}

为什么我不推荐使用-short或flags:
第一次 checkout 你的仓库的人应该能够运行go test ./...并且所有测试都通过了,如果这依赖于外部依赖项,情况往往不是这样。
flag包的问题是,它将一直工作,直到你有跨不同包的集成测试,有些会运行flag.Parse(),有些不会,这将导致如下错误:

go test ./... -integration
flag provided but not defined: -integration
Usage of /tmp/go-build3903398677/b001/foo.test:

环境变量似乎是最灵活、最健壮的,需要的代码量最少,没有明显的缺点。

相关问题