golang 单元测试几个小技巧

栏目: Go · 发布时间: 5年前

内容简介:关于为什么要单元测试,记得有的人说过,从单元测试,到业务测试再到UI测试,越底层发现错误越快,修改的成本也越低。就自己来说,最近用到了golang的项目,发现 golang 的单元你测试不如 java 的 springboot的好用,因此做了个技巧的总结,希望能方便单元测试。有时我们的代码依赖外部组件,但是外部组件无法提供单测环境,或者按正常流程运行不起来,这个时候就可以考虑用mock的方式处理,专注于自己模块的测试。使用的组件:

一、 背景

关于为什么要单元测试,记得有的人说过,从单元测试,到业务测试再到UI测试,越底层发现错误越快,修改的成本也越低。就自己来说,最近用到了golang的项目,发现 golang 的单元你测试不如 java 的 springboot的好用,因此做了个技巧的总结,希望能方便单元测试。

二、小技巧

1、golang 的 mock

有时我们的代码依赖外部组件,但是外部组件无法提供单测环境,或者按正常流程运行不起来,这个时候就可以考虑用mock的方式处理,专注于自己模块的测试。

使用的组件: testify ,这个简直是神器,建议使用。

mock 使用方式

a、创建一个 dog_service,实现 Speak 的方法

package service

import "fmt"

type DogService struct {
    
}

func (dog DogService) Speak(times int) int  {
    for a := 0; a < times; a++ {
        fmt.Printf("汪! %d\n", a)
    }
    return times
}

b、创建声音sevice,可以传入其他实现了SpeakService的实例。

voice_service

package service

type SpeakService interface {
    Speak(times int) int //发声次数
}

type MyService struct {
    SpeakService SpeakService
}

func (m MyService)SendVoice() {
    m.SpeakService.Speak(3)
}

c、在测试文件中mock Dog 的 speak 方法

main_test

package main

import (
    "fmt"
    "github.com/stretchr/testify/mock"
    "gotips/service"
    "testing"
)

//1、mock struct
type DogMock struct {
    mock.Mock
}

//2、args 对应 return 的参数列表,Called 对应 On 方法
func (m *DogMock) Speak(times int) int {
    fmt.Println("Mocked charge notification function")
    fmt.Printf("Value passed in: %d\n", times)

    args := m.Called(times)

    return args.Int(0)
}

//3、执行调用
func TestSendVoice(t *testing.T)  {
    dogService := new(DogMock)
    dogService.On("Speak", 3).Return(3)
    myService := service.MyService{dogService}
    myService.SendVoice()

}

使用 mock 后可以更细粒度的测试代码模块。目前还不能像 php 、java那样就行部分mock,这是作者的回复 https://github.com/stretchr/testify/issues/29 ,go实现这个还是有困难。

2、测试 golang 私有方法

私有方法测试的话就在同一个包下,如下面的例子。

private_p

package service

import "fmt"

func eat()  {
    fmt.Sprintf("test")
}

private_p_test

package service

import "testing"

func TestEat(t *testing.T) {
    eat()
}

关于是否要测试私有方法,stackoverflow 有些讨论

https://stackoverflow.com/questions/105007/should-i-test-private-methods-or-only-public-ones

多数认为,一般不要测试私有方法,这会破坏封装性。

3、golang fixture

单元测试的资源文件应该放在那里呢?查了下标准库,可以放到包下面的 testdata 文件夹。go build 的时候回忽略该文件夹。go 运行单元测试的时候会把当前package设置为当前目录,因此可以直接使用当前目录加载。例如下面里例子,hello.txt 放在 controller 的 testdata 中

|____controller

| |____testdata

| | |____hello.txt

| |____upload.go

| |____upload_test.go

path := "testdata/hello.txt"//要上传文件所在路径
    file, _ := os.Open(path)
    defer file.Close()

4、idea 传参问题

一般的大型项目会涉及到环境切换,测试环境、生产环境等。因此如果在 idea 中使用系统自带的 工具 跑单测需要把环境参数传进去。查询官方的资料会发现 idea 可以设置运行的测试模板,而且使用成本很低,因此采用这种方式传参。设置方式如下图:

golang 单元测试几个小技巧

Jietu20190616-130243.jpg

读取参数例子

args := os.Args

    env := ""
    for _, v := range args {
        if strings.HasPrefix(v, "env=") {
            env = string([]byte(v)[4:])
            break
        }
    }

    fmt.Println("env=",env)

5、包循环引用问题

正常开发中如果a引用了b包,b引用了a包,然后在测试b的单元单测会出现循环引用问题。这个官方的代码中已经有了解决方法,是把b的单元测试包改为 b_test,这样就完美的解决了这个问题。当然b_test不能被其他包引用,部分版本出现过包找不到的空指针问题,因为b_test和包的文件名不一致。后面的测试文件上传有个比较完整的例子。

package controller_test

6、golang http test

普通的 post、get请求比较简单,这里介绍下怎样测试文件上传。

func Bootstrap() *gin.Engine {


    args := os.Args

    env := ""
    for _, v := range args {
        if strings.HasPrefix(v, "env=") {
            env = string([]byte(v)[4:])
            break
        }
    }

    fmt.Println("env=",env)


    engine := gin.New()

    engine.MaxMultipartMemory = 1024 * 1024 * 1024

    engine.POST("/upload", controller.Upload)


    return engine
}

upload_test.go

package controller_test //防止循环引用

import (
    "bytes"
    "gotips/bootstrap"
    "io"
    "mime/multipart"
    "net/http"
    "net/http/httptest"
    "os"
    "path/filepath"
    "testing"
)

  func TestUpload(t *testing.T)  {

    //添加参数
    params := map[string]string{}
    params["test"] = "100"

    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)
    for k,v := range params{
        writer.WriteField(k,v)
    }
    //添加文件
    path := "testdata/hello.txt"//要上传文件所在路径
    file, _ := os.Open(path)
    defer file.Close()
    part, err := writer.CreateFormFile("content", filepath.Base(path))

    if err != nil {
        writer.Close()
        t.Error(err)
    }
    io.Copy(part, file)
    writer.Close()

    myRouter := bootstrap.Bootstrap()
    w := httptest.NewRecorder()
    request, _ := http.NewRequest("POST", "/upload", body)
    request.Header.Add("Content-Type", writer.FormDataContentType())


    myRouter.ServeHTTP(w,request);

    t.Log(w.Body.String())
}

upload.go

package controller

import (
    "bufio"
    "fmt"
    "github.com/gin-gonic/gin"
    "io"
    "net/http"
)

func Upload(c *gin.Context)  {
    // 单文件
    header, err := c.FormFile("content")

    if err != nil {
        errMsg := fmt.Sprintf("%s", err)
        c.JSON(-1,errMsg)
        return
    }



    fmt.Println(header.Filename)
    fp,err := header.Open()
    if err != nil {
        errMsg := fmt.Sprintf("%s", err)
        c.JSON(-1,errMsg)
        return
    }
    defer fp.Close()
    bufReader := bufio.NewReader(fp)

    var lines [][]byte
    for {
        line, _, err := bufReader.ReadLine() // 按行读
        if err != nil {
            if err == io.EOF {
                err = nil
                break
            }
        } else {
            lines = append(lines, line)
        }
    }

    for i, line := range lines {
        fmt.Printf("readfile: %d %s\n", i+1, line)
    }

    c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", header.Filename))
}
package main

import (
    "gotips/bootstrap"
)


main.go
func main()  {

    myRouter := bootstrap.Bootstrap()

    myRouter.Run(":8080")

}

三、总结

golang 的高效便捷给开发人员带来了极大的便利,但是代码质量也不能忽视。单元测试也是一门技术,需要在开发过程中不断的总结和创新。除了阅读官方源码的单测例子,利用好google也能找到好多实用的单测技巧。希望在单测中遇到困难的留个言,共同完善golang的单元测试。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

首席产品官2 从白领到金领

首席产品官2 从白领到金领

车马 / 机械工业出版社 / 79元

《首席产品官》共2册,旨在为产品新人成长为产品行家,产品白领成长为产品金领,最后成长为首席产品官(CPO)提供产品认知、能力体系、成长方法三个维度的全方位指导。 作者在互联网领域从业近20年,是中国早期的互联网产品经理,曾是周鸿祎旗下“3721”的产品经理,担任CPO和CEO多年。作者将自己多年来的产品经验体系化,锤炼出了“产品人的能力杠铃模型”(简称“杠铃模型”),简洁、直观、兼容性好、实......一起来看看 《首席产品官2 从白领到金领》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

SHA 加密
SHA 加密

SHA 加密工具