9

编写可维护的单元测试代码

 1 year ago
source link: https://jiajunhuang.com/articles/2022_10_31-goconvey.md.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

编写可维护的单元测试代码

这篇文章主要讲讲单元测试代码的可维护性。不知道你是否写过面条式的单元测试,也就是这样的结构。坦白讲,我写过不少:

func TestFoo(t *testing.T) {
    // test get
    resp, err := GET(blabalbal)
    assert.Nil(err)
    ...

    // test post
    resp, err = POST(blabalbal)
    assert.Nil(err)
    ...

    // test update
    resp, err = PUT(blabalbal)
    assert.Nil(err)
    ...
}

绝大部分童鞋这样写的时候,都是为了方便:方便初始化变量,方便复用。但是一旦当用例代码行数过长,而单测恰好又执行失败, 需要找到具体原因时,就会比较困难,调试时,需要花很多时间定位。

Go社区的测试框架,已经提供了两套比较成熟的解决方案:

我们分别看看这两者。

GoConvey

package package_name

import (
    "testing"
    . "github.com/smartystreets/goconvey/convey"
)

func TestSpec(t *testing.T) {
    // Only pass t into top-level Convey calls
    Convey("Given some integer with a starting value", t, func() {
        x := 1

        Convey("When the integer is incremented", func() {
            x++

            Convey("The value should be greater by one", func() {
                So(x, ShouldEqual, 2)
            })
        })

        Convey("When the integer is incremented again", func() {
            x++

            Convey("The value should be greater by one", func() {
                So(x, ShouldEqual, 2)
            })
        })
    })
}

如上代码,是可以通过的。GoConvey比较特殊的一点,是它是树状执行的,而不是从上到下执行的。也就是说,它是深度优先遍历执行, 且不共享变量的,在 When the integer is incrementedWhen the integer is incremented again 执行时,x的值都是上层 赋值的1。

以上代码执行顺序为:

  • Given some integer... -> When the integer is incremented -> The value should be....
  • Given some integer... -> When the integer is incremented again -> The value should be....

testify assert suite

// Basic imports
import (
    "testing"
    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/suite"
)

// Define the suite, and absorb the built-in basic suite
// functionality from testify - including a T() method which
// returns the current testing context
type ExampleTestSuite struct {
    suite.Suite
    VariableThatShouldStartAtFive int
}

// Make sure that VariableThatShouldStartAtFive is set to five
// before each test
func (suite *ExampleTestSuite) SetupTest() {
    suite.VariableThatShouldStartAtFive = 5
}

// All methods that begin with "Test" are run as tests within a
// suite.
func (suite *ExampleTestSuite) TestExample() {
    assert.Equal(suite.T(), 5, suite.VariableThatShouldStartAtFive)
    suite.Equal(5, suite.VariableThatShouldStartAtFive)
}

// In order for 'go test' to run this suite, we need to create
// a normal test function and pass our suite to suite.Run
func TestExampleTestSuite(t *testing.T) {
    suite.Run(t, new(ExampleTestSuite))
}

suite 主要通过如下几个hook函数:

// SetupAllSuite has a SetupSuite method, which will run before the
// tests in the suite are run.
// 执行测试之前先执行这个
type SetupAllSuite interface {
    SetupSuite()
}

// SetupTestSuite has a SetupTest method, which will run before each
// test in the suite.
// 执行每个用例之前都会执行这个
type SetupTestSuite interface {
    SetupTest()
}

// TearDownAllSuite has a TearDownSuite method, which will run after
// all the tests in the suite have been run.
// 执行整个测试之后,执行这个
type TearDownAllSuite interface {
    TearDownSuite()
}

// TearDownTestSuite has a TearDownTest method, which will run after
// each test in the suite.
// 执行每个用例之后都会执行这个
type TearDownTestSuite interface {
    TearDownTest()
}

这样,就可以把共享变量以及销毁等分别放置到对应函数进行处理,从而将一系列函数整合成一套一套的测试。

我个人更喜欢用 convey,只要理解它的树状执行模式,就会发现这样整体测试代码可以少写很多,结构也很清晰。通过树状组织, 可以将同一主题的测试用例,放在同一个 TestXXX 函数中,然后逐层根据条件细化,分别放在各个 Convey 函数中,最后通过 So 传入断言,进行校验。

这篇文章没有讲具体技术的东西,主要是简单介绍了两个单元测试框架,但最重要的,是想要说明单元测试代码,同样是需要受到 重视的代码,也需要好好地组织代码结构和用例,单元测试是用来确保代码本身执行的,通常写好以后,变更频率都不会太高,如果 使用面条式组织方式,在时间久了以后,调试起来非常困难。

借助 convey 这种工具,就可以将测试用例代码细分到不同函数,且互不干扰,非常有利于维护性。


微信公众号
关注公众号,获得及时更新

TCP/IP简明教程 - 从零构建TCP/IP协议(二)连接,断开与拥塞控制 TCP/IP简明教程 - 从零构建TCP/IP协议(这次叫PCT协议) Lua Manual 阅读笔记 Golang Map 源码阅读与分析 MySQL 零碎知识 - MySQL必知必会 Golang slice 源码阅读与分析 经典好书推荐(2017) Golang log库 源码阅读与分析 毕业后一年 ansible 简明教程 自己写个搜索引擎 HTTP 路由的两种常见设计形式 Golang的short variable declaration Greenlet和Stackless Python 写一个简单的ORM



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK