乐趣区

关于测试驱动开发:单元测试的一些分享

背景

最近在给一个客户做技术咨询,而后发现了客户对于单元测试的一个有意思的景象。分享进去,大家一起学习探讨一下。

现状剖析

这里以 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 测试,有的开发人员齐全没有写过单元测试,而测试人员了解单元测试是本人手动测试的时候用的测试用例。

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

什么是单元测试?

单元测试(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。

退出移动版