单元测试的一些分享

背景

最近在给一个客户作技术咨询,而后发现了客户对于单元测试的一个有意思的现象。分享出来,你们一块儿学习探讨一下。html

现状分析

这里以java后端项目例,发现客户写的测试长下面的样子。(代码已经脱敏处理过。)java

@Autowired
    private SampleJob handler;

    @Test
    public void testStart() throws Exception {
        SampleParamVo paramVo = new SampleParamVo();
        paramVo.setStartTime("2021-03-18");
        paramVo.setEndTime("2021-03-18");
        handler.execute(paramVo);
    }
@Autowired
    private SampleHandler handler;

    @Test
    public void testHandler() {
        handler.doHandler(new DateTime("2021-11-26"), null);
    }

那么这样的测试代码有什么问题呢?编程

  1. 别人看不懂这个测试是在作什么。首先测试的方法名没有任何意义,其次测试代码也只是调用了某个函数。
  2. 没法运行。这类测试代码运行每每须要启动其余服务或者须要一些特殊的设置。没法运行就意味着它不能成为CI跑测试的一部分。
  3. 没有断言。没有断言就没法知道测试的代码的正确性。
  4. 使用了@Autowired这样的代码,增长了测试的耦合以及编写成本。

和客户深聊了以后发现,原来客户不一样的人对单元测试的理解也不同。后端

  • 写这个代码的开发人员说,“这些代码是在开发完成以后作一些自测的辅助脚本。”
  • 有的开发人员说,“咱们是微服务,单元测试须要调用其余服务,写起来很麻烦,并且若是其余服务不可用时,测试也跑不过。”
  • 测试人员说:“单元测试咱们有的,我天天都在写测试用例,到单元测试的时候我就会把个人用例所有过一遍。”

因此咱们能够发现,有的开发人员口中的单元测试其实应该属于集成测试或者E2E测试,有的开发人员彻底没有写过单元测试,而测试人员理解单元测试是本身手动测试的时候用的测试用例。框架

那咱们就先来讲说什么是单元测试。ide

什么是单元测试?

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。

一般在java的世界里面,单元测试就是指对一个public的方法编写检查和验证的代码。函数

为何要写单元测试?

写单元测试主要有两大目的:微服务

  1. 验证功能实现。
  2. 保护已有功能不被破坏。

当咱们写完一个方法,咱们如何知道本身写的方法是定期望工做的呢?这个时候就能够添加单元测试来验证咱们的代码是定期望工做的。即当咱们给定指定的输入,咱们得到指望的输出,则咱们说这个功能是符合指望的。单元测试

其次,代码不是写了就永远不变的,当需求变动时,新增需求时,修复bug时,都会修改代码,而单元测试则能保护咱们已有的功能不被破坏。保护已有功能不会被本身破坏,被新人破坏,被新功能破坏。学习

如何写单元测试?

下面是一个单元测试的例子

@Test
    public void should_return_fizz_given_input_can_be_divided_by_3() {
        FizzBuzz fizzBuzz = new FizzBuzz(); // Given
        String actual = fizzBuzz.sayIt(6); // When
        Assertions.assertEquals("Fizz", actual); // Then
    }

一个标准的单元测试包含如下几个部分:

  1. 能描述清楚作了什么的测试名(方法名)
  2. 单元测试的Given、When、Then具体内容。

    1. Given:初始状态或前置条件
    2. When:行为发生
    3. Then:断言结果

写好单元测试要主要几个要点:

  • 由于测试代码并不会进入生产环境,同时咱们指望测试即文档,所以测试的名称写很长也没有关系,重要的是能清晰的表达咱们这个测试所覆盖的用例是什么。
  • 一个测试只测一种case。
  • 单元测试一般须要覆盖大量的case来保证咱们的代码在绝大多数场景下都是定期望工做的。所以要作到这一点能够参考下面两大原则。这里就不详细讲解这两个原则,具体内容能够Google。

    • CORRECT原则
    • Right-BICEP原则
  • 单元测试有一个考核的标准就是测试覆盖率,指的是咱们的代码有百分之多少被单元测试测到了。

    • 测试覆盖率分几种:行覆盖率,分支覆盖率,路径覆盖率,条件覆盖率等。每种均可以单独设置百分比。一般咱们会看中行覆盖率和分支覆盖率。
    • 一般行业里面常设置测试覆盖率在85%以上。
    • 为何不是100%?由于不是全部代码都能被测到的,好比private的构造函数是没法被测到的,这种就会下降覆盖率。
  • 一般全部的自动化测试都是开发人员来写,好比单元测试,集成测试等。

测试金字塔

说到单元测试,就不得不提测试金字塔,以下图,最底层是单元测试,最顶层是UI测试。(测试金字塔有好几种,但道理都是相通的)

看左边的箭头,越往下越快,越往上越慢,它主要包括编写越快,运行越快,定位问题越快等。

看右边的箭头,越往下成本越低,越往上成本越高,包括时间成本,金钱成本,人员成本,维护成本等。

测试金字塔

什么是mock?

咱们在作单元测试的时候,经常可能访问外部系统或者外部类,这些外部的不可控性会让咱们的单元测试成本变得很高。

常见的外部不可控性有:HTTP访问,增删文件,随机性,时间相关性,接口类等。

因而开发者便开始探索更廉价的方式来写单元测试,mock就是其中的解决方案。

mock 对象运行在本地彻底可控环境内,利用 mock 对象模拟被依赖的资源,使开发者能够轻易的建立一个稳定的测试环境。

mock是Test double理论中的一种,若是对test double理论感兴趣,能够到这里了解更多,这里就不展开说了。

如何用mock?

仍是以java为例,java的世界中经常使用的mock框架好比mockito。

下面是一个mock的例子。

@Test
    void should_return_100_when_get_list_size() {
        List map = mock(List.class);
        //当调用list.size()方法时候,返回100
        when(map.size()).thenReturn(100);
        Assert.assertEquals(100, map.size());
    }

单元测试是咱们测试的最小单位,所以咱们只测当前这个public的方法中的实现,而方法中调用第三方类的东西,咱们都应该mock掉。

这样的好处有两个:

  1. 不会由于其余类的不可控性而致使这个测试方法变得难写。
  2. 其余类的修改不会致使这个测试方法挂掉。全部的变化都被隔离出去了。

什么是TDD?

最后再升华一下,简单说一说TDD,TDD的全称是Test driven development,即测试驱动开发。它是极限编程XP中的一个标准实践。

TDD要求在编写某个功能的代码以前先编写测试代码,而后只编写使测试经过的功能代码,经过测试来推进整个开发的进行。

这样作有四大好处:

  1. TDD是一个很好的契机,可让你在考虑解决方案以前先考虑问题。
  2. 首先考虑测试会迫使你首先考虑与代码的接口。先思考接口能够帮助你将接口与实现分开。
  3. 简单设计。
  4. 几乎100%的测试覆盖率。

这里我就不详细叙述TDD相关的话题了,由于TDD是一个比较大的话题,若是感兴趣,下次专门开一个新话题来聊TDD。

相关文章
相关标签/搜索