首页 > 后端开发 > Golang > 揭露 Go 中隐藏的测试陷阱:避免误报

揭露 Go 中隐藏的测试陷阱:避免误报

Susan Sarandon
发布: 2024-12-26 11:08:14
原创
318 人浏览过

Unmasking Hidden Test Pitfalls in Go: Avoiding False Positives

测试中的噩梦将是误报。 “一切都会过去!惊人的!”直到在未来的某个未知时间,所有地雷一起爆炸,将你的团队炸入地狱。

测试可能默默失败的原因有很多。

今天我要讲一个很基本的原因:不知道哪些是测试。

为什么你不知道哪些是测试?

大多数人都是半途而废地加入 Go 项目的。大多数人通过在现实生活中使用语言来学习语言。

因此,当有人用testify这样的测试框架搭建项目时,你很可能会认为下面这样的方法就是测试。

func (suite *ExampleTestSuite) TestExample() {
    suite.Equal(5, suite.VariableThatShouldStartAtFive)
}
登录后复制
登录后复制

然后您添加另一种方法,如 TestAnotherCase 并发现它有效。您认为您非常清楚什么是测试。

测试在不同的框架中有不同的含义

您所说的“测试”可能与 Go 包所说的测试不同。

从内置的测试包中,测试可以是以下形式的任何函数

func TestXxx(*testing.T)
登录后复制
登录后复制

当然,由于内置的​​测试包功能有限,大多数项目都使用 testify/suite 或其他类似的第三方包作为测试框架。从 testify/suite 的角度来看,什么是测试?

添加任何以“Test”开头的方法来添加测试

看,我们对测试有两种不同的定义。

使用第三方测试工具时就会出现问题

当使用mockery等工具时,你会阅读以下内容

您不必再担心忘记 AssertExpectations 方法调用...AssertExpectations 方法已注册为在测试结束时调用

太棒了! “所以我只需要创建一个模拟,当预期的行为发生时,包就会通知我”。

那就是陷阱。

当mockery在测试结束时说时,它实际上意味着来自testing的定义,而不是来自testify/suite的定义。

因此,当您有以下代码时,您将看到 TestA 和 TestB 都通过,即使它们都应该失败,因为 TestA 中的模拟设置在 TestB 中使用。

package mockandsubtest

import (
    "fmt"
    "testing"

    "github.com/stretchr/testify/suite"
)

// Prod code
type ExternalService interface {
    Work()
}

type Server struct {
    externalService ExternalService
}

func NewServer(externalService ExternalService) *Server {
    return &Server{
        externalService: externalService,
    }
}

// Test code
type ServerSuite struct {
    suite.Suite
    ExternalService *MockExternalService
    Server
}

func TestServerSuite(t *testing.T) {
    suite.Run(t, &ServerSuite{})
}

// Run before all test cases
func (s *ServerSuite) SetupSuite() {
    s.ExternalService = NewMockExternalService(s.T())
    s.Server = Server{externalService: s.ExternalService}
}

// In this test, Work is set up to be called once but not called
func (s *ServerSuite) TestA() {
    fmt.Println("TestA is running")
    s.ExternalService.EXPECT().Work().Times(1)
}

// In this test, Work is called once unexpectedly
func (s *ServerSuite) TestB() {
    fmt.Println("TestB is running")
    s.Server.externalService.Work()
}

登录后复制

运行上述代码的结果是

TestA is running
TestB is running
PASS
登录后复制

解释

事实证明,从测试和嘲笑的角度来看,只有 TestServerSuite 才被视为测试。这就是为什么在 TestServerSuite 末尾调用 AssertExpectations 的原因,即使 TestA 和 TestB 是由 testify/suite 内部执行的。

从mockery的角度来看,s.ExternalService预计会被调用一次,并且在TestServerSuite的生命周期中实际上会被调用一次。所以期望达到了。

如何缓解?

有两种方法可以弥补 testify/suite 和测试之间的差距。

第一种方法是在每个测试方法之前创建一个新的模拟,如下所示。

func (suite *ExampleTestSuite) TestExample() {
    suite.Equal(5, suite.VariableThatShouldStartAtFive)
}
登录后复制
登录后复制

有时,由于多种原因,它在您的项目中并不实用,例如为每个测试用例设置一个服务器实例太昂贵。然后你可以尝试另一个方向,即每次测试后手动断言。

第二个是在每个测试方法的末尾添加对 AssertExpectations 的调用。例如,在 TearDownTest 中调用 AssertExpectations,它在每个测试方法之后执行。

func TestXxx(*testing.T)
登录后复制
登录后复制

以上是揭露 Go 中隐藏的测试陷阱:避免误报的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板