不为有趣之事,何遣有涯之生
不失其所者久,死而不亡者寿

测试驱动编程(1) 快速入门

java测试驱动编程简明教程

本篇教程基于junit5,不熟悉的人可以先去官网了解下:https://junit.org/junit5/ 当然比较推荐我个人整理的junit5的简明教程,可以帮你省下不少时间

测试驱动编程实战

我们的原始需求

开发一个“井”字游戏,双方在3X3的网格中画X和O,最先在水平、垂直、对角线上将自己3个标记连起来的玩家获胜

  • 初始用户故事:作为玩家,可以将棋子放在3X3棋盘上任何没有棋子的地方

故事验收:

  • 1 如果棋子放在超出X轴边界地方,引发RuntimeException;
  • 2 如果棋子放在超出Y轴边界地方,引发RuntimeException;
  • 3 如果棋子放在已经有棋子的地方,引发RuntimeException

开始红-绿-重构

  • 首先我们在业务模块创建一个井字游戏的实现类 JingGameService
  • 然后利用Idea工具创建对应的测试类(手动也可以,这里只是图个方便)

选择创建一个在每个测试方法之前都执行的方法

  • 添加一个初始化井字游戏实例的方法

  • 编写X轴越界的测试方法

注意,我们创建的井字游戏类还没有任何的方法,所以肯定会报错的,这个过程就是红-绿-重构过程中的阶段

  • 为了进入绿阶段我们实现play方法

  • 执行下该测试方法

我们看到测试通过了,这个过程就是红-绿-重构过程中的绿阶段

  • 我们还可以查看下Junit5自动生成的测试报告

  • 此时我们的代码比较简单也没有重构的必要,继续完成其他的验收测试

  • Y轴的异常逻辑和X的如同一辙,我们马上就可以实现


执行下整个类的测试,发现都是绿的,此时我们又经过了一次红-绿-重构的过程(重构没做?因为目前还不用嘛)

  • 好了,只剩下最后一个验收测试了:确定棋子在棋盘内,并且该位置是没有棋子的
  • 先完成位置冲突的测试用例

执行下,不出意外的报错了:

  • 接下来进入绿阶段,进行业务代码的编写

执行下整个测试类,全部通过:

  • 此时的代码其实已经可以重构了,因为我们的判断语句比较多,不能很好的表达业务含义,并且这些判断可能其它地方也会用到,应该抽取为不同的方法,用明确的含义表示出来该步骤的意图

下面是一个代码重构的参考:

重构完记得重新执行下测试类哦

PS:使用Idea重构很方便,只要二步
第一步:选中要重构的代码

第二步:起个方法名和选择需要的参数

  • 好了到目前为止,需求1的用户故事结束了

进入需求2

我们得加入处理玩家落子的规则,验收测试如下:

  • 白子先下
  • 如果上一次是白子,那么这次是黑子
  • 如果上一次是黑子,那么这次是白子

我们开始红-绿-重构的过程:
先编写测试方法,当白子先下,下一个玩家应该是白,我们用W表示白子,B表示黑子

的过程完成了,接下来是绿的过程,编写业务方法

我们只要编写一行代码就能实现了,然后继续完成白方黑方交替下的过程
编写白方完成后轮到黑方测试方法:

修改刚才的nextPalyer方法,将它变绿:


因为我们修改了nextPalyer方法,之前的用例也要重新跑一次,保证一直是绿的
那么我们需要编写黑方下完后应该是白方的逻辑吗?如果你写完测试方法,发现业务代码啥也不用动就能正确通过了,那么说明这个测试是多余的,我们应该删除掉
OK,我们又完成了第二个需求

进入需求3

终于进入该游戏的关键规则环节了:如何获胜?最先在水平、垂直、对角线上将自己3个标记连起来的玩家获胜
我们需要检查三个方向:水平,垂直,对角线,当没有分出胜负时,我们也要返回没有赢家,先来完成这个

看来我们又要重构/修改play方法了

之前我们是使用0或1来表示是否有棋子的,现在不仅要知道某个位置上是否有棋子,还要知道棋子是谁下的,所以保存的时候直接保存是谁下的



继续编写垂直方向的

写了水平的,垂直的也就简单了

最后是对角线三连,注意对角线有二种可能

因为都只有唯一的一条线,判断起来也更加简单了

最后所有的用例跑一次看看

完美,所有的用例都通过了,需求3也完成了

进入需求4

最后我们要处理下平局,毕竟这个游戏的大多数结局可能是平局
平局只有一种可能,就是棋盘满了,所以最后的验收测试是,当棋盘满了,游戏显示为平局

进入阶段

进入绿阶段


最后看下测试结果:

好了,一个超级简单的井字游戏就告一个段落了,代码层面还有一些优化的地方,比如获胜的判断这块。当然这不是本教程的主题,我主要演示的是如何通过测试驱动来进行业务需求/用户故事的开发

我们可以清晰的体会到,我们在编写的时候没有一开始就写业务代码,而是分析需求的验收测试,然后将验收测试编程Junit5的测试代码,当然这个过程中,代码肯定是会报错的,报错的原因有二类:

  • 编译错误:缺少必要的方法和参数
  • 运行错误:逻辑上的不满足

我们要做的事情就是把不通过的测试用例都变绿,如此反复进行。当我们发现代码臃肿难以阅读时,就应该及时的进行重构,我们可以放心的进行重构,因为有之前的测试为我们检验重构后的结果,让我们不用过于担心代码重构的不可控。

测试覆盖率

最后我们看下Junit5提供的测试覆盖率功能:

在Idea的右上角可以看到下图所示,类、方法、代码行的测试覆盖率情况,让我们做到心里有数,有多少代码或方法是测试过的

一些忠告

  • 测试方法的名称:使用given(前置条件,一般如果有公共的前置,方法中可省略)/when(描述操作)/then(描述期望的结果)
  • 红-绿-重构的过程:保证短节奏式的交替进行

最后的话

本文讲述的只是一个非常简单的测试驱动需求例子,现实情况中需求比较复杂。我们需要转变的是我们开发系统的思想,一开始可能你还不是很熟练,但是随着练习次数,你将会喜欢上这种方法。就像当初学习DDD的时候,上手比较困难,各种概念难以理解,但是一旦你上手后,你再也不想回到以前那种贫血失血模型的开发中去了。

如果你立志成为一个优秀的工程师,那么测试驱动编程是你一定要掌握好的技能。

PS:本教程的源码分享在了 https://gitee.com/hzqiuxm/tdd-demo-lessons.git 的jingame模块

未经允许不得转载:菡萏如佳人 » 测试驱动编程(1)

欢迎加入极客江湖

进入江湖关于作者