7

golang单元测试踩坑系列(一)

 3 years ago
source link: https://studygolang.com/articles/35031
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.

golang单元测试踩坑系列(一)

geange · 大约10小时之前 · 36 次点击 · 预计阅读时间 3 分钟 · 大约8小时之前 开始浏览    

7320c81a18c32d9c562217abdb4e0ace.jpg

在业务开发的过程中,老旧代码变成了新功能开发和测试的最大阻碍,人员变动以及。基于历史代码的功能修改可能会带来副作用,进而影响用户体验(绩效)。

  • 老代码对接口的使用非常少
  • 业务逻辑中存在大量网络调用

熟悉业务是需要找到对外部依赖最少的代码,从依赖最少的代码入手可以极大减少初期编写单元测试的工作量。外部依赖代码主要是对数据库的操作、接口调用等等。将对外的依赖抽离出来,是最简单的一种处理方式。

将网络调用通过接口形式调用

假设有一个场景,需要通过接口获取一个用户的信息,并判断用户的性别,代码如下(在业务场景中这样的代码非常常见):

type Service struct{}

func (s *Service) IsBoy(userid int) bool {
    user, err := GetUserInfo(userid int)
    if err != nil {
        return false
    }
    return user.Sex    == 1
}

func GetUserInfo(userid int) (*User, error) {
    resp, err := resty.New().R().Get(fmt.Sprintf("http://yoururl?user=%d", userid))
    if err != nil {
        return nil, err
    }
    var user User
    err = json.Unmarshal(resp.Body(), &user)
    return &user, err
}

如果需要编写单元测试,步骤如下

  1. 首先需要将GetUserInfo抽离出来,尽量不要在代码中直接调用包含网络请求的函数。
  2. 修改完成的代码如下,定义一个ApiUser的接口,实现该接口需要实现GetUserInfo方法。
  3. Service中嵌入一个实现ApiUser的对象,方便我们后续使用mock将模拟数据注入到Service对象中(关于mock我们后续后有详细分析)。
  4. mock简单来说就是,我们可以使用一个实现了ApiUser接口的类型放入到Service中,而这个ApiUser对象返回*User时可以不需要网络请求,只是简单伪装返回数据。在测试的时候我们就可以不需要依赖外部的服务来实现单元测试。
type Service struct{
    ApiUser
}

func (s *Service) IsBoy(userid int) bool {
    user, err := s.GetUserInfo()
    if err != nil {
        return false
    }
    return user.Sex    == 1
}

type ApiUser interface {
    GetUserInfo(userid int) (*User, error)
}

type apiUser struct {}

func (*apiUser) GetUserInfo(userid int) (*User, error) {
    ...
    return &user, err
}

ApiUser嵌入到调用类型中实际上也是一种妥协的。IsBoy(userid int)这个方法可能在多个地方被同时调用,如果在方法的参数中传入ApiUser对象也是不太现实(需要注意的是,初始化Service对象需要记得设置ApiUser的值)

另外一种方式是使用全局变量,但个人不太推荐。

编写逻辑测试

定义一个apiUserFake类型,实现GetUserInfo方法,用于模拟实际的网络请求

type apiUserFake struct{}

func (*apiUserFake) GetUserInfo(userid int) (*User, error) {
    switch userid {
    case 0:
        return &User{ID: userid, Name: "G1", Age: 18, Sex: 0}, nil
    case 1:
        return &User{ID: userid, Name: "B1", Age: 19, Sex: 1}, nil
    default:
        return nil, errors.New("user_not_found")
    }
}

假设源码文件为user.go,在当前目录下增加一个user_test.go

package rpc

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

func TestService_IsBoy(t *testing.T) {
    assert := assert.New(t)
    svc := Service{ApiUser: &apiUserFake{}}
    assert.Equal(false, svc.IsBoy(0))
    assert.Equal(true, svc.IsBoy(1))
}

一个简单的逻辑测试完成。

这种方式写单元测试很繁琐,后面提到的mock就是为了解决这种问题而出现的。

编写接口测试

接口测试需要启动服务器,并且将请求指向接口地址。

func TestApiUser_GetUserInfo(t *testing.T) {
    assert := assert.New(t)
    api := &apiUser{}
    user, err := api.GetUserInfo(1)
    assert.Nil(err)
    assert.EqualValues(&User{ID: 1, Name: "AA", Age: 23, Sex: 0}, user)
}

接口测试实际是作为验证外部依赖正确性的一种方式,不应该完全相信被调用方。

提高代码覆盖率

增加测试用例。

个人公众号:

qrcode_for_gh_7764e4bb99e9_258.jpg


有疑问加站长微信联系(非本文作者))

280

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:701969077


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK