技术经验分享:谈一谈单元测试(1)

发表于:2022-2-17 09:32

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

 作者:有尘    来源:阿里技术

  写在前面
  对于我们开发人员来说,单元测试一定不会陌生,但在各种原因下会被忽视,尤其是在我接触到的项目中,提测阶段发现各种各样的问题,我觉得有必要聊一下单元测试。
  为了写而写的单元测试没什么价值,但一个好的单元测试带来的收益是非常客观的。问题是怎么去写好单元测试?怎么去驱动写好单元测试?
  一 、我们的现状
  现状一:多个项目完全没有单元测试。
  现状二:开发人员没有写单元测试的习惯,或者由于赶业务记录而没有时间去写。
  现状三:单元测试写成了集成测试,比如容器、数据库,导致单元测试运行时间长,失去了意义。
  现状四:太依赖集成测试。
  以上是我在aone找的两个项目的测试情况,基本不考虑单元测试就合并发布,形同虚设。
  站在开发的角度讲,导致以上问题的原因大概有以下几点:
  开发成本
  对于系统初期,可能要花很多时间去写新业务,对于老系统又太过庞大,无法下手。
  维护成本
  每修改相关的类,或者重构一次代码,我们就要去修改相应的单元测试。
  ROI
  投入产出是不是正收益?可能无论是管理者还是我们开发自己都回质疑这个问题,所以有时候没有强有力的动力。
  二、 怎么解决
  说来说去都是成本的问题,所以我们怎么去解决成本呢?
  那么,我们一切从最开始说起:开发的成本
  一个单元测试的传统写法,包含以下几个方面:
  ·测试数据 (被测数据,和依赖对象)
  · 测试方法
  · 返回值断言

  @Test
    public void testAddGroup() {
      // 数据
      BuyerGroupDTO groupDTO = new BuyerGroupDTO();
      groupDTO.setGmtCreate(new Date());
      groupDTO.setGmtModified(new Date());
      groupDTO.setName("中国");
      groupDTO.setCustomerId(customerId);
      // 方法
      Result<Long> result = customerBuyerDomainService.addBuyerGroup(groupDTO);
      // 返回值断言
      Assert.assertTrue(result.isSuccess());
      Assert.assertNotNull(result.getData());
    }
 
  一个简单的测试还好,但如果是一逻辑复杂,且入参数据复杂的时候,那写起来其实挺头痛的。怎么解放我们程序员的双手?
  “工欲善其事必先利其器”
  我们以最大的努力降低我们的开发成本,这就涉及到我们测试框架和工具的选择问题
  1. 测试框架选择
  首先第一个问题就是junit4和junit5的选择,【从junit4到junit5】 我觉得最便利的一个好处就是可以参数化测试,并且基于参数化测试我们可以更加灵活的配置我们的参数。
  效果如下:
  @ParameterizedTest
  @ValueSource(strings = { "racecar", "radar", "able was I ere I saw elba" })
  void palindromes(String candidate) {
      assertTrue(StringUtils.isPalindrome(candidate));
  }

  更好的是,junit5提供了扩展,比如我们常用的json格式。这里我们使用json文件作为输入:
  @ParameterizedTest
    @JsonFileSource(resources = {"/com/cq/common/KMPAlgorithm/test.json"})
     public void test2Test(JSONObject arg) {
      Animal animal = JSONObject.parseObject(arg.getString("Animal"),Animal.class); 
      List<String> stringList = JSONObject.parseArray(arg.getString("List<String>"),String.class); 
      when(testService.testOther(any(Student.class))).thenReturn(stringArg);
      when(testService.testMuti(any(List.class),any(Integer.class))).thenReturn(stringList);
      when(testService.getAnimal(any(Integer.class))).thenReturn(animal);
      String result = kMPAlgorithm.test2();
      //todo verify the result
    }

  2. mock框架
  然后就是其他mock类的框架了。
  Mockito: 语法特别优雅,对于容器类的模拟比较合适,且对于返回值为空的函数调用也提供比较好的断言。缺点是不能模拟静态方法(3.4.x以上版本已支持)
  EasyMock: 使用方法类似,但是更严格。
  PowerMock: 可以作为Mockito的一个补充,比如要测试静态方法,不过不支持junit5。
  Spock: 基于Groovy语言的单元测试框架。
  3. 数据库层
  这里主要介绍一下H2数据库,其基于内存来作为对于关系型数据库的模拟,运行完成自动释放,达到隔离的目的。
  主要配置:ddl文件路径、dml文件路径。这里不作详述。
  但对于要不要集成数据库,很难去定义,它的作用主要是用来验证sql语法的问题,但是相对来说较重,建议可以用于轻量级的集成测试。

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

关注51Testing

联系我们

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

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

沪ICP备05003035号

沪公网安备 31010102002173号