优测云测试平台 | 有效的单元测试

这篇具有很好参考价值的文章主要介绍了优测云测试平台 | 有效的单元测试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

本文作者提出了一种评价单元测试用例的质量的思路,即判断用例是否达到测试的“四大目标”。掌握识别好的用例的能力,可以帮助我们高效地写出高质量的测试用例。

评判冰箱的好坏,并不需要有制造一台冰箱的能力。在开始写测试用例之前,可以先掌握识别好的用例的能力,这样可以避免我们自己花费大量的时间写出低质量的用例。要评价用例的质量好坏,就看测试是否达到我们期望的目标。

二、测试的第一目标是“尽可能地”排除缺陷

当我们给系统增加功能时,首先要保证增加的功能没有缺陷,同时还要防止回归。“回归”(regression) 意指系统在增加了一些功能后,一些旧的功能出现缺陷。测试用例是否最大范围地去挖掘了系统的缺陷,最广为认知的手段就是计算测试覆盖率。但是关于覆盖率有一些认知需要澄清。
覆盖率高是不够的!
测试覆盖率低,就是系统的代码只有很少一部分被测试过了,那些未测试的部分是好是坏不知道。但是测试覆盖率高却并不意味着测试质量高,简单的例子就是无断言的测试用例,覆盖率可以很高,但是它跟没有测试几乎是一样的。不过还有更违反直觉的事实可以看一下一个简单的例子:
listing 1

func IsStringLong(s string) bool {
      if len(s) > 5 {
         return true
      }
      return false
   }

   func TestIsStringLong(t *testing.T) {
      got := IsStringLong("abc")
      assert.Equal(t, false, got)
   }

被测代码一共 6 行,测试覆盖到了 1,2,5 行,覆盖率 50%. 然后我们测试代码不变,被测代码简化一下
listing 2

func IsStringLong(s string) bool {
      return len(s) > 5 
   }

马上覆盖率就达到了 100%. 很显然这个 100% 的覆盖率并不充分,它都没有测试 s > 5 的情况。了解测试的同学马上会想到,上面覆盖率的概念其实是覆盖率的一种,叫行覆盖率(其实英文的 statement coverage 会更加确切)。另外一种覆盖率叫做分支覆盖率,IsStringLong 有 2 个逻辑分支,我们的测试代码只覆盖了其中一个,为了充分测试,我们要提供分支的覆盖率。
listing3

func TestIsStringLong(t *testing.T) {
  type args struct {
    s string
  }
  tests := []struct {
    name string
    args args
    want bool
  }{
    {
      name: "'abcde' results short",
      args: args{
        s: "abcde",
      },
      want: false,
    },
    {
      name: "'abcdef' results long",
      args: args{
        s: "abcdef",
      },
      want: true,
    },
  }
  for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
      if got := IsStringLong(tt.args.s); got != tt.want {
        t.Errorf("IsStringLong() = %v, want %v", got, tt.want)
      }
    })
  }
}

撒花,我们测试了所有分支!但是全部的分支覆盖率也存在问题,我们来看另一个例子:

listing 4

type Recorder struct {
  Value string
}

var recorder = Recorder{}

func IsStrLong(s string) bool {
  recorder.Value = s
  return len(s) > 5
}

func TestIsStrLong(t *testing.T) {
  type args struct {
    s string
  }
  tests := []struct {
    name string
    args args
    want bool
  }{
    {
      name: "'abcde' results short",
      args: args{
        s: "abcde",
      },
      want: false,
    },
    {
      name: "'abcdef' results long",
      args: args{
        s: "abcdef",
      },
      want: true,
    },
  }
  for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
      if got := IsStrLong(tt.args.s); got != tt.want {
        t.Errorf("IsStrLong() = %v, want %v", got, tt.want)
      }
    })
  }
}

被测函数增加了一项功能,记录最后一次调用的参数。测试代码不变,同样还是 100% 的行覆盖和 100% 的分支覆盖,但是 “recoreder 里是否有正确记录最后一次参数” 却无法得到保障。假设这段代码提交以后,下个迭代某个开发失手删除了:

recorder.Value = s

这一行,测试流水线依然通过,甚至因为全覆盖还会给你发个点赞的信息。但是项目上线后却可能因为这段记录丢了引发功能故障。解决上面的问题就是在断言阶段,增加对 recorder.Value 的断言:
listing 5

for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
      if got := IsStrLong(tt.args.s); got != tt.want {
        t.Errorf("IsStrLong() = %v, want %v", got, tt.want)
      }
      if recorder.Value != tt.args.s {
        t.Errorf("IsStrLong() called but recorder.Value = %v, want %v", recorder.Value, tt.args.s)
      }
    })

变异测试可以辅助评价断言质量,但是性能开销巨大
接着上面的例子,listing 4 中我们看到一类断言不足,但是覆盖率 100% 的用例,这种情况我们可以用变异测试来检测。其大致原理是流水线在启动后,随机修改被测代码,而测试代码不变,然后运行测试,若用例依然能通过,则预示着测试用例可能质量不高。以 listing 4 为例,变异测试引擎生成的一个版本是删除了:

recorder.Value = s

这一行代码,对变异版本运行用例会发现用例通过,则该用例的变异得分会低。而相同的变异版本, listing 5 中的用例会失败。

变异测试会对被测代码的语法树作各种变异,要对每个变异版本进行测试,其工作量是巨大的,耗时可想而知。因此对整个代码库进行变异测试,通常不适合放在对耗时要求较高的 CR 流水线上。可行的方法有:

-1.在定时流水线上对被测系统全量运行,被测系统比较大时,可以分模块进行。

-2.如果被测系统组织良好,CR 流水线进准测试(分片测试)能力足够高,则可以在 CR 流水线上对改动部分作变异测试。

实践建议

  • 1.每次 CR 统计覆盖率,特别是增量覆盖率, 覆盖率过低时阻挡合入。[1]

  • 2.CR 的 reviewer 需注意断言是否充分合理,相对变异测试,负责任的高水平的 reviewer 效率更高。因此 CR 单的 change list 应该尽可能小,这样 CR 通过才能尽可能快。

  • 3.引入定时流水线,分模块对代码库进行变异测试。这需要根据实际性能调整策略。

代码覆盖率高是不够的!终极的覆盖

以 tRPC-Go 数据校验为例。tRPC-Go 有配套的数据校验工具,其原理简单说就是在 proto 文件中增加 Message 各字段的校验规则,在 tRPC 服务中引入校验拦截器,在运行时拦截器会针对入参进行校验。
step 1 在 proto 中增加校验规则


// QueryCaseRecentExecsRequest 包含用例 id, 用来查询其最近 n 次执行记录, 最多查询最近 100 次记录。
message QueryCaseRecentExecsRequest {
  sint64 case_id = 1;
  uint32 count = 2[(validate.rules).uint32.lte = 100];
}

step 2 在服务的 trpc_go.yaml 中配置使用拦截器


server:
  filter:
    - validation
}

step 3 在服务的 main.go 中注册拦截器


import (
   // ...
   _ "git.code.oa.com/trpc-go/trpc-filter/validation"
)

func main() {
   // ...
}

在服务的方法中,就不需要再做这类校验了


func (s *XXXService) QueryCaseRecentExecs(ctx context.Context, req *proto.QueryCaseRecentExecsRequest, rsp *proto.QueryCaseRecentExecsReply) error {
  // 不再需要
  //if req.Count > 100 {
  //  return errors.New("count not allowed")
  //}

  result, err := s.CaseExecService.QueryRecentExecsByCaseID(ctx, req.CaseId, int(req.Count))
  if err != nil {
    return err
  }
  rsp.CaseExecs = pbconv.FromCaseExecs(result)
  return nil
}

在这样的代码库中,不管是 XXXService 还是 XXXService.CaseExecService (domain service) 都不需要对 count 进行拦截校验了。即使单测代码全覆盖,我们也无法保证我们 step 1,2,3 都按照文档正确地配置了,更进一步,即使我们非常仔细地检查了配置,也不能保证规则检查正确地生效了,毕竟,谁知道

git.code.oa.com/trpc-go/trpc-filter/validation

有没有 bug?这个问题的最终解决方案就是将服务部署起来,向它发请求,来确保参数校验确确实实生效了(接口测试或者端到端测试)。可能有的同学会有疑问“不要测试框架代码”这样的建议有错吗?建议没错,但那是针对单元测试的。但是我们的自己的产品在发布前,不管是哪种原因产生的缺陷,都应该尽量通过测试来排查出来,框架不行就替换框架。产品出问题,用户才不关心是开发者造成的还是框架造成的。请注意完整测试我们的系统并不是建议大家完整地测试我们用到的每一个框架每一个库,而是测试我们系统的每个功能。[2]

三、测试的目标之二:支撑重构

理想的测试用例应该只检验被测系统的输入输出(输出包含通常意义的返回值和一切副作用,如上文提到的状态改变),而不应该关心系统到底是怎么实现这个功能的。这样当我们重构一段代码时,我们只要针对修改后的功能代码运行原有的测试用例,当用例通过时就证明我们的重构没有引入缺陷。如果你重构一段代码后发现原有的用例无法通过了,但是我们自己对重构前后的功能一致非常有信心,此时不得不“微调”一下用例来保证用例通过,每当此时就应该意识到这些用例在支撑重构上做得不够好。功能不变的情况下,通过改变实现而让测试用例失败的情况称之为误警(false alarm),或者叫假阳性(false positive). 熟悉 SRE 的同学对误警应该不会陌生。假阳性最好的类比就是医学上假阳性:没病检验出病。事实上自动化测试跟医学检验非常相似,医学检验的英文也叫 test,有假阳性,也有假阴性,而且甚至都没有办法完全排除假阳性和假阴性的发生。

支撑重构做得不好的危害:

  • 1.就假阳性而言,假阳性如果多了,会麻痹大家对测试失败的认知,导致团队成员忽略一些失败用例。这是也是 flakey test 的问题。

  • 2.每次重构都要增加维护测试的开销,这违背了自动化测试的初衷。内网上已经有开发在讨论做测试带来的“额外”负担该由谁承担之类的问题,过大的负担会阻碍团队成员对自动化测试的接纳。当开发者把写用例当作一种不情愿的任务来完成时,自动化测试的质量想必不会太好。

  • 3.支撑重构做得不好往往意味着用例检验了功能以外的东西,这其实在某种意义也是一种断言不当,它反映了用例作者对测试的误解,暗示着用例还可能有其他的问题。
    支撑重构做得不好的重要信号是过度依赖 mock,特别是那些在 mock 规则中还增加了调用顺序校验的。这也是为什么在各个场合中,一些测试布道者偏爱基于输入输出的测试(classical testing),而不是基于 mock 的测试(mockist testing)。但是有时候,团队流水线对覆盖率有硬性质量红线,使用 classical testing 无法达到那么高的覆盖率,我们不得不做一些 mock 来通过质量红线。因此应该尽量避免设置过高的质量红线,而应该培养团队成员的 testing sense, 程序员的价值就在这里。

先更新到这里,下篇继续~
优测测试平台简介:
是一个为企业与开发者提供专业的测试工具和服务的平台,沉淀十年产品测试经验,提供终端测试、接口测试、性能测试、安全测试等多领域测试服务与产品,协助客户提高效率降低成本,保证产品质量。
优测云测试平台 | 有效的单元测试,单元测试,测试工具,集成测试
优测云测试平台 | 有效的单元测试,单元测试,测试工具,集成测试文章来源地址https://www.toymoban.com/news/detail-777182.html

到了这里,关于优测云测试平台 | 有效的单元测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Tessy—嵌入式软件单元测试/集成测试工具

    产品概述 Tessy源自戴姆勒—奔驰公司的软件技术实验室,由德国Hitex公司负责销售及技术的支持服务,是一款专门针对嵌入式软件进行单元/集成测试的工具。它可以对C/C++代码进行单元、集成测试,可以自动化搭建测试环境、执行测试、评估测试结果并生成测试报告,其多样

    2024年01月18日
    浏览(56)
  • 单元测试工具——JUnit的使用

    ⭐️ 前言 ⭐️ 本篇文章主要介绍单元测试工具JUnit的使用。 🍉 欢迎点赞 👍 收藏 ⭐ 留言评论 📝 私信必回哟 😁 🍉 博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言 🍉 博客中涉及源码及博主日常练习代码均已上传GitHub JUnit提供了非常强大的注解功能

    2024年02月02日
    浏览(46)
  • Tessy — 嵌入式软件单元测试/ 集成测试工具学习

    Tessy — 嵌入式软件单元测试/ 集成测试工具 本文章向大家介绍Tessy — 嵌入式软件单元测试/ 集成测试工具,主要包括Tessy — 嵌入式软件单元测试/ 集成测试工具使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。 Tessy 源

    2024年02月04日
    浏览(68)
  • 嵌入式单元测试工具Tessy的一些测试技巧

    最近做了一个平台项目,需要进行动态代码测试,入门了嵌入式单元测试工具Tessy,总结了一些简单的测试技巧。 当前网上的教程普遍只写内容概要,真正入手还得自己认真摸索一番。为此,特意总结了一些Tessy测试技巧以供有缘人参考。 提几个Tessy工具使用的问题。 1.如何

    2023年04月17日
    浏览(61)
  • AI测试工具的有效性探索 与实践 (一)

    随着软件系统复杂性的增加,有效测试⽅法的需求也在上升。传统的测试⽤例⽣成⽅法通常⽆法满⾜快速开发周期和复杂代码库的需求 开发人员使用生成式人工智能可以以两倍的速度完成编码任务,这可能意味着生成的代码量将会相应增加。这意味着 QA 工程师必须提高测试

    2024年04月25日
    浏览(36)
  • 单元测试之- mock工具mockito

     常用的mock工具mockito 在编写单元测试时,需要mock依赖的对象,减少依赖对象对测试的影响,Mocktio是常用的mock工具之一,那么mockito提供了哪些功能呢? Mock对象的创建和配置:Mockito可以通过简单的语法创建mock对象,并允许你配置mock对象的行为。 Mock对象的验证:Mockito提供

    2024年02月13日
    浏览(41)
  • Java单元测试AI工具-Diffblue Cover

    什么是Diffblue? Diffblue是一款基于人工智能和机器学习技术的自动化测试工具。它可以帮助开发人员快速、准确地发现代码中的错误和漏洞,并生成相应的测试用例。Diffblue的核心技术是人工智能和机器学习,它可以自动地分析代码,理解代码逻辑,并生成相应的测试用例。

    2024年02月08日
    浏览(40)
  • 单元测试自动生成工具Evosuite的安装使用

    1.1介绍 官网: https://www.evosuite.org/ GitHub: https://github.com/EvoSuite 1.2安装插件 (1)直接安装 进入settigs中,点击plugins,搜索Evosuite,点击安装 (2)从官网下载安装 打开网页 https://plugins.jetbrains.com/plugin/18956-evosuite-xenoamess-tpm-/versions ,点击Download按钮下载插件。 选择菜单File

    2024年02月03日
    浏览(50)
  • copilot 产生 python工具函数并生成单元测试

    stock.py 这个文件,我只写了注释(的开头),大部分注释内容和函数都是copilot 再新建一个 stock_test.py,写一行注释,Ctrl+L(或者调用 Open compiletion Panel)产生了很多的单元测试样例。 再装一个 Python Test Explorer 插件用来进行 GUI test 附上我的 python test gui 配置 .vscode/settings.jon 有了

    2024年02月04日
    浏览(44)
  • 【Python百宝箱】Python测试工具大揭秘:从单元测试到Web自动化

    在现代软件开发中,测试是确保代码质量和稳定性的关键步骤。Python作为一门广泛应用的编程语言,拥有丰富的测试工具和库,从单元测试到Web自动化,覆盖了多个测试层面。本文将介绍一系列Python测试工具,帮助开发者选择适合项目需求的工具,提高代码的可靠性和可维护

    2024年02月03日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包