简介 Spock 是一款用 Groovy 写的用于 Java 和 Groovy 应用测试的框架 相对于 JUnit 和 Mockito 的复杂和繁冗来说,无疑会让人眼前一亮。笔者正是在几年前被其吸引并一直在内部进行推广
优势
语法优雅
效率高(单元测试还是很适合用动态语言来写的)
依赖 1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.spockframework</groupId > <artifactId > spock-core</artifactId > <version > 2.0-M1-groovy-2.5</version > <scope > test</scope > </dependency > <dependency > <groupId > org.codehaus.groovy</groupId > <artifactId > groovy</artifactId > <version > 2.5.8</version > </dependency >
配置 gmavenplus plugin 编译 Groovy 脚本用的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <plugin > <groupId > org.codehaus.gmavenplus</groupId > <artifactId > gmavenplus-plugin</artifactId > <version > 1.6</version > <executions > <execution > <goals > <goal > compile</goal > <goal > compileTests</goal > </goals > </execution > </executions > </plugin >
maven-surefire-plugin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <plugin > <groupId > org.apache.maven.plugins</groupId > <artifactId > maven-surefire-plugin</artifactId > <version > 3.0.0-M1</version > <configuration > <includes > <include > **/*Test.java</include > <include > **/*Spec.java</include > </includes > <excludes > <exclude > **/module/room/viewer/service/impl/RoomViewerServiceImplTest.java</exclude > </excludes > </configuration > </plugin >
使用 依赖注入 可以在setup/setupSpec 里对要测试对象进行依赖注入
1 2 3 4 5 6 7 8 9 10 11 AccountService accountService = Mock() RedisHelper redisHelper = Mock() AddLogPOMapper addLogPOMapper = Mock() private AccountManager accountManager = new AccountManager();void setup () { accountManager.accountService = accountService accountManager.redisHelper = redisHelper accountManager.addLogPOMapper = addLogPOMapper }
💡 setup/cleanup对标JUnit的@Before/@After,setupSpec/cleanUpSpec对标JUnit的@BeforeClass/@AfterClass
准备测试 1 2 3 4 5 6 def fakeNum = 100 def realNum = 200 given: "mock账户余额" 1 * accountService.queryAccount(_) >> new QueryResultDTO(balance: fakeNum)1 * accountService.queryAccount(_) >> new QueryResultDTO(balance: realNum)
given & and
spock通过这两个关键字来修饰测试前的准备,比如mock的数据、方法等
1*
放在方法前用于表达方法的执行次数
>>
后面跟方法的返回内容
执行受试方法 1 2 when: AccountDTO accountDTO = accountManager.getAccount(123456 )
验证结果 1 2 3 4 5 6 then: with(accountDTO){ realNum == realNum fakeNum == fakeNum num == realNum + fakeNum }
实例 修改并返回传参 1 2 given: 1 * addLogPOMapper.insertSelective(_ as AddLogPO) >> {AddLogPO po -> po.id = 123 }
验证方法的执行 验证参数值
1 2 then: 1 * msgStoreManger.insertSelective({ MessagePO messagePO -> messagePO.getOptPlatform() == PlatformEnum.SINA.name()})
验证参数类型
1 2 then: 1 * redisHelper.expire(_ as String, _ as Long, _ as TimeUnit)
验证异常 1 2 3 then: def ex = thrown(Exception)ex.message == LotteryGameExceptionEnum.EXISTS_UNFINISHED_LOTTERY.getMessage()
如果想使用where
同时验证正常和异常两种情况的话,就需要一点点 Hack 了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class GameUnfinishedValidatorSpec extends Specification { class Success extends Exception { } def "Valid" () { given: gamePOMapper.selectUnfinishedItem(_ as Long) >> item when: unfinishedValidator.valid(new GameParam(roomId: 1 L)) throw new Success() then: def ex = thrown(expectedException) ex.message == expectedMessage where: item | expectedException | expectedMessage new LotteryGamePO(roomId: 1 L) | Exception | LotteryGameExceptionEnum.EXISTS_UNFINISHED_LOTTERY.getMessage() null | Success | null } }
坑 1 def ruleParam = new GameRuleParam(isFollow: false , isShare: true , isComment: false )
这种写法虽然效率高,但是在原始类上对变量名进行重构后,这儿并不会更新