TIPS之 Golang unittest package使用案例

Golang unittest package使用案例

Posted by 董江 on Tuesday, April 23, 2024

Golang unittest package使用案例

Unittest框架: testing vs testify

testify testing
基本信息 一组 golang 包,其中包含许多用于测试 Go 代码的工具。Testify 是一个 Go 测试框架,具有一些出色的功能,例如更简单的断言、测试套件接口和 Mocks go test是一个内置的工具/命令,用于在 Golang 中进行自动化测试,而 test 是内置的测试库测试是随 go 一起提供的包,与 go test 命令结合以提供最小但完整的测试体验
服务端
fixture增量 是。go test原生支持可忽略 $GOPATH 中以“testdata”一词开头、以句点或下划线开头的任何目录
Licence MIT MIT
Mocks 提供“mock”包具有一种可以轻松编写用于代替真实对象的模拟对象的机制 与测试库集成良好的第三方库GoMock
Group 使用“suite”包,开发人员可以构建一个测试套件作为结构构建拆卸和设置方法以及结构上的测试方法,然后使用“go test”运行它们 使用Map测试,这是用最少的代码对函数或行为执行多个 I/O 测试的好方法
  • testinggolang 官方提供的测试包.高性能,无外部依赖。 唯一缺憾是缺少 assert 断言判断类和方法,冥冥之中加大了unittest的判断数量和代码行。
  • testify是 用是 golang 实现的一个 assert 风格的测试框架,这个包提供了我们需要的断言的功能,提供了非常丰富的断言方法,使用起来非常简单且易于理解。

testify包使用

testify核心有三部分内容: 其中 require 已废弃

  1. assert: 断言. 包括EqualNilErrordeng
  2. mock: mock测试. 包括 http mockobject mockmethod mock
  3. suite: 测试套件. 包括 SetupSuiteSetupTestBeforeTestAfterTestTearDownTestTearDownSuite

assert使用

package utils

import (
	"testing"

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

// 简单使用方式
func TestMd5Encode(t *testing.T) {
	assert := assert.New(t)
	assert.Equal("74b87337454200d4d33f80c4663dc5e5", Md5Encode("aaaa"), "is not equal")
}

// Map驱动方式
func TestEqual(t *testing.T) {
	assert := assert.New(t)
	var tt = []struct {
		input    int
		expected int
	}{
		{2, 4},
		{1, 1},
		{0, 0},
		{-5, 5},
		{99999, 99999},
	}
	
	for _, t := range tt {
		assert.Equal(t.input, t.expected)
	}
}

mock使用

package api

import (
	"testing"

	"github.com/alibaba/sentinel-golang/core/base"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

type prepareSlotMock struct {
	mock.Mock
}

func (m *prepareSlotMock) Order() uint32 {
	return 0
}

func (m *prepareSlotMock) Prepare(ctx *base.EntryContext) {
	m.Called(ctx)
	return
}

func Test_entryWithArgsAndChainPass(t *testing.T) {
	sc := base.NewSlotChain()
	ps1 := &prepareSlotMock{}
	rcs1 := &mockRuleCheckSlot1{}
	rcs2 := &mockRuleCheckSlot2{}
	ssm := &statisticSlotMock{}
	sc.AddStatPrepareSlot(ps1)
	sc.AddRuleCheckSlot(rcs1)
	sc.AddRuleCheckSlot(rcs2)
	sc.AddStatSlot(ssm)

	// method mock 
	ps1.On("Prepare", mock.Anything).Return()
	rcs1.On("Check", mock.Anything).Return(base.NewTokenResultPass())
	rcs2.On("Check", mock.Anything).Return(base.NewTokenResultPass())
	ssm.On("OnEntryPassed", mock.Anything).Return()
	ssm.On("OnCompleted", mock.Anything).Return()

	entry, b := entry("abc", &EntryOptions{
		resourceType: base.ResTypeCommon,
		entryType:    base.Inbound,
		batchCount:   1,
		flag:         0,
		slotChain:    sc,
	})
	assert.Nil(t, b, "the entry should not be blocked")
	assert.Equal(t, "abc", entry.Resource().Name())

	entry.Exit()

	ps1.AssertNumberOfCalls(t, "Prepare", 1)
	rcs1.AssertNumberOfCalls(t, "Check", 1)
	rcs2.AssertNumberOfCalls(t, "Check", 1)
	ssm.AssertNumberOfCalls(t, "OnEntryPassed", 1)
	ssm.AssertNumberOfCalls(t, "OnEntryBlocked", 0)
	ssm.AssertNumberOfCalls(t, "OnCompleted", 1)
}

suite使用

package discovery

import (
	"context"
	"fmt"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/suite"
	"go.etcd.io/etcd/integration"

	"github.com/kubeservice-stack/common/pkg/config"
)

type ETCDMockCluster struct {
	cluster   *integration.ClusterV3
	Endpoints []string
}

func StartEtcdMockCluster(t *testing.T) *ETCDMockCluster {
	cluster := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
	return &ETCDMockCluster{
		cluster:   cluster,
		Endpoints: []string{cluster.Members[0].GRPCAddr()},
	}
}

func (etcd *ETCDMockCluster) Terminate(t *testing.T) {
	etcd.cluster.Terminate(t)
}

type EtcdClusterTestSuite struct {
	suite.Suite
	Cluster *ETCDMockCluster
}

func (s *EtcdClusterTestSuite) SetupTest() {
	s.Cluster = StartEtcdMockCluster(s.T())
}

func (s *EtcdClusterTestSuite) TearDownTest() {
	s.Cluster.Terminate(s.T())
}

func (s *EtcdClusterTestSuite) TestWriteRead() {
	ed, err := newEtedDiscovery(config.Discovery{
		Endpoints: s.Cluster.Endpoints,
	}, "nobody")

	s.Nil(err)

	err = ed.Put(context.TODO(), "/test/key1", []byte("dongjiang"))
	s.Nil(err)

	d1, err1 := ed.Get(context.TODO(), "/test/key1")
	s.Nil(err1)
	s.Equal(string(d1), "dongjiang")

	err2 := ed.Delete(context.TODO(), "/test/key1")
	s.Nil(err2)

	err3 := ed.Close()
	s.Nil(err3)
}

// go test 入口
func TestEtcdClusterTestSuite(t *testing.T) {
	suite.Run(t, new(EtcdClusterTestSuite))
}

高级使用

httpmock: local http server

package healthz

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"code.iflytek.com/stc_sae/golang-template-project/pkg/routers"
	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

func TestHealthzRoute(t *testing.T) {
	assert := assert.New(t)
	r := gin.Default()
	router.Router(r)

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/healthz", nil)
	req.Host = "127.0.0.1:9445"
	r.ServeHTTP(w, req)

	assert.Equal(200, w.Code)
	assert.Len(w.Body.String(), 15)
}

orm mock: 使用内存sqlite3

package models

import (
	"testing"

	"git.iflytek.com/stc_sae/sae-apiserver/pkg/common/orm"
	"git.iflytek.com/stc_sae/sae-apiserver/pkg/config"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/suite"
)

type ClusterSuite struct {
	suite.Suite
	g *orm.DBConn
}

func (suite *ClusterSuite) SetupTest() {
	GlobalCfg = config.Orm{
		DBType:   config.SQLITE3,
		Database: "file::memory:?cache=shared",
	}
	suite.g = Ormer()
}

func (suite *ClusterSuite) TearDownTest() {
	assert := assert.New(suite.T())
	err := suite.g.Where("1 = 1").Delete(&Cluster{}).Error
	assert.NoError(err)
}

func (suite *ClusterSuite) TestCreateCluster() {
	assert := assert.New(suite.T())
	cm := &clusterModel{}
	id, err := cm.Add(&Cluster{
		Name: "aa",
	})

	assert.Equal(uint(1), id)
	assert.NoError(err)

	v, err := cm.GetById(uint(1))
	assert.Equal("aa", v.Name)
	assert.Equal("", v.DisplayName)
	assert.NoError(err)

	err = cm.UpdateByName(
		&Cluster{
			Name:        "aa",
			DisplayName: "this is test",
		})
	assert.NoError(err)
	v, err = cm.GetById(uint(1))
	assert.NoError(err)
	assert.Equal("aa", v.Name)
	assert.Equal("this is test", v.DisplayName)

	err = cm.DeleteByName("abc", false)
	assert.Error(err)
	v, err = cm.GetByName("aa")
	assert.NoError(err)
	assert.Equal("aa", v.Name)
	assert.Equal("this is test", v.DisplayName)
	assert.NotNil(v.DeletedAt)

	v, err = cm.GetById(uint(1))
	assert.NoError(err)
	assert.Equal("aa", v.Name)
	assert.Equal("this is test", v.DisplayName)
	assert.NotNil(v.DeletedAt)

	err = cm.DeleteByName("aa", false)
	assert.NoError(err)
	v, err = cm.GetById(uint(1))
	assert.Error(err)
}

func (suite *ClusterSuite) TestGetAllNormal() {
	assert := assert.New(suite.T())
	cm := &clusterModel{}
	id, err := cm.Add(&Cluster{
		Name: "a",
	})
	assert.NoError(err)
	assert.Greater(id, uint(0))

	id, err = cm.Add(&Cluster{
		Name: "b",
	})
	assert.Greater(id, uint(1))
	assert.NoError(err)

	id, err = cm.Add(&Cluster{
		Name: "c",
	})
	assert.Greater(id, uint(2))
	assert.NoError(err)

	vs, err := cm.GetNames()
	assert.NoError(err)
	assert.Len(vs, 3)
}

func TestClusterSuite(t *testing.T) {
	suite.Run(t, new(ClusterSuite))
}

其他

「如果这篇文章对你有用,请随意打赏」

Kubeservice博客

如果这篇文章对你有用,请随意打赏

使用微信扫描二维码完成支付