毕业设计做网站还是系统,公司如何注册企业邮箱,中国建筑装饰网设计师联盟,seo推广具体做什么文章目录 测试驱动编程(4)模拟消除依赖模拟框架Mockito什么要模拟名词解释Mockito常用注解Mockito常用静态方法Mockito测试流程三部曲基础用法可变返回结果验证verfily对象监视spy 示例实战升级版井字游戏需求一需求二需求三 总结 测试驱动编程(4)模拟消除依赖
模拟框架Mockit… 文章目录 测试驱动编程(4)模拟消除依赖模拟框架Mockito什么要模拟名词解释Mockito常用注解Mockito常用静态方法Mockito测试流程三部曲基础用法可变返回结果验证verfily对象监视spy 示例实战升级版井字游戏需求一需求二需求三 总结 测试驱动编程(4)模拟消除依赖
模拟框架Mockito
大道至简
什么要模拟
单元测试的要点就在于验证单个单元是否正常而不考虑依赖TDD中的单元测试尤其如此。 对于内部依赖我们应该已对其进行测试过对于外部依赖(JDK包)我们应该信任它们 消除依赖的两种手段合理的设计和模拟实现合理的设计与具体的业务有关本次只介绍模拟实现如何去实践 下图是一个实际的关系我们的目标就是通过模拟简化关系方便测试
名词解释
测试替身的其它名字哑元对象(dummy object)、测试存根(test stub)、测试间谍(test spy)、模拟对象(mock object)、伪造对象(fake object)
Mockito常用注解
使用的模拟框架是Mockito它的常用注解如下
Mock用于模拟的创建使得测试类更具可读性不调用真实方式默认返回都是null需要配对ExtendWith(MockitoExtension.class)才能使用Spy用于创建间谍实例代替spy(Object)方法(调用真实方式)InjectMocks用于自动实例化测试对象并将所有的Mock或Spy注解字段依赖项注入其中类似Spring框架中自动注入Captor用于创建参数捕获器
要处理所有上述注释请MockitoAnnotations.initMocks(testClass); 必须至少使用一次。 要处理注释我们可以使用内置的运行器MockitoJUnitRunner或规则MockitoRule 。 我们还可以在Before注释的Junit方法中显式调用initMocks()方法。
Mockito常用静态方法
除了使用注解我们还需要用到是它的三个主要静态方法
mock()创建模拟对象还可以使用when()和given()指定模拟行为spy()实现部分模拟调用实际的对象verify()检查调用方法时提供的参数是否是指定参数是一种断言
Mockito测试流程三部曲 模拟mock一个模拟对象 模拟一个List对象它会给所有方法添加基本实现返回值和由方法的返回类型决定如 int 会返回 0布尔值返回 false。对于其他 type 会返回 null 打桩Stub打桩设置预期 指定条件和预期返回 验证验证预期和实际值是否一致
基础用法 无返回值使用notify 监视对象 抛出异常 模拟传入参数 Mockito 提供 argument matchers 机制例如 anyString() 匹配任何 String 参数anyInt() 匹配任何 int 参数anySet() 匹配任何 Setany() 则意味着参数为任意值。自定义类型也可以如 any(User.class)
可变返回结果
之前我们thenReturn 是返回结果都是写死的如果要让被测试的方法不写死返回实际结果并让我们可以获取到应该怎么做呢 利用 InvocationOnMock 提供的方法可以获取 mock 方法的调用信息。下面是它提供的方法
getArguments() 调用后会以 Object 数组的方式返回 mock 方法调用的参数getMethod() 返回 java.lang.reflect.Method 对象getMock() 返回 mock 对象callRealMethod() 真实方法调用如果 mock 的是接口它将会抛出异常
验证verfily
由程序员自己来决定验证结果可以关注调用参数、返回结果、调用次数(times(0)) verify 也可以像 when 那样使用模拟参数若方法中的某一个参数使用了matcher则所有的参数都必须使用 matcher 在最后的验证时如果只输入字符串”hello”是会报错的必须使用 Matchers 类内建的 eq 方法
对象监视spy
spy 的意思是你可以修改某个真实对象的某些方法的行为特征而不改变他的基本行为特征 spy 保留了 list 的大部分功能只是将它的 size() 方法改写了。不过 spy 在使用的时候有很多地方需要注意一不小心就会导致问题所以不到万不得已还是不要用 spy
示例实战
升级版井字游戏
“井字游戏”第二版的需求很简单添加永久性存储让玩家能够保存游戏的当前状态以便以后接着玩。
需求一
作为玩家我希望把当前下的棋能够保存起来以便于我能看到下的历史记录 需求分析需要保存的信息有轮次、X和Y坐标以及玩家 我这边打算以mongoDB数据库来进行持久化保存选用什么数据库和测试驱动没什么关系 准备好依赖和环境配置
-- 依赖
implementation org.springframework.boot:spring-boot-starter-data-mongodb
-- 配置
spring:data:mongodb:host: 192.168.3.112port: 27117database: tic-tac我们的数据库叫tic-tac并打算将数据保存在一个叫game的mongoDB集合中接下来设计一个持久化类用来保存游戏的相关数据 (示例项目源码链接将在文末给出大家不用太关注技术细节关注在功能和测试模拟上)
一般刚接触单元测试的开发人员的第一反应就是先初始化一个数据在数据库中然后测试下能不能查到类似下面代码
上面是这些spring的相关配置和资源库自动注入大家不用关心和具体业务无关 1处表示构建一个游戏记录然后将它保存到数据库中 2处表示根据唯一编号从数据库中取出游戏保存记录检验下唯一编号是否对应的上
初看起来好像这个测试并没有什么问题但是其实这里存在至少四个大问题1 构建一个游戏记录本身就需要操作数据库而且运行每个测试方法都要构建很多数据库会导致主键冲突2 构建记录本身需要依赖具体的数据库需要配置一大堆额外的东西数据库驱动、获取实例、进行连接、释放资源等等3 万一有人修改了数据库的数据测试用例将失败你每次测试的时候得告诉别人别动我的数据4 因为设计到了数据库操作要时刻保证你的数据库运行正常因为测试用例往往很多所以你还要忍耐相当长时间的数据库操作本来单元测试都是在内存中快速运行的
等等我们的目的就是要测试下井字游戏的逻辑现在怎么好像变得在测试数据库了数据库作为外部依赖我们原则上应该信任它才对呀所以我们需要使用mockit来模拟数据库操作
所以我们如果只是测试下的一步棋被保存了其实就是调用了资源库的保存方法即可我们默认是信任资源库能保存成功的
模拟的对象的方式有二种一种是使用注解一种是使用静态方法 上面是静态方法方式 上面是注解的方式,需要在测试类上添加ExtendWith(MockitoExtension.class)
井字游戏涉及到的几个类职责如下
TicTacToeBean存储游戏状态信息的实体类TicTacService提供存储服务与资源库进行协作TicTacRepository对实体类数据进行持久化
下面是一个保存一步棋的测试示例采用了注解的方式
注意可以看到我们的测试类并没有使用SpringBootTest注解单元测试已经脱离了Spring上下文环境
1处模拟了资源库和服务因为需要服务中需要调用资源库对数据库进行操作所以需要将资源库注入到服务中采用了InjectMocks注解 2处是构建一个某步棋的状态信息 3处是调用服务保存下棋的信息 4处是验证我们关心的是验证资源库的save方法是不是被调用了一次
因为还没有saveMove方法所以编译直接报错了这个是我们的的红阶段接着实现一个空方法让编译通过 执行下测试用例后发现报错了因为和预期的不一样 预期是要产生一次数据库的save操作结果实际上是0然后实现刚才添加的空方法让其变绿
跑下测试用了发现变绿了并查看下测试报告
我们目前只是测试了服务中的saveMove()方法其实资源库的方法我们也应该测试下由于项目中我们使用的基于SpringJpaData项目下的Spring Data MongoDB框架来进行操作数据库底层实现和默认的方法都是遵循JPA规范的不需要我们定义各种增删改查方法。
各种方法的返回值也确定比如之前的Save方法其接口如下 所以我们测试成功和失败的时候分别返回的是当前保存对象以及空对象示例代码如下
跑下测试用例验证我们的结果
我们发现其中需要的游戏状态信息是重复代码所以可以重构下重构完毕后记得验证下测试用例
需求二
刚才只是实现了其中一步游戏信息状态的保存在游戏过程中我们还需要保存每一步的信息并且每次重新开始的时候要清空数据库
在实施需求二之前我们先把第一版的测试用例加进来运行保证其正确性
经过分析大概有这么几个阶段
游戏开始时初始化一个全新的游戏状态信息对象同时删除原来的游戏记录
以上两个方法比较简单和之前的类似就直接给出代码了不一小步一小步的讲解了完成后一定要保证测试用例全部通过
每当玩家下一步就保存起来
运行测试用例发现报错了
因为游戏的逻辑中目前我们还没有调用保存经过分析我们应该在setBoard()中保存如果在play中编写第一版的大量测试用例需要重构。
目前该方法已经具备了保存的所有参数信息(不熟悉该方法的需要回顾下井字游戏的第一版本即该系列教程的第一篇)
当前的方法如下所示接收的是多个参数我们需要重构为一个TicTacBean
重构后如下
play方法中调用的地方也应该做相应的修改
运行所有的测试用例第一版游戏的用例和第二版游戏用例都全部通过大功告成
需求三
当玩家是继续游戏时读取当前游戏的状态然后继续
作为玩家我想保存当前游戏以便于我下次可以继续上次的游戏
需要保存的游戏信息棋盘以及最后的玩家我这边就先不给出步骤了请大家自行实现下
PS代码仓库 https://gitee.com/hzqiuxm/tdd-demo-lessons.git 的jinggame2模块
总结
单元测试的本质就是要限定好单元的边界如果单元测试的过程在为如何解析请求报文或数据库连接之类事情烦恼那么你的单元测试很有可能超过边界了
为了在边界内快速响应结果做出业务实现在涉及到数据库等第三方依赖的时候会使用模拟的方式来解决。Mockito是一个优秀的模拟框架在性能和灵活性上做到了很好的平衡值得大家去熟悉其常用的API和注解
一个模拟测试过程有三步1 模拟(对象)2 打桩(预期)3 验证(结果)然后结合红-绿-重构大法就是良好的TDD实践之路。 我们都希望自己写的代码持续的焕发出活力想持续焕发活力你必须为以后重构提供坚实的基础。
敏捷大行其道的今天TDD也是敏捷思想核心技术之环的一个重要环节没有技术实践的敏捷注定是失败的没有TDD为代表的测试驱动技术技术之环同样也不够完善。