从头到脚说单测——谈有效的单元测试(二)

发表于:2021-4-27 09:32

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:佚名    来源:知乎

分享:
  四. 单元测试的阶段
  1. 广义的单元测试,我们指这三部分的有机组合:
  · code review
  · 静态代码扫描
  · 单元测试用例编写
  2. 结合新闻的实践,我把单测成长的过程分为4个目标,分别为:
  · 会写,全员可写;
  · 写的好,同时关注可测性问题,试点解决;
  · 识别可测性问题,熟练使用重构方法进行重构;识别代码架构设计问题;case与业务代码同步编写;
  · TDD。但这个目标是期望,不能作为必须实现的目标。
  截至发稿当天,新闻处于第三阶段,即,每个迭代均能产出高质量的case,人数覆盖和需求覆盖均较高;关注重点在于可测性,时刻注重重构。
  五. 单元测试的指标
  还挺尴尬的,不太有直接的指标去衡量单测的效果。我们也经常被问到,“怎么证明你们新闻单测的作用呀?”
  · bug类指标(间接指标):连续迭代的bug总数趋势、迭代内新建bug的趋势、千行bug率;
  · 单测的需求覆盖度(50%以上),参与人员覆盖度(80%以上);
  · 单测case总数趋势,代码行增量趋势;
  · 增量代码的行覆盖率(接入层80%,客户端30%);
  · 单函数圈复杂度(低于40),单函数代码行数(低于80),扫描告警数。
  在迭代需求持续高吞吐量的前提下,以新闻iOS的数据为例:
  六. go单元测试框架选型
  基本选型:testify + gomonkey
  附加:httptest + sqlmock
  前提
  · 测试文件,以_test.go结尾,与被测文件放于相同目录;
  · 测试函数,函数名以Test开头,并且随后的第一个字符必须为大写字母或下划线,如:TestParseReq_CorrectNum_TableDriven;
  · 测试函数,参数为t *testing.T;对于bench测试,参数为b *testing.B;
  · 运行命令行,我的文章有深入讲解:go test命令行。
  testify常规用法
  testify基于gotesting编写,所以语法上、执行命令行与go test完全兼容。
  支持大量高效的api,比如:
  assert.Equal:常规对比,是把两者分别换成[]byte去严格比对。
  assert.Nil:判断对象为nil时,有时对err判空时也用。
  assert.Error:判断err的具体类型和内容。
  assert.JSONEq:这个比较有用,对比map时;或者对比struct的时候,也会先转为map,在用这个api去做对比,如下面这个例子,我封装了建议的方法去将struct转换为string(json):
  · 支持suite,用例集管理
  · 运行时,可以指定用例集执行
  · 自带mock工具,但只支持接口方法的mock,而且用法相对复杂
  · table-driven
  gomonkey用法(加粗字体表示常用)
  · 支持为一个函数打一个桩
  · 支持为一个成员方法打一个桩
  · 支持为一个全局变量打一个桩
  · 支持为一个函数变量打一个桩
  · 支持为一个函数打一个特定的桩序列
  · 支持为一个成员方法打一个特定的桩序列
  · 支持为一个函数变量打一个特定的桩序列
  · table-driven的方式定义一系列stub
  注意,对内联函数的Stub,go test命令行一定要加上参数才可生效。见官方文档。所以,我的命令行默认加上-gcflags=all=-l就行了。
  我设置了一些goland的代码模板,放在附件中。
  ApplyFunc是对外部函数Stub(非类方法)
  /* 用法:gomonkey.ApplyFunc(被stub函数名, 被stub函数签名) 函数返回值
  *例子:
  patches := gomonkey.ApplyFunc(fake.Exec, func(_ string, _ ...string) (string, error) {
  return outputExpect, nil
  })
  */
  patches := gomonkey.ApplyFunc(lcache.GetCache, func(_ string) (interface{}, bool) {
  return getCommentsResp()
  })
  defer patches.Reset()
  ApplyMethod是对类函数Stub。但这里注意,要被stub的方式是私有方法,gomonkey通过反射是找不到的,有两种解决方法:1)使用增强版的gomonkey;2)不Stub它,而是选择走进这个函数,这个话题在后面专题谈mock的时候说。
  /* 用法:gomonkey.ApplyMethod(反射类名, 被stub函数签名) 函数返回值
  *例子:
  var s *fake.Slice
  patches := ApplyMethod(reflect.TypeOf(s), "Add", func(_ *fake.Slice, _ int) error {
  return nil
  })
  */
  var ac *auth.AuthCheck
  patches := gomonkey.ApplyMethod(reflect.TypeOf(ac), "PrepareWithHttp", func(_ *auth.AuthCheck, _ *http.Request, _ ...auth.AuthOption) error {
  return fmt.Errorf("prepare with nil object")
  })
  defer patches.Reset()
  ApplyMethodSeq是对同一个Stub的函数返回不同的结果
  /* 用法:gomonkey.ApplyMethodSeq(类的反射,"被stub函数名", 返回结构体);
  Params{info1},中括号内为被stub函数的返回值列表;
  Times为生效次数
  *例子:
  e := &fake.Etcd{}
  info1 := "hello cpp"
  info2 := "hello golang"
  info3 := "hello gomonkey"
  outputs := []OutputCell{
  {Values: Params{info1, nil}},
  {Values: Params{info2, nil}},
  {Values: Params{info3, nil}},
  }
  patches := ApplyMethodSeq(reflect.TypeOf(e), "Retrieve", outputs)
  defer patches.Reset()
  */
  conn := &redis.RedisConn{}
  patch1 := gomonkey.ApplyFunc(redis.NewRedisHTTP, func(serviceName string, _ string) *redis.RedisConn {
  conn := &redis.RedisConn{
  redis.RedisConfig{},
  &redis.RedisHelper{},
  }
  return conn
  })
  defer patch1.Reset()
  // mock redis data. 返回空和不为空的情况
  outputCell := []gomonkey.OutputCell{
  {Values: gomonkey.Params{"12", nil}, Times: 1},
  {Values: gomonkey.Params{"", nil}, Times: 1},
  }
  patchs := gomonkey.ApplyMethodSeq(reflect.TypeOf(conn.RedisHelper), "Get", outputCell)
  defer patchs.Reset()
  先举这几个例子,详细的可以在上面的链接文章中全面得到。
  这里补充一点,对类方法进行stub,必须要找到该方法对应的真实的类(结构体),举个例子:
  //被测函数中有如下一段,其中的Get方法我们想stub掉,只要找到Get方法对应的类就好了
  readCountStr, _ := conn.Get(redisKey)
  if len(readCountStr) == 0 {
  return 0, nil
  }
  定位conn,是RedisConn类型的struct
  type RedisConn struct {
  RedisConfig
  *RedisHelper
  }
  所以第一次,我用gomonkey.AppleyMethod时这么写:
  patches := gomonkey.ApplyMethod(reflect.TypeOf(*RedisConn),"Get", func(_ *redis.RedisHelper,_ string, _ []string) ([]string, error){
  return info,err_notNil
  })
  defer patches.Reset()

      本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号