Spock(Spock官网:http://spockframework.org/ )作为java和Groovy测试一种表达的规范语言,其参考了Junit、Groovy、jMock、Scala等众多语言的优点,并采用Groovy作为其语法,目前能够在绝大多数的集成开发环境(如eclipse,Intellij Ieda),构建工具(如Maven,gradle)等场景运行。Spock单元测试相对于传统的junit、JMockito、EsayMock、Mockito、PowerMock,由于使用了Groovy作为语法规则,代码量少,容易上手,提高了单元测试开发的效率,因此号称是下一代单元测试框架。
本文以实战的方式详解怎样使用Spock进行单元测试,以便更好地理解Spock单元测试,至少能够让读者能够在选择java单元测试面前多了一种选择。
1. 实战
1.1 Spock的Maven依赖:
org.spockframework
spock-core
1.1-groovy-2.4-rc-3
test
org.codehaus.groovy
groovy-all
2.4.7
cglib
cglib-nodep
3.2.4
test
org.objenesis
objenesis
2.5.1
test
1.2 构造项目基本代码
BizService.java(接口)、BizServiceImpl.java(接口实现类)、Dao.java(dao层代码)、PersonEntity.java(bean对象)
1.2.1 接口类 BizService.java
/*** Created by lance on 2017/2/25.
*/
package com.lance.spock.demo.api;
public interface BizService {
String insert(String id, String name, int age);
String remove(String id);
String update(String name, int age);
String finds(String name);
boolean isAdult(int age) throws Exception;
}
1.2.2 接口实现类 BizServiceImpl.java
package com.lance.spock.demo.service.impl;
import com.lance.spock.demo.api.BizService;
import com.lance.spock.demo.dao.Dao;
import com.lance.spock.demo.entity.PersonEntity;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/*** Created by lance on 2016/9/6.
*/
@Service
public class BizServiceImpl implements BizService {
@Autowired
private Dao dao;
public String insert(String id, String name, int age) {
if (StringUtils.isAnyBlank(id, name)) {
return "";
}
PersonEntity bean = new PersonEntity();
bean.setAge(age);
bean.setPersonId(id);
bean.setPersonName(name);
dao.insert(bean);
return name;
}
public String remove(String id) {
if (StringUtils.isBlank(id)) {
return "";
}
dao.remove(id);
return id;
}
public String update(String name, int age) {
if (StringUtils.isAnyBlank(name)) {
return "";
}
dao.update(name, age);
return name;
}
public String finds(String name) {
if (StringUtils.isBlank(name)) {
return null;
}
List beans = dao.finds(name);
StringBuilder sb = new StringBuilder();
sb.append("#");
for (PersonEntity bean : beans) {
sb.append(bean.getAge()).append("#");
}
return sb.toString();
}
public boolean isAdult(int age) throws Exception {
if(age < 0) {
throw new Exception("age is less than zero.");
}
return age >= 18;
}
public Dao getDao() {
return dao;
}
public void setDao(Dao dao) {
this.dao = dao;
}
}
1.2.3 Dao层类 Dao.java
package com.lance.spock.demo.dao;
import com.lance.spock.demo.entity.PersonEntity;
import org.springframework.stereotype.Repository;
import java.util.Arrays;
import java.util.List;
/**
* Created by lance on 2016/9/6.
*/
@Repository
public class Dao {
public void insert(PersonEntity bean) {
System.out.println("Dao insert person");
}
public void remove(String id) {
System.out.println("Dao remove");
}
public void update(String name, int age) {
System.out.println("Dao update");
}
public List finds(String name) {
System.out.println("Dao finds");
PersonEntity bean = new PersonEntity();
bean.setPersonId("24336461423");
bean.setPersonName("张三");
bean.setAge(28);
return Arrays.asList(bean);
}
}
1.2.4 Bean数据类 PersonEntity.java
/**
* Created by lance on 2016/9/6.
*/
package com.lance.spock.demo.entity;
public class PersonEntity {
private String personId;
private String personName;
private int age;
public String getPersonId() {
return personId;
}
public void setPersonId(String personId) {
this.personId = personId;
}
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "PersonEntity{" +
"personId='" + personId + '\'' +
", personName='" + personName + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
PersonEntity that = (PersonEntity) o;
if (age != that.age) return false;
if (personId != null ? !personId.equals(that.personId) : that.personId != null) return false;
return personName != null ? personName.equals(that.personName) : that.personName == null;
}
@Override
public int hashCode() {
int result = personId != null ? personId.hashCode() : 0;
result = 31 * result + (personName != null ? personName.hashCode() : 0);
result = 31 * result + age;
return result;
}
}
1.2.5 上下文配置 applicationContext.xml
1.3. Test类 BizServiceTest.groovy
package com.lance.spock.demo.service.impl.groovy
import com.lance.spock.demo.api.BizService
import com.lance.spock.demo.dao.Dao
import com.lance.spock.demo.entity.PersonEntity
import com.lance.spock.demo.service.impl.BizServiceImpl
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.ApplicationContext
import org.springframework.context.support.FileSystemXmlApplicationContext
import spock.lang.Specification
import spock.lang.Unroll
/**
* Created by lance on 2017/2/25.
*/
public class BizServiceTest extends Specification {
@Autowired
private BizServiceImpl bizService;
Dao dao = Mock(Dao) // 生成dao的Mock对象
/**
* Spock和Junit类似也将单元测试划分成了多个阶段
* 如 setup() 类似于Junit的@Before,在这个方法中的代码块会在测试用例执行之前执行,一般用于初始化程序以及Mock定义
* when:和then: 表示当...的时候,结果怎样.
* @return
*/
def setup() {
println(" ============= test start =============")
// 关联Mock对象,由于本测试是基于接口的测试,没有相应的setDao()方法,故采用此方法设置dao
//
ApplicationContext ac = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
bizService = ac.getBean(BizService.class)
// bizService.h.advised.targetSource.target.dao = dao;
bizService.setDao(dao)
}
def "test isAdult"() {
setup: //setup: 代码块主要针对自己所在方法的初始化参数操作
int age = 20
when:
bizService.isAdult(age) // 当执行isAdult方法时
then:
true // 判断when执行bizService.isAdult(age)结果为true
notThrown() // 表示没有异常抛出
println("age = " + age)
}
def "test isAdult except"() {
expect: // expect简化了when...then的操作
bizService.isAdult(20) == true
}
def "age less than zero"() {
setup:
int age = -100
when:
bizService.isAdult(age)
then:
def e = thrown(Exception) //thrown() 捕获异常,通常在then:中判断
e.message == "age is less than zero."
println(e.message)
cleanup:
println("test clean up") // 单元测试执行结束后的清理工作,如清理内存,销毁对象等
}
@Unroll
// 表示根君where的参数生成多个test方法,如本例生成了2个方法,方法名称分别为:
// 1."where blocks test 20 isadult() is true"
// 2."where blocks test 17 isadult() is false"
def "where blocks test #age isadult() is #result"() {
expect:
bizService.isAdult(age) == result
where: // 其实实质是执行了两次"where blocks test"方法,但是参数不一样
age || result
20 || true
17 || false
}
def "insert test"() {
setup:
PersonEntity person = new PersonEntity();
person.setAge(28)
person.setPersonId("id_1")
person.setPersonName("zhangsan")
when:
bizService.insert("id_1", "zhangsan", 28)
then:
PersonEntity
1 * dao.insert(person) //判断dao执行了一次insert,且插入的对象是否equals
}
}
参考资料:
使用Spock框架进行单元测试(http://www.open-open.com/lib/view/open1439793373083.html);
Spock官网(http://spockframework.org/).