golang及beego框架单元测试小结

  单元测试是保证开发质量的一个重要手段,提及golang开发,要保证开发质量,则难以回避单元测试的使用。golang开发语言有原生提供单元测试相关代码及工具,不足之处是代码实现层面不是太友好,写单元测试不大便利;有第三方,依据大家习惯使用的断言方式,给出了开源解决方案testify,为大家写单元测试提供了便利;具体到beego框架,鉴于其实现机制,实现单元测试,也需要进行适当的调整,下面将依次进行说明。

一、golang原生单元测试

  golang原生支持单元测试,使用上非常简单,测试代码只需要放到以 _test.go 结尾的文件中即可,golang单元测试的测试用例以 Test 开头,下面举例说明。

1 代码样例

  文件列表如下:

file.go
file_test.go
go.mod

file.go文件内容如下:

package fileimport ("os""path""strings"
)// Exists reports whether the named file or directory exists.
func Exists(name string) bool {if _, err := os.Stat(name); err != nil {if os.IsNotExist(err) {return false}}return true
}func PureName(name string) string {return strings.TrimSuffix(name, path.Ext(name))
}

file_test.go文件内容如下:

package fileimport ("fmt""io/ioutil""os""testing"
)func TestExists(t *testing.T) {// ioutil.TempDir 不指定第一个参数,则默认使用os.TempDir()目录tmepDir := os.TempDir()fmt.Println(tmepDir)tempDir, err := ioutil.TempDir("", "fileTest") // 在DIR目录下创建tmp为目录名前缀的目录,DIR必须存在,否则创建不成功if err != nil {t.Errorf("create temp directory failed with %v.", err)return}// fmt.Println(tempDir) // 生成的目录名为tmpXXXXX,XXXXX为一个随机数defer os.RemoveAll(tempDir)testFilePath := tempDir + string(os.PathSeparator) + "existsTest.txt"r := Exists(testFilePath)if r {t.Errorf("Exists(%s) failed. Got %t, expected false.", testFilePath, r)return}file, error := os.Create(testFilePath)if error != nil {fmt.Println("文件创建失败")return}file.WriteString("insert into file") //利用file指针的WriteString()写入内容file.Close()r = Exists(testFilePath)if !r {t.Errorf("Exists(%s) failed. Got %t, expected true.", testFilePath, r)}
}func TestPureName(t *testing.T) {name := "what.bat"expect := "what"pure := PureName(name)r := pure == expectif !r {t.Errorf("PureName(%s) failed. Got %s, expected %s.", name, pure, expect)}name = "name"expect = "name"pure = PureName(name)r = pure == expectif !r {t.Errorf("PureName(%s) failed. Got %s, expected %s.", name, pure, expect)}
}

go.mod文件内容如下:

module example.com/filego 1.18

2 创建golang模块说明

  创建go语言模块,可以考虑使用go命令行工具的mod子命令创建模块:

go mod init example.com/file

然后再使用如下命令下载更新依赖:

go mod tidy

  关于golang模块创建,详情可参考官方说明https://golang.google.cn/doc/tutorial/create-module。golang workspaces使用,详情可参考官方说明https://golang.google.cn/doc/tutorial/workspaces。

3 运行测试用例

  下面以Visual Studio Code作为开发工具进行演示说明,将依次说明如何运行单个测试用例、运行全部测试用例以及运行部分测试用例。

(1) 运行单个测试用例

  在每个测试用例左上角有“run test|debug test”两个按钮,点击“run test”按钮会直接运行该测试用例,点击“debug test”按钮则会以debug模式运行该测试用例,实际效果可参见下图:

运行后的测试结果会在下面的OUTPUT栏下面展示。

(2) 运行全部测试用例

  运行全部测试用例,比较简单,在控制台上,进入代码所在目录,直接运行下面命令即可:

go test

相应效果截图如下:

(3) 运行部分测试用例

  运行部分测试用例,有两种比较典型的方式,一是直接在go test命令后带上要跑的测试代码所在文件以及依赖文件,样例如下:

go test .\file.go .\file_test.go

相应效果截图如下:

二是直接运行整个模块的测试用例,样例如下:

go test cfh008.com/file

相应效果截图如下:

  想了解更详细的执行测试用例方式,可通过执行下面命令查看官方说明文档:

go help test

二、使用单元测试框架

  前面讲过,直接使用原生的单元测试方式,写测试用例不大方便,这里推荐一个第三方开源组件testify(https://github.com/stretchr/testify),可简化测试用例的编写,提高效率。下面我们使用testify重新实现一下上述file模块单元测试用例编写,新的file_test.go代码如下:

package fileimport ("fmt""io/ioutil""os"// "path/filepath""testing""github.com/stretchr/testify/assert"
)func TestExists(t *testing.T) {assert := assert.New(t)// ioutil.TempDir 不指定第一个参数,则默认使用os.TempDir()目录tmepDir := os.TempDir()fmt.Println(tmepDir)tempDir, err := ioutil.TempDir("", "fileTest") // 在DIR目录下创建tmp为目录名前缀的目录,DIR必须存在,否则创建不成功assert.Nil(err)// fmt.Println(tempDir) // 生成的目录名为tmpXXXXX,XXXXX为一个随机数defer os.RemoveAll(tempDir)testFilePath := tempDir + string(os.PathSeparator) + "existsTest.txt"assert.False(Exists(testFilePath))file, error := os.Create(testFilePath)assert.Nil(error)file.WriteString("insert into file") //利用file指针的WriteString()写入内容file.Close()assert.True(Exists(testFilePath))
}func TestPureName(t *testing.T) {assert := assert.New(t)name := "what.bat"expect := "what"pure := PureName(name)assert.Equal(pure, expect)name = "name"expect = "name"pure = PureName(name)assert.Equal(pure, expect)
}

对照查看,会发现使用testify,实现同样的效果,代码简洁很多。如果使用新的file_test.go替换了前面的旧文件,则需要在控制台运行下面的命令:

go mod tidy

确保依赖的第三库已经下载下来,然后再根据需要,使用前面运行单元测试用例的方式,执行测试用例。
  如果想对面向对象实现的代码进行单元测试,该框架也提供了便利(使用方式可参考https://pkg.go.dev/github.com/stretchr/testify/suite),下面直接提供代码fileSuite_test.go,想进一步研究的可自行下载实际验证。

package file// Basic imports
import ("fmt""io/ioutil""os""testing""github.com/stretchr/testify/suite"
)// Define the suite, and absorb the built-in basic suite
// functionality from testify - including assertion methods.
type FileTestSuite struct {suite.SuiteModleName string
}// Make sure that ModleName is set to file
// before each test
func (suite *FileTestSuite) SetupSuite() {fmt.Println("begin to execute")suite.ModleName = "file"
}// before each test
func (suite *FileTestSuite) SetupTest() {fmt.Println("begin to execute test case")
}// after each test
func (suite *FileTestSuite) TearDownTest() {// suite.Equal(suite.ModleName, "")fmt.Println("to the end of test case")
}// after all test
func (suite *FileTestSuite) TearDownSuite() {fmt.Println("to the end")
}// All methods that begin with "Test" are run as tests within a
// suite.
func (suite *FileTestSuite) TestExists() {suite.Equal(suite.ModleName, "file")// ioutil.TempDir 不指定第一个参数,则默认使用os.TempDir()目录tmepDir := os.TempDir()fmt.Println(tmepDir)tempDir, err := ioutil.TempDir("", "fileTest") // 在DIR目录下创建tmp为目录名前缀的目录,DIR必须存在,否则创建不成功suite.Nil(err)// fmt.Println(tempDir) // 生成的目录名为tmpXXXXX,XXXXX为一个随机数defer os.RemoveAll(tempDir)testFilePath := tempDir + string(os.PathSeparator) + "existsTest.txt"suite.False(Exists(testFilePath))file, error := os.Create(testFilePath)suite.Nil(error)file.WriteString("insert into file") //利用file指针的WriteString()写入内容file.Close()suite.True(Exists(testFilePath))
}// All methods that begin with "Test" are run as tests within a
// suite.
func (suite *FileTestSuite) TestPureName() {suite.Equal(suite.ModleName, "file")name := "what.bat"expect := "what"pure := PureName(name)suite.Equal(pure, expect)name = "name"expect = "name"pure = PureName(name)suite.Equal(pure, expect)
}// 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 TestFileTestSuite(t *testing.T) {suite.Run(t, new(FileTestSuite))
}

三、beego单元测试注意点

  提到beego的单元测试(以我使用的“github.com/beego/beego/v2 v2.0.5”版本为例),这里重点需要注意的主要是两点,第一点,因为单元测试用例执行时自成体系,正常执行程序时所做的配置信息加载以及数据库存储之类的初始化操作,是不会执行的,所以我们需要自行处理这方面的内容;第二点,涉及到网络请求的,特别是涉及登录session之类的操作的,需要考虑全部环节。下面将分别进行具体说明。

1 单元测试依赖初始化

  单元测试的依赖主要是全局配置的加载以及数据库初始化,下面我们直接给出方案,代码样例如下:

package modelsimport ("path/filepath""runtime"beego "github.com/beego/beego/v2/server/web"
)
func InitForTest() {_, file, _, _ := runtime.Caller(0)apppath, _ := filepath.Abs(filepath.Dir(filepath.Join(file, ".."+string(filepath.Separator))))// 加载全局配置beego.TestBeegoInit(apppath)// logs.Info("%v call beego.TestBeegoInit(%v)", runutils.RunFuncName(), apppath)Init(apppath)
}

其中数据库初始化相关代码封装在了*func Init(workPath string)*方法中,可根据实际情况,实现具体业务逻辑。

2 外部请求全环节实现

  实现外部请求全环节,关键是登录完成后,需要将登录后获取到的cookie带入到后面的请求中,从而实现模拟已登录用户请求操作,达到单元测试目的。在讲解具体实现前,先给大家查看一下整体目录结构,使大家对背景有一个大体了解,对应截图信息如下:

上图中,有用红色方框标记的conf目录,这里之所以特意标注,是因为使用beego 2.0.5版本时,很多全局性的变量是在运行时(使用模块的init()方法,优先级很高),直接从工作目录下或可执行文件同级目录下的conf目录中读取配置直接进行的初始化,而且后面调用相应方法重新加载配置也无效,不得已情况下,只好把全局的配置拷贝一份放到tests目录下,确保可找到配置,正常完成初始化。
  具体实现全环节请求时,直接上代码:

package testimport ("bytes""encoding/json""errors""fmt""manager/controllers""net/http""net/http/httptest""strconv""testing""time""gitee.com/cfh008/runutils""github.com/beego/beego/v2/core/logs"beego "github.com/beego/beego/v2/server/web""github.com/stretchr/testify/assert"
)func loginForCookies(username string, password string) (cookies []*http.Cookie, err error) {params := make(map[string]interface{})currentTime := time.Duration(time.Now().UnixNano()).Milliseconds()timestamp := strconv.FormatInt(currentTime, 10)params["sn"] = timestampdata := make(map[string]interface{})params["data"] = datadata["username"] = usernamedata["password"] = passwordreqBody, doErr := json.Marshal(params)if doErr != nil {errorInfo := fmt.Sprintf("%v json.Marshal(%v) failed with %v", runutils.RunFuncName(), params, doErr)err = errors.New(errorInfo)logs.Error(errorInfo)return}r, _ := http.NewRequest("POST", "/v1/user/login", bytes.NewReader(reqBody))w := httptest.NewRecorder()beego.BeeApp.Handlers.ServeHTTP(w, r)var result map[string]interface{}doErr = json.Unmarshal(w.Body.Bytes(), &result)if doErr != nil {errorInfo := fmt.Sprintf("%v json.Unmarshal(%v) failed with %v", runutils.RunFuncName(), string(w.Body.Bytes()), doErr)err = errors.New(errorInfo)logs.Error(errorInfo)return}cookies = w.Result().Cookies()return
}func TestUserStatus(t *testing.T) {asserter := assert.New(t)// 未登录直接请求params := make(map[string]interface{})currentTime := time.Duration(time.Now().UnixNano()).Milliseconds()timestamp := strconv.FormatInt(currentTime, 10)params["sn"] = timestampdata := make(map[string]interface{})params["data"] = datareqBody, reqErr := json.Marshal(params)asserter.Nil(reqErr)r, _ := http.NewRequest("POST", "/v1/user/status", bytes.NewReader(reqBody))w := httptest.NewRecorder()beego.BeeApp.Handlers.ServeHTTP(w, r)var result map[string]interface{}doErr := json.Unmarshal(w.Body.Bytes(), &result)asserter.Nil(doErr)asserter.Equal(200, w.Code)asserter.Greater(w.Body.Len(), 0)resultCode := result["code"].(string)asserter.Equal(strconv.Itoa(controllers.ERROR_USER_NOT_LOGIN), resultCode)// 先登录cookies, doErr := loginForCookies("yourName", "yourPassword")asserter.Nil(doErr)asserter.True(len(cookies) > 0)// 正常登录后请求params = make(map[string]interface{})currentTime = time.Duration(time.Now().UnixNano()).Milliseconds()timestamp = strconv.FormatInt(currentTime, 10)params["sn"] = timestampdata = make(map[string]interface{})params["data"] = datareqBody, reqErr = json.Marshal(params)asserter.Nil(reqErr)r, _ = http.NewRequest("POST", "/v1/user/status", bytes.NewReader(reqBody))for _, item := range cookies {r.AddCookie(item)}w = httptest.NewRecorder()beego.BeeApp.Handlers.ServeHTTP(w, r)doErr = json.Unmarshal(w.Body.Bytes(), &result)asserter.Nil(doErr)asserter.Equal(200, w.Code)asserter.Greater(w.Body.Len(), 0)resultCode = result["code"].(string)asserter.Equal(strconv.Itoa(0), resultCode)
}

上面的代码,是和对应接口实现相匹配的代码,大家在实现逻辑时,需要根据实际情况进行必要的调整。
  在实际工作中,大家可结合前面提到的testify框架,把一些公共的代码抽离出来,减少冗余代码。至此,总结告一段落。

golang及beego框架单元测试小结相关推荐

  1. windows下安装及配置 golang 的Web框架Beego环境

    1.首先需要安装配置 go and git,参考如下链接 https://www.cnblogs.com/zjwgo/p/9356280.html 2.安装配置 beego 前提: 安装并配置成功go ...

  2. beego框架 golang web项目-个人博客系统

    beego框架 golang web项目-个人博客系统 beego个人博客系统功能介绍 首页 分页展示博客 博客详情 评论 文章专栏 分类导航 资源分享 时光轴点点滴滴 关于本站 后台管理 登录 系统 ...

  3. Golang语言快速上手到综合实战(Go语言、Beego框架、高并发聊天室、豆瓣电影爬虫) 下载

    下载Golang语言快速上手到综合实战(Go语言.Beego框架.高并发聊天室.豆瓣电影爬虫) 下载地址:请加QQ:397245854 Go是Google开发的一种编译型,可并行化,并具有垃圾回收功能 ...

  4. golang beego框架对运行异常的处理

    运行时异常 panic 在通常情况下,函数向其调用方报告错误的方式都是返回一个error类型的值.但是当遇到致命错误的时候,很可能会使程序无法继续运行.Go推荐通过调用panic函数来报告致命错误,它 ...

  5. [Golang] GoConvey测试框架使用指南

    GoConvey 是一款针对Golang的测试框架,可以管理和运行测试用例,同时提供了丰富的断言函数,并支持很多 Web 界面特性. GoConvey 网站 : http://smartystreet ...

  6. beego框架-logs模块学习笔记

    前一段时间的项目中用到了beego框架下的logs模块,记录一下使用过程.logs模块官方文档 一.示例 1.控制台输出 //控制台输出 func Console() {log := logs.New ...

  7. Go语言Web框架:Beego框架快速入门

    文章目录 Beego框架快速入门 1.beego框架了解 2.MVC架构 3.环境搭建 4.beego的项目结构分析 5.Beego快速体验 6.Beego运行流程分析 7.Post案例实现 7.1前 ...

  8. 十九、Beego框架快速入门

    Beego框架快速入门 1.框架了解 go语言的web框架:beego,gin,echo等等,那为什么我们选择beego呢? 第一,beego是中国人开发的,开发文档比较详细,beego官网网址: h ...

  9. Golang哪个Web框架好用?

    框架一直是敏捷开发中的利器,能让开发者很快的上手并做出应用,甚至有的时候,脱离了框架,一些开发者都不会写程序了.成长总不会一蹴而就,从写出程序获取成就感,再到精通框架,快速构造应用,当这些方面都得心应 ...

最新文章

  1. 安装mysql和memcached
  2. 图解RxJava2(一)
  3. perl 分析mysql binlog
  4. 华为云FusionInsight+永洪BI共建政企用数之道,普惠千行百业
  5. 【深度学习】收藏|神经网络调试Checklist
  6. DataFountain新上两项CV算法竞赛(文化传承——汉字书法多场景识别、大数据医疗——肝癌影像AI诊断)——50万巨奖等你来拿!
  7. Python+OpenCV:图像去噪(Image Denoising)
  8. F#基础教程 unit类型
  9. node主要应用场景是在大前端
  10. IDDD 实现领域驱动设计-上下文映射图及其相关概念
  11. 【优化预测】基于matlab布谷鸟算法优化SVM预测【含Matlab源码 1422期】
  12. Weblogic部署
  13. OC 如何读取plist文件
  14. Ubuntu 16.04安装sogou 拼音输入法
  15. 穷爸爸富爸爸作者呼吁投资者提前进入数字货币市场
  16. 【实战】如何在手机上实时接收微信小店订单提醒
  17. 华为电脑怎么把虚拟化打开_电脑怎么在bios开启虚拟化?
  18. 【YOLOv5实战2】基于YOLOv5的交通标志识别系统-自定义数据集
  19. CES 2022:四大芯片巨头正面厮杀,抢滩自动驾驶、元宇宙
  20. 计算机经典好书整理收集(持续更新中...)

热门文章

  1. 大数据实战 --- 淘宝用户行为数据分析
  2. 桌面支持--PLM软件必须右键用管理员账号打开
  3. 开源PLM软件Aras详解四 ItemType的概念
  4. WireShark找不到小米wifi,360wifi如何解决
  5. [面试]机器学习面试常见问题
  6. 有苦有乐的算法 --- 自定义一个栈,实现压栈(push)、弹栈(pop)、获取站内最小值(getmin)
  7. 小程序wxs使用教程
  8. 学习编程(c语言)的经历以及对未来的期望
  9. 普乐蛙7D动感影院|6d动感电影院|7d动感影院设备
  10. 流处理旅程——storm之spout介绍