七叶笔记 » golang编程 » Go语言单元测试介绍

Go语言单元测试介绍

快速入门

相信每个开发者刚开始写新项目的时候,都不会喜欢去写单元测试。我们会花更多时间写功能、修一些小bug。

然后项目上线,过了一段时间,项目需要重构一些原有的功能,加一些优化。交付给测试人员或者在没有测试人员的情况下,自测完成直接上线,结果就是:瞬 间 爆 炸

Go语言的单元测试非常简单、容易上手。Golang甚至内置了一个用于单元测试的包:testing

 // foobar.go
package foobar

func DoSomething() string {
    // do something amazing here
}  

在Go语言里创建一个单元测试文件,只要在文件名之后加上 _test 后缀即可,例如 hello_word_test.go

我们首先创建一个名为 foobar_test.go 的test文件,用于测试 foobar.go 。在用于单元测试的文件里,以Test开头的方法都会被Go扫描和执行测试。

 // foobar_test.go
func TestDoSomething(t *testing.T) {
}  

尽管Golang里的单测不依赖其他包,但是 stretchr/testify 这个包绝对值得用,这个包里有很多辅助单元测试的方法,可以增加代码可读性,保持代码整洁。

 func TestDoSomething(t *testing.T) {
    something := foobar.DoSomething()
    assert.Equal(t, "expected", something, "some message")
}  

Now all these makes sense, but how do you write unit tests when there are external calls?

Easy! Use interfaces!

现在万事俱备,只需要执行下面命令就可以完成一个简单的Golang的单元测试了。

 go test ./...  

但是…如果DoSomething方法里调用了其他方法,其他方法有请求外部Api或者有些异步操作。这时候,该怎么办?

在执行单元测试的时候,如果请求了外部api而且外部api比较花时间并且有可能不稳定,我们又有很多这样的测试方法,岂不是总时间要花费很久,而且每次单测的结果有可能不一致(结果取决于外部api返回的结果)。

使用Interface进行单元测试

 type Foobarer interface {
    Foobar(int) error
}

type HelloWorld struct {
    ...
}

func (hw *HelloWorld) Foobar() error {
    // Makes external call (eg. HTTP or DB)
}  

假设我们有一个方法 Foobar 是有请求外部api的,那么,其他方法调用 Foobar 的时候,不需要去实际执行 Foobar 方法,因为这已经不是一个“单元”测试了。

这时,我们应该使用Go语言里的interface去mock Foobar 方法(模拟Foobar方法的返回结果)。

使用Mock

我们并不用创建一个 type 实现 Foobarer 接口来mock,我们可以直接使用GitHub上的 stretchr/testify 包模拟你的每个单元测试。

或者你可以使用官方的golang/mock包,使用mockgen命令来生成要mock的文件:

 mockgen -source {source_file}.go -destination {dest_file}.go  

或者用这个包会更简单:

要测试一个调用了 Foobar 的方法,我们需要创建一个 testObj ,并模拟其返回结果。

 // foobar.go
func DoSomething(f Foobarer) {
    err := f.Foobar(123)
    if err != nil {
      panic(err)
    }
    //do other things ...
}  

假设我们现在有个方法 DoSomething 调用了 Foobarer 里的Foobar方法(请求了外部api)

 // foobar_test.go
type MockedFoobarer struct{
    mock.Mock
}

// MockedFoobarer实现了Foobar方法,即MockedFoobarer实现了Foobarer的接口
// 所以MockedFoobarer是一个Foobarer类型,可以用于模拟Foobar方法
func (m *MockedFoobarer) Foobar(n int) error {
    args := m.Called(n)
    return args.Error(0)
}

func TestDoSomething(t *testing.T) {
    testObj := new(MockedFoobarer)
    testObj.On("Foobar", 123).Return(nil)    // 或者你可以模拟它返回error   
    foobar.DoSomething(testObj)    // 模拟Foobarer的Foobar方法在传入123时返回nil
    testObj.AssertExpectations(t)
}  

在以上例子中,我们可以测试 DoSomething 方法,并且模拟 Foobar 方法在其输入参数是123时返回nil。

在项目里编写单元测试可能会很麻烦,但是绝对是值得的。因为当你的单元测试写得足够仔细覆盖足够多的时候,每当你修改了原先的function,都会牵一发动全身,单元测试可能会失败,你需要找到失败的地方并且做出修复;并且在你单元测试的过程中,你会设想各种各样的场景,来测试你的function是否足够健壮,是否能够覆盖所有可能的情况。

在CI中跑单元测试,能够让我们检查自己的代码逻辑是否正确,是否覆盖到了足够多的文件和方法,对于保证项目质量有非常大的帮助~

相关文章