测试中的噩梦将是误报。 “一切都会过去!惊人的!”直到在未来的某个未知时间,所有地雷一起爆炸,将你的团队炸入地狱。
测试可能默默失败的原因有很多。
今天我要讲一个很基本的原因:不知道哪些是测试。
大多数人都是半途而废地加入 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中文网其他相关文章!