java搭建单元测试框架_基于Springboot+Junit+Mockito做单元测试的示例

前言

这篇文章介绍如何使用springboot+junit+mockito做单元测试,案例选取撮合交易的一个类来做单元测试。

单元测试前先理解需求

要写出好的单测,必须先理解了需求,只有知道做什么才能知道怎么测。但本文主要讲mockito的用法,无需关注具体需求。所以本节略去具体的需求描述。

隔离外部依赖

case1. 被测类中被@autowired 或 @resource 注解标注的依赖对象,如何控制其返回值

以被测方法 matchingserviceimpl.java的matching(matchingorder buyorder, matchingorder sellorder)为例

被测类matchingserviceimpl

public class matchingserviceimpl implements matchingservice {

private static final logger log = loggerfactory.getlogger(matchingserviceimpl.class);

@autowired

private quoteservice quoteservice;

...

public matchingresult matching(matchingorder buyorder, matchingorder sellorder) {

int currentprice = quoteservice.getcurrentpricebyproduct(buyorder.getproductcode());

matchingresult result = new matchingresult();

if (sellorder != null && buyorder != null &&

sellorder.getprice() <= buyorder.getprice()) {

...

}

}

matching方法中的quoteservice.getcurrentpricebyproduct(buyorder.getproductcode());要访问redis获取当前报价,这里我们需要把外部依赖quoteservice mock掉,控制getcurrentpricebyproduct方法的返回值。使用mockito可以做到,具体如下:

测试类matchingserviceimpltest

public class matchingserviceimpltest extends mockitobasedtest {

/**

* 被@mock标注的对象会自动注入到被@injectmocks标注的对象中

*/

@mock

private quoteservice quoteservice;

/**

*

 
 

* 被测对象,用@injectmocks标注,那些被@mock标注的对象就会自动注入其中。

* 另一个注意点是这里的matchingserviceimpl是直接new出来(mockito 1.9版本后不new也可以),而不是通过spring容器注入的。因为这里我不需要从spring容器中

* 获得其他依赖,不需要database ,redis ,zookeeper,mq,啥都不依赖,所以直接new

*

*/

@injectmocks

private matchingserviceimpl matchingservice = new matchingserviceimpl();

@test

public void testmatching_successwhencurrentpricebetweenbuypriceandsellprice() {

matchingorder buyorder = new matchingorder();

buyorder.setprice(1000);

buyorder.setcount(23);

matchingorder sellorder = new matchingorder();

sellorder.setprice(800);

sellorder.setcount(20);

// 方法打桩(method stubbing)

// when(x).thenreturn(y) :当指定方法被调用时返回指定值

mockito.when(quoteservice.getcurrentpricebyproduct(mockito.anystring())).thenreturn(900);

matchingresult result = matchingservice.matching(buyorder, sellorder);

org.junit.assert.assertequals(true, result.issuccess());// 断言撮合是否成功

org.junit.assert.assertequals(20, result.gettradecount());// 断言成交数量

org.junit.assert.assertequals(900, result.gettradeprice()); // 断言最新报价是否符合预期

}

case2. 被测函数a调用被测类其他函数b,怎么控制函数b的返回值?

比如,matchingserviceimpl中有个函数startbuyprocess,它里面调用了该类中的其他函数,如gettopsellorder,matching,如何控制这两个函数的返回值?

这里要解决的问题其实是怎么对一个类”部分mock”–被测类的被测方法(如startbuyprocess)要真实执行,而另一些方法(如gettopsellorder)则是要打桩(不真正进去执行)。

被测类matchingserviceimpl

protected void startbuyprocess(matchingorder buyorder, boolean waitformatching) {

while (true) {

//对手方最优价

matchingorder topsellorder = gettopsellorder(buyorder.getproductcode());

matchingresult matchingresult = matching(buyorder,topsellorder);

if(matchingresult.issuccess()) {

domatchingsuccess(buyorder,topsellorder,matchingresult,matchingtype.buy);

if(buyorder.getcount() <= 0) {

break;

}

}else {

if(waitformatching) {

//加入待撮合队列

addtomatchingbuy(buyorder);

}else {

//撤单

sendcanclemsg(buyorder);

}

break;

}

}

}

利用mockito.spy()可以做到“部分mock”

测试类matchingserviceimpltest.teststartbuyprocess_incaseofmatchingsuccess

/**

*

* 测试startbuyprocess方法在撮合成功后的处理是否符合预期,即测试startbuyprocess方法进入下面这个判断分支后的行为

* {@link matchingserviceimpl#startbuyprocess(matchingorder, boolean)}

*

*

 
 

* if (matchingresult.issuccess()) {

*

* domatchingsuccess(buyorder, topsellorder, matchingresult, matchingtype.buy);

*

* if (buyorder.getcount() <= 0) {

* break;

* }

* }

*

*

*/

@test

public void teststartbuyprocess_incaseofmatchingsuccess() {

matchingorder buyorder = new matchingorder();

buyorder.setprice(700);

buyorder.setcount(23);

// 用mockito.spy()对matchingservice进行部分打桩

matchingservice = mockito.spy(matchingservice);

matchingresult firstmatchingresult = new matchingresult();

firstmatchingresult.setsuccess(true);

firstmatchingresult.settradecount(20);

matchingresult secondmatchingresult = new matchingresult();

secondmatchingresult.setsuccess(false);

// doreturn(x).when(obj).method() 对方法打桩,打桩后,程序执行这些方法时将按照预期返回指定值,未被打桩的方法将真实执行

// 两个doreturn表示第一次调用matchingservice.matching时返回firstmatchingresult,第二次调用返回secondmatchingresult

// 因为startbuyprocess里有个while循坏,可能会多次执行matching方法

mockito.doreturn(firstmatchingresult).doreturn(secondmatchingresult).when(matchingservice)

.matching(mockito.any(matchingorder.class), mockito.any(matchingorder.class));

matchingorder sellorder = new matchingorder();

sellorder.setprice(600);

sellorder.setcount(20);

// 对gettopsellorder方法打桩

mockito.doreturn(sellorder).when(matchingservice).gettopsellorder(mockito.anystring());

// 对外部依赖jedis的方法进行打桩

mockito.when(jedisclient.incrby(mockito.anystring(), mockito.anylong())).thenreturn(0l);

// startbuyprocess是被测函数,不打桩,会真实执行

matchingservice.startbuyprocess(buyorder, true);

// 后面的校验和断言是测试domatchingsuccess方法的行为的,这也是这个测试的目的

// verify可用来校验,某个类的方法被执行过多少次,这里是校验jedisclient.zremfirst是否被执行过1次

mockito.verify(jedisclient, mockito.times(1)).zremfirst(mockito.anystring());

org.junit.assert.assertequals(3, buyorder.getcount());

org.junit.assert.assertequals(0, sellorder.getcount());

}

spy的用法已经演示完毕,下面从teststartbuyprocess_incaseofmatchingsuccess说下单元测试的“粒度”。

teststartbuyprocess_incaseofmatchingsuccess的目的是想测domatchingsuccess,我们费了很大劲才把前面的一堆准备工作做完,才能去测domatchingsuccess。

更好的实践应该是另起测试方法去单独测domatchingsuccess,关注点也集中很多,domatchingsuccess覆盖完了,再测startbuyprocess其实就只是覆盖下它本身的判断分支就行了。覆盖率照样达到,而且测试代码也更容易维护,teststartbuyprocess_incaseofmatchingsuccess由于考虑的职责太多,它很容易受到变化的影响,细小的东西改变,可能就会影响它的正常工作。

引入测试框架maven依赖

junit

junit

4.11

test

org.mockito

mockito-all

1.10.19

test

org.springframework

spring-test

4.2.5.release

test

springboot+junit+mockito的上下文构建

mockitobasedtest

@runwith(springjunit4classrunner.class)

@springapplicationconfiguration(classes = testapplication.class)

public abstract class mockitobasedtest {

@before

public void setup() throws exception {

// 初始化测试用例类中由mockito的注解标注的所有模拟对象

mockitoannotations.initmocks(this);

}

}

// 其他测试类继承mockitobasedtest

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持萬仟网。

希望与广大网友互动??

点此进行留言吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"基于springboot框架的网上商城系统的设计与实现.zip"是一个压缩文件,它通常包含了一套完整的网上商城系统的源代码及相关资源。这个系统是基于Spring Boot框架开发的,Spring Boot是一个开源的Java基础框架,它简化了基于Spring的应用程序的开发过程,使得开发者能够快速搭建和部署微服务或Web应用。该资源包中可能包含以下几个主要部分:项目源码:包括了整个网上商城系统后端的所有Java源代码,这些代码遵循MVC(模型-视图-控制器)设计模式,将业务逻辑、数据模型和用户界面分离,以提高代码的可维护性和扩展性。数据库脚本:可能包含了用于创建和初始化数据库的SQL脚本。这些脚本定义了系统中使用的数据表结构、存储过程以及初始数据的插入。配置文件:Spring Boot项目通常会有application.properties或者application.yml文件,用于配置项目的各种参数,如服务器端口、数据库连接信息、缓存设置等。静态资源:包括CSS样式表、JavaScript脚本、图片和字体文件等,这些资源用于构建前端用户界面,提供良好的用户体验。文档说明:可能会包含README.md或其他文档文件,对项目的使用方法、功能特性、开发环境要求等进行详细说明。第三方库:项目可能会依赖一些第三方库,如数据库驱动、安全框架、日志组件等,这些库通常通过Maven或Gradle进行管理。测试用例:可能会有一些JUnit或Spring Boot Test的测试用例,用于验证系统的功能和性能。部署说明:虽然不包含安装步骤,但可能会提供如何将应用部署到服务器上的简要指南或建议。这个资源包是为了帮助开发者快速搭建一个基于Spring Boot的网上商城系统,无论是用于学习目的还是实际项目开发,都能节省大量的时间。由于Spring Boot的自动配置特性,开发者可以更加专注于业务逻辑的实现,而不必过多关心底层的配置和依赖管理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值