老铁们,大家好,相信还有很多朋友对于Spock是什么意思和Spock单测的相关问题不太懂,没关系,今天就由我来为大家分享分享Spock是什么意思以及Spock单测的问题,文章篇幅可能偏长,希望可以帮助到大家,下面一起来看看吧!
在之前的关于swagger文章里提到过,程序员最讨厌的两件事,一件是别人不写文档,另一件就是自己写文档。这里如果把文档换成单元测试也同样成立。每个开发人员都明白单元测试的作用,也都知道代码覆盖率越高越好。高覆盖率的代码,相对来说出现BUG的概率就越低,在线上运行就越稳定,接的锅也就越少,就也不会害怕测试同事突然的关心。既然这么多好处,为什么还会讨厌他呢?至少在我看来,单测有如下几点让我喜欢不起来的理由。第一,要额外写很多很多的代码,一个高覆盖率的单测代码,往往比你要测试的,真正开发的业务代码要多,甚至是业务代码的好几倍。这让人觉得难以接受,你想想开发5分钟,单测2小时是什么样的心情。而且并不是单测写完就没事了,后面业务要是变更了,你所写的单测代码也要同步维护。第二,即使你有那个耐心去写单测,但是在当前这个拼速度挤时间的大环境下,会给你那么多写单测的时间吗?写一个单测的时间可以实现一个需求,你会如何去选?第三,写单测通常是一件很无趣的事,因为他比较死,主要目的就是为了验证,相比之下他更像是个体力活,没有真正写业务代码那种创造的成就感。写出来,验证不出bug很失落,白写了,验证出bug又感到自己是在打自己脸。
所以得到的结论就是不写单测?那么问题又来了,出来混迟早是要还的,上线出了问题,最终责任人是谁?不是提需求的产品、不是没发现问题的测试同学,他们顶多就是连带责任。最该负责的肯定是写这段代码的你。特别是对于那些从事金融、交易、电商等息息相关业务的开发人员,跟每行代码打交通的都是真金白银。每次明星搞事,微博就挂,已经被传为笑谈,毕竟只是娱乐相关,如果挂的是支付宝、微信,那用户就没有那么大的包容度了。这些业务如果出现严重问题,轻则扫地出门,然后整个职业生涯背负这个污点,重则直接从面向对象开发变成面向监狱开发。所以单元测试保护的不仅仅是程序,更保护的是写程序的你。最后得出了一个无可奈何的结论,单测是个让人又爱又恨的东西,是不想做但又不得不做的事情。虽然我们没办法改变要写单测这件事,但是我们可以改变怎么去写单元测试这件事。
当然,本文不是教你用旁门左道的方法提高代码覆盖率。而是通过一个神奇的框架spock去提高你编写单元测试的效率。spock这名称来源,个人猜测是因为《星际迷航》的同名人物(封面图)。那么spock是如何提高编写单测的效率呢?我觉得有以下几点:第一,他可以用更少的代码去实现单元测试,让你可以更加专注于去验证结果而不是写单测代码的过程。那么他又是如何做到少写代码这件事呢?原来他使用一种叫做groovy的魔法。groovy其实是一门基于jvm的动态语言。可以简单的理解成跑在jvm上的python或js。说到这里,可能没有接触过动态语言的同学,对它们都会有一个比较刻板的印象,太过于灵活,很容易出现问题,且可维护性差,所以有了那一句『动态一时爽,全家xxx』的梗。首先,这些的确是他的问题,严格的说是使用不当时才带来的问题。所以主要还是看使用的人。比如安卓领域的官方依赖管理工具gradle就是基于groovy开发的。另外不要误以为我学这门框架,还要多学一门语言,成本太大。其实大可不必担心,你如果会groovy当然更好,如果不会也没有关系。因为groovy是基于java的,所以完全可以放心大胆的使用java的语法,某些要用到的groovy独有的语法很少,而且后面都会告诉你。第二,他有更好的语义化,让你的单测代码可读性更高。语义化这个词可能不太好理解。举两个例子来说吧,第一个是语义化比较好的语言--HTML。他的语法特点就是标签,不同的类型放在不同的标签里。比如head就是头部的信息,body是主体内容的信息,table就是表格的信息,对于没有编程经验的人来说,也可以很容易理解。第二个是语义化比较差的语言--正则。他可以说基本上没有语义这种东西,由此导致的直接问题就是,即使是你自己的写的正则,几天之后你都不知道当时写的是什么。比如下面这个正则,你能猜出他是什么意思吗?(可以留言回复)
((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))3.领略SPOCK的魔法
3.1引入依赖
<!--如果没有使得springboot,以下包可以省略-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.3-groovy-2.5</version>
<scope>test</scope>
<!--引入spock与spring集成包-->
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.3-groovy-2.5</version>
<scope>test</scope>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.5.7</version>
<scope>test</scope>
注释已经标明,第一个包是springboot项目需要使用的,如果你只是想使用spock,只要最下面3个即可。其中第一个包spock-core提供了spock的核心功能,第二个包spock-spring提供了与spring的集成(不用spring的情况下也可以不引入)。注意这两个包的版本号->1.3-groovy-2.5。第一个版本号1.3其实代表是spock的版本,第二个版本号代表的是spock所要依赖的groovy环境的版本。最后一个包就是我们要依赖的groovy。
3.2准备基础测试类
3.2.1Calculator.java
/*\n**\n**blog.coder4j.cn\n**Copyright(C)2016-2019AllRightsReserved.\n*\n*/\npackagecn.coder4j.study.example.spock;\n\n/**\n*@authorbuhao\n*@versionCalculator.java,v0.12019-10-3010:34buhao\n*/\npublicclassCalculator{\n\n/**\n*加操作\n*\n*@paramnum1\n*@paramnum2\n*@return\n*/\npublicstaticintadd(intnum1,intnum2){\nreturnnum1+num2;\n}\n\n/**\n*整型除操作\n*\n*@paramnum1\n*@paramnum2\n*@return\n*/\npublicstaticintdivideInt(intnum1,intnum2){\nreturnnum1/num2;\n}\n\n/**\n*浮点型操作\n*@paramnum1\n*@paramnum2\n*@return\n*/\npublicstaticdoubledivideDouble(doublenum1,doublenum2){\nreturnnum1/num2;\n}\n}
说明
这是一个很简单的计算器类。只写了三个方法,一个是加法的操作、一个整型的除法操作、一个浮点类型的除法操作。
3.3开始单测Calculator.java
3.3.1创建单测类CalculatorTest.groovy
classCalculatorTestextendsSpecification{\n\n}
说明
这里一定要注意,之前我们已经说了spock是基于groovy。所以单测类的后缀不是.java而.groovy。千万不要创建成普通java类了。否则创建没有问题,但是写一些groovy语法会报错。如果你用的是IDEA可以通过如下方式创建,以前创建Java类我们都是选择第一个选项,现在我们选择第三个GroovyClass就可以了。
另外就是spock的测试类需要继承spock.lang.Specification类。
3.3.2验证加操作-expect
def"testadd"(){\nexpect:\nCalculator.add(1,1)==2\n}
说明
def是groovy的关键字,可以用来定义变量跟方法名。后面"testadd"是你单元测试的名称,也可以用中文。最后重点说明的是expect这个关键字。expect字面上的意思是期望,我们期望什么样的事情发生。在使用其它单测框架时,与之类似的是assert。比如_Assert.assertEquals(_Calculator.add(_1+1),2)_这样,表示我们断言加操作传入1与1相加结果为2。如果结果是这样则用例通过,如果不是则用例失败。这与我们上面的代码功能上完成一致。expect的语法意义就是在expect的块内,所有表达式成立则验证通过,反之有任一个不成立则验证失败。这里引入了一个块的概念。怎么理解spock的块呢?我们上面说spock有良好的语义化及更好的阅读性就是因为这个块的作用。可以类比成html中的标签。html的标签的范围是两个标签之间,而spock更简洁一点,从这个标签开始到下一个标签开始或代码结束的地方,就是他的范围。我们只要看到expect这个标签就明白,他的范围内都是我们预期要得到的结果。
3.3.3验证加操作-given-and
这里代码比较简单,参数我只使用了一次,所以直接写死。如果想复用,我就得把这些参数抽成变量。这个时候可以使用spock的given块。given的语法意义相当于是一个初始化的代码块。
def"testaddwithgiven"(){\ngiven:\ndefnum1=1\ndefnum2=1\ndefresult=2\n\nexpect:\nCalculator.add(num1,num2)==result\n}
当然你也可以像下面这样写,但是严重不推荐,因为虽然可以达到同样的效果,但是不符合spock的语义。就像我们一般是在head里面引入js、css,但是你在body或者任何标签里都可以引入,语法没有问题但是破坏了语义,不便理解与维护。
//反倒\ndef"testaddwithgiven"(){\nexpect:\ndefnum1=1\ndefnum2=1\ndefresult=2\nCalculator.add(num1,num2)==result\n}
如果你还想让语义更好一点,我们可以把参数与结果分开定义,这个时候可以使用and块。它的语法功能可以理解成同他上面最近的一个标签。
def"testaddwithgivenand"(){\ngiven:\ndefnum1=1\ndefnum2=1\n\nand:\ndefresult=2\n\nexpect:\nCalculator.add(num1,num2)==result\n}
3.3.4验证加操作-expect-where
看了上面例子,可能觉得spock只是语义比较好,但是没有少写几行代码呀。别急,下面我们就来看spock的一大杀器where。
def"testaddwithexpectwhere"(){\nexpect:\nCalculator.add(num1,num2)==result\n\nwhere:\nnum1|num2|result\n1|1|2\n1|2|3\n1|3|4\n}
where块可以理解成准备测试数据的地方,他可以跟expect组合使用。上面代码里expect块里面定义了三个变量num1、num2、result。这些数据我们可以在where块里定义。where块使用了一种很像markdown中表格的定义方法。第一行或者说表头,列出了我们要传数据的变量名称,这里要与expect中对应,不能少但是可以多。其它行都是数据行,与表头一样都是通过『|』号分隔。通过这样,spock就会跑3次用例,分别是1+2=2、1+2=3、1+3=4这些用例。怎么样?是不是很方便,后面再扩充用例只要再加一行数据就可以了。
3.3.5验证加操作-expect-where-@Unroll
上面这些用例都是正常可以跑通的,如果是IDEA跑完之后会如下所示:
那么现在我们看看如果有用例不通过会怎么样,把上面代码的最后一个4改成5
def"testaddwithexpectwhere"(){\nexpect:\nCalculator.add(num1,num2)==result\n\nwhere:\nnum1|num2|result\n1|1|2\n1|2|3\n1|3|5\n}
再跑一次,IDEA会出现如下显示
左边标注出来的是用例执行结果,可以看出来虽然有3条数据,其中2条数据是成功,但是只会显示整体的成功与否,所以显示未通过。但是3条数据,我怎么知道哪条没通过呢?右边标注出来的是spock打印的的错误日志。可以很清楚的看到,在num1为1,num2为3,result为5并且他们之间的判断关系为==的结果是false才是正确的。spock的这个日志打印的是相当历害,如果是比较字符串,还会计算异常字符串与正确字符串之间的匹配度,有兴趣的同学,可以自行测试。嗯,虽然可以通过日志知道哪个用例没通过,但是还是觉得有点麻烦。spock也知道这一点。所以他还同时提供了一个@Unroll注解。我们在上面的代码上再加上这个注解:
@Unroll\ndef"testaddwithexpectwhereunroll"(){\nexpect:\nCalculator.add(num1,num2)==result\n\nwhere:\nnum1|num2|result\n1|1|2\n1|2|3\n1|3|5\n}
运行结果如下:
通过添加@Unroll注解,spock自动把上面的代码拆分成了3个独立的单测测试,分别运行,运行结果更清晰了。那么还能更清晰吗?当然可以,我们发现spock拆分后,每个用例的名称其实都是你写的单测方法的名称,然后后面加一个数组下标,不是很直观。我们可以通过groovy的字符串语法,把变量放入用例名称中,代码如下:
@Unroll\ndef"testaddwithexpectwhereunrollby#num1+#num2=#result"(){\nexpect:\nCalculator.add(num1,num2)==result\n\nwhere:\nnum1|num2|result\n1|1|2\n1|2|3\n1|3|5\n}
如上,我们在方法名后加了一句#num1+#num2=#result。这里有点类似我们在mybatis或者一些模板引擎中使用的方法。#号拼接声明的变量就可以了,执行后结果如下。
这下更清晰了。另外一点,就是where默认使用的是表格的这种形式:
where:\nnum1|num2|result\n1|1|2\n1|2|3\n1|3|5
很直观,但是这种形式有一个弊端。上面『|』号对的这么整齐。都是我一个空格一个TAG按出来的。虽然语法不要求对齐,但是逼死强迫症。不过,好在还可以有另一种形式:
@Unroll\ndef"testaddwithexpectwhereunrollarrby#num1+#num2=#result"(){\nexpect:\nCalculator.add(num1,num2)==result\n\nwhere:\nnum1<<[1,1,2]\nnum2<<[1,2,3]\nresult<<[1,3,4]\n}
可以通过『<<』符(注意方向),把一个数组赋给变量,等同于上面的数据表格,没有表格直观,但是比较简洁也不用考虑对齐问题,这两种形式看个人喜好了。
3.3.6验证整数除操作-when-then
我们都知道一个整数除以0会有抛出一个『/byzero』异常,那么如果断言这个异常呢。用上面expect不太好操作,我们可以使用另一个类似的块when...then。
@Unroll\ndef"testintdividezeroexception"(){\nwhen:\nCalculator.divideInt(1,0)\n\nthen:\ndefex=thrown(ArithmeticException)\nex.message=="/byzero"\n}
when...then通常是成对出现的,它代表着当执行了when块中的操作,会出现then块中的期望。比如上面的代码说明了,当执行了_Calculator.divideInt(1,0)的操作,就一定会抛出_ArithmeticException异常,并且异常信息是_/byzero_。
3.4准备Spring测试类
上面我们已经学会了spock的基础用法,下面我们将学习与spring整合的知识,首先创建几个用于测试的demo类
3.4.1User.java
/*\n**\n**blog.coder4j.cn\n**Copyright(C)2016-2019AllRightsReserved.\n*\n*/\npackagecn.coder4j.study.example.spock.model;\n\nimportjava.util.Objects;\n\n/**\n*@authorbuhao\n*@versionUser.java,v0.12019-10-3016:23buhao\n*/\npublicclassUser{\nprivateStringname;\nprivateIntegerage;\nprivateStringpasswd;\n\npublicUser(Stringname,Integerage,Stringpasswd){\nthis.name=name;\nthis.age=age;\nthis.passwd=passwd;\n}\n\n/**\n*Gettermethodforpropertypasswd.\n*\n*@returnpropertyvalueofpasswd\n*/\npublicStringgetPasswd(){\nreturnpasswd;\n}\n\n/**\n*Settermethodforpropertypasswd.\n*\n*@parampasswdvaluetobeassignedtopropertypasswd\n*/\npublicvoidsetPasswd(Stringpasswd){\nthis.passwd=passwd;\n}\n\n/**\n*Gettermethodforpropertyname.\n*\n*@returnpropertyvalueofname\n*/\npublicStringgetName(){\nreturnname;\n}\n\n/**\n*Settermethodforpropertyname.\n*\n*@paramnamevaluetobeassignedtopropertyname\n*/\npublicvoidsetName(Stringname){\nthis.name=name;\n}\n\n/**\n*Gettermethodforpropertyage.\n*\n*@returnpropertyvalueofage\n*/\npublicIntegergetAge(){\nreturnage;\n}\n\n/**\n*Settermethodforpropertyage.\n*\n*@paramagevaluetobeassignedtopropertyage\n*/\npublicvoidsetAge(Integerage){\nthis.age=age;\n}\n\npublicUser(){\n}\n\n@Override\npublicbooleanequals(Objecto){\nif(this==o)returntrue;\nif(o==null||getClass()!=o.getClass())returnfalse;\nUseruser=(User)o;\nreturnObjects.equals(name,user.name)&&\nObjects.equals(age,user.age)&&\nObjects.equals(passwd,user.passwd);\n}\n\n@Override\npublicinthashCode(){\nreturnObjects.hash(name,age,passwd);\n}\n}
3.4.2UserDao.java
/*\n**\n**blog.coder4j.cn\n**Copyright(C)2016-2019AllRightsReserved.\n*\n*/\npackagecn.coder4j.study.example.spock.dao;\n\nimportcn.coder4j.study.example.spock.model.User;\nimportorg.springframework.stereotype.Component;\n\nimportjava.util.HashMap;\nimportjava.util.Map;\n\n/**\n*@authorbuhao\n*@versionUserDao.java,v0.12019-10-3016:24buhao\n*/\n@Component\npublicclassUserDao{\n\n/**\n*模拟数据库\n*/\nprivatestaticMapuserMap=newHashMap<>();\nstatic{\nuserMap.put("k",newUser("k",1,"123"));\nuserMap.put("i",newUser("i",2,"456"));\nuserMap.put("w",newUser("w",3,"789"));\n}\n\n/**\n*通过用户名查询用户\n*@paramname\n*@return\n*/\npublicUserfindByName(Stringname){\nreturnuserMap.get(name);\n}\n}
3.4.3UserService.java
/*\n**\n**blog.coder4j.cn\n**Copyright(C)2016-2019AllRightsReserved.\n*\n*/\npackagecn.coder4j.study.example.spock.service;\n\nimportcn.coder4j.study.example.spock.dao.UserDao;\nimportcn.coder4j.study.example.spock.model.User;\nimportorg.springframework.beans.factory.annotation.Autowired;\nimportorg.springframework.stereotype.Service;\n\n/**\n*@authorbuhao\n*@versionUserService.java,v0.12019-10-3016:29buhao\n*/\n@Service\npublicclassUserService{\n\n@Autowired\nprivateUserDaouserDao;\n\npublicUserfindByName(Stringname){\nreturnuserDao.findByName(name);\n}\n\npublicvoidloginAfter(){\nSystem.out.println("登录成功");\n}\n\npublicvoidlogin(Stringname,Stringpasswd){\nUseruser=findByName(name);\nif(user==null){\nthrownewRuntimeException(name+"不存在");\n}\nif(!user.getPasswd().equals(passwd)){\nthrownewRuntimeException(name+"密码输入错误");\n}\nloginAfter();\n}\n}
3.4.3Application.java
/*\n**\n**blog.coder4j.cn\n**Copyright(C)2016-2019AllRightsReserved.\n*\n*/\n\npackagecn.coder4j.study.example.spock;\n\nimportorg.springframework.boot.SpringApplication;\nimportorg.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublicclassApplication{\n\npublicstaticvoidmain(String[]args){\nSpringApplication.run(Application.class,args);\n}\n\n}\n
3.5与spring集成测试
/*\n**\n**blog.coder4j.cn\n**Copyright(C)2016-2019AllRightsReserved.\n*\n*/\n\npackagecn.coder4j.study.example.spock.service\n\nimportcn.coder4j.study.example.spock.model.User\nimportorg.springframework.beans.factory.annotation.Autowired\nimportorg.springframework.boot.test.context.SpringBootTest\nimportspock.lang.Specification\nimportspock.lang.Unroll\n\n@SpringBootTest\nclassUserServiceFunctionTestextendsSpecification{\n\n@Autowired\nUserServiceuserService\n\n@Unroll\ndef"testfindByNamewithinput#namereturn#result"(){\nexpect:\nuserService.findByName(name)==result\n\nwhere:\nname<<["k","i","kk"]\nresult<<[newUser("k",1,"123"),newUser("i",2,"456"),null]\n\n}\n\n@Unroll\ndef"testloginwithinput#nameand#passwdthrow#errMsg"(){\nwhen:\nuserService.login(name,passwd)\n\nthen:\ndefe=thrown(Exception)\ne.message==errMsg\n\nwhere:\nname|passwd|errMsg\n"kd"|"1"|"${name}不存在"\n"k"|"1"|"${name}密码输入错误"\n\n}\n}\n
spock与spring集成特别的简单,只要你加入了开头所说的spock-spring和spring-boot-starter-test。再于测试代码的类上加上@SpringBootTest注解就可以了。想用的类直接注入进来就可以了,但是要注意的是这里只能算功能测试或集成测试,因为在跑用例时是会启动spring容器的,外部依赖也必须有。很耗时,而且有时候外部依赖本地也跑不了,所以我们通常都是通过mock来完成单元测试。
3.6与springmock测试
/*\n**\n**blog.coder4j.cn\n**Copyright(C)2016-2019AllRightsReserved.\n*\n*/\n\npackagecn.coder4j.study.example.spock.service\n\nimportcn.coder4j.study.example.spock.dao.UserDao\nimportcn.coder4j.study.example.spock.model.User\nimportspock.lang.Specification\nimportspock.lang.Unroll\n\nclassUserServiceUnitTestextendsSpecification{\n\nUserServiceuserService=newUserService()\nUserDaouserDao=Mock(UserDao)\n\ndefsetup(){\nuserService.userDao=userDao\n}\n\ndef"testloginwithsuccess"(){\n\nwhen:\nuserService.login("k","p")\n\nthen:\n1*userDao.findByName("k")>>newUser("k",12,"p")\n}\n\ndef"testloginwitherror"(){\ngiven:\ndefname="k"\ndefpasswd="p"\n\nwhen:\nuserService.login(name,passwd)\n\nthen:\n1*userDao.findByName(name)>>null\n\nthen:\ndefe=thrown(RuntimeException)\ne.message=="${name}不存在"\n\n}\n\n@Unroll\ndef"testloginwith"(){\nwhen:\nuserService.login(name,passwd)\n\nthen:\nuserDao.findByName("k")>>null\nuserDao.findByName("k1")>>newUser("k1",12,"p")\n\nthen:\ndefe=thrown(RuntimeException)\ne.message==errMsg\n\nwhere:\nname|passwd|errMsg\n"k"|"k"|"${name}不存在"\n"k1"|"p1"|"${name}密码输入错误"\n\n}\n}
spock使用mock也很简单,直接使用Mock(类)就可以了。如上代码_UserDaouserDao=Mock(UserDao)。_上面写的例子中有几点要说明一下,以如下这个方法为例:
def"testloginwitherror"(){\ngiven:\ndefname="k"\ndefpasswd="p"\n\nwhen:\nuserService.login(name,passwd)\n\nthen:\n1*userDao.findByName(name)>>null\n\nthen:\ndefe=thrown(RuntimeException)\ne.message=="${name}不存在"\n\n}
given、when、then不用说了,大家已经很熟悉了,但是第一个then里面的_1*userDao.findByName(name)>>null_是什么鬼?首先,我们可以知道的是,一个用例中可以有多个then块,对于多个期望可以分别放在多个then中。第二,1xx表示期望xx操作执行了1次。_1userDao.findByName(name)_就表现当执行_userService.login(name,passwd)时我期望执行1次userDao.findByName(name)方法。如果期望不执行这个方法就是_0*xx_,这在条件代码的验证中很有用,然后_>>null又是什么意思?他代表当执行了_userDao.findByName(name)方法后,我让他结果返回null_。因为userDao这个对象是我们mock出来的,他就是一个假对象,为了让后续流程按我们的想法进行,我可以通过『>>』让spock模拟返回指定数据。第三,要注意第二个then代码块使用${name}引用变量,跟标题的#name是不同的。
3.7.1公共方法
方法名作用setup()每个方法执行前调用cleanup()每个方法执行后调用setupSpec()每个方法类加载前调用一次cleanupSpec()每个方法类执行完调用一次
这些方法通常用于测试开始前的一些初始化操作,和测试完成后的清理操作,如下:
defsetup(){\nprintln"方法开始前初始化"\n}\n\ndefcleanup(){\nprintln"方法执行完清理"\n}\n\ndefsetupSpec(){\nprintln"类加载前开始前初始化"\n}\n\ndefcleanupSpec(){\nprintln"所以方法执行完清理"\n}
3.7.2@Timeout
对于某些方法,需要规定他的时间,如果运行时间超过了指定时间就算失败,这时可以使用timeout注解
@Timeout(value=900,unit=TimeUnit.MILLISECONDS)\ndef"testtimeout"(){\nexpect:\nThread.sleep(1000)\n1==1\n}
注解有两个值,一个是value我们设置的数值,unit是数值的单位。
def"testfindByNamebyverity"(){\ngiven:\ndefuserDao=Mock(UserDao)\n\nwhen:\nuserDao.findByName("kk")>>newUser("kk",12,"33")\n\nthen:\ndefuser=userDao.findByName("kk")\nwith(user){\nname=="kk"\nage==12\npasswd=="33"\n}\n\n}
with算是一个语法糖,没有他之前我们要判断对象的值只能,user.getXxx()==xx。如果属性过多也是挺麻烦的,用with包裹之后,只要在花括号内直接写属性名称即可,如上代码所示。
因为篇幅有限,无法贴完所有代码,完整代码已上传github。
https://github.com/kiwiflydream/study-example/tree/master/study-spock-example
本文在瞻仰了如下博主的精彩博文后,再加上自身的学习总结加工而来,如果本文在看的时候有不明白的地方可以看一下下方链接。
关于Spock是什么意思的内容到此结束,希望对大家有所帮助。