Groovy/Spock 测试导论

原文 http://java.dzone.com/articles/intro-so-groovyspock-testing

翻译 hxfirefox

测试对于软件开发者而言至关重要,不过总会有人说:“写代码是我的事,测试那是QA的工作”,这样的想法真是弱爆了,因为大量的业界实践已经证明测试驱动编码可以有效地帮助开发者提升代码质量。

大多数遵循TDD的Java开发者均会使用mockito或powermock,但mockito和powermock均包含了许多样本代码,导致测试代码变得冗长而难以维护。在测试中引入Groovy/Spock后,我完全被它们吸引,并转向使用Groovy/Spock来替代原有的测试框架。

下面将围绕一个简单例子来讲解Groovy/Spock,例子中将包含一个service类,负责处理domain对象,以及一个数据访问层。

首先是domain类:

public class User {

    private int id;
private String name;
private int age; // Accessors omitted
}

接下来是DAO接口:

public interface UserDao {

    public User get(int id);

}

最后是service类:

public class UserService {

    private UserDao userDao;

    public UserService(UserDao userDao) {
this.userDao = userDao;
} public User findUser(int id){
return null;
}
}

采用Groovy/Spock针对UserService编写测试

class UserServiceTest extends Specification {

    UserService service
UserDao dao = Mock(UserDao) def setup(){
service = new UserService(dao)
} def "it gets a user by id"(){
given:
def id = 1 when:
def result = service.findUser(id) then:
1 * dao.get(id) >> new User(id:id, name:"James", age:27)
result.id == 1
result.name == "James"
result.age == 27
}
}

上述测试代码中,首先我们使用了groovy,这是一种非常类似Java的语言,但是它的语法更加轻,例如它不用像Java语言那样,在每句结尾加上分号;它也不需要使用public修饰符,因为public是默认的。上述测试类继承自spock.lang.Specification,这是Spock基类,继承该基类后就可以使用given,when,then等代码块。

在Spock中创建mock对象非常容易,只需要使用Mock(Class)这样的语句即可。如上所述,mock后的DAO对象被传入userService中。Setup方法会在每个测试方法运行前被执行。Groovy的一个显著特点是可以使用字符串文本来命名方法,将这个特点应用在测试方法上就能使得测试方法可以更加容易被阅读和理解,如上述代码所示。

Given, when, then

Spock是一个BDD测试框架,因此对于Spock中涉及的given,when,then样式最简单的理解就是:

Given 给定一些条件,When 当执行一些操作时,Then 期望得到某个结果。

如上述测试方法中Given,给定id=1,即测试的变量;而在When中则是被测试方法,如在上述代码中调用findUser();Then中则是断言,即检查被测试方法的输出结果。

上述Then中的第一句语句虽然看上去可怕,但实际上却非常容易理解:

1 * dao.get(id) >> new User(id:id, name:"James", age:27)

该行表示了对于mock对象dao的期望值,即期望调用dao.get()方法1次,而“>>”是spock的特色,表示“then return”含义。因此该句翻译过来的意思是:期望调用1次dao.get()方法,当执行该方法后,请返回一个新的User对象。此外在构造方法中使用具名参数也是groovy的另一特点。Then中剩余的代码对result对象进行检查。

由此测试代码驱动产生的产品代码非常简单,如下所示:

public class UserService {

    private UserDao userDao;

    public UserService(UserDao userDao) {
this.userDao = userDao;
} public User findUser(int id){
return userDao.get(id);
}
}

接下来实现创建用户功能,在UserService中添加如下代码:

public void createUser(User user){
// check name // if exists, throw exception // if !exists, create user
}

在UserDao中添加如下方法:

public User findByName(String name);
public void createUser(User user);

相应的测试方法如下:

def "it saves a new user"(){
given:
def user = new User(id: 1, name: 'James', age:27) when:
service.createUser(user) then:
1 * dao.findByName(user.name) >> null then:
1 * dao.createUser(user)
}

在上述代码中出现了两处Then,这是因为当所有断言放在一个then块中,Spock会认为这些断言是同时发生的。如果期望断言按顺序执行,则需要将断言分割到多个then块中,spock会按顺序执行断言。如上述所示,首先需要判断用户是否存在,然后再去创建用户。产品代码实现如下:

public void createUser(User user){
User existing = userDao.findByName(user.getName()); if(existing == null){
userDao.createUser(user);
}
}

上述代码针对用户不存在场景,而对于用户存在的场景,测试代码如下:

def "it fails to create a user because one already exists with that name"(){
given:
def user = new User(id: 1, name: 'James', age:27) when:
service.createUser(user) then:
1 * dao.findByName(user.name) >> user then:
0 * dao.createUser(user) then:
def exception = thrown(RuntimeException)
exception.message == "User with name ${user.name} already exists!"
}

上述代码当调用findByName时,返回一个存在的用户,然后不调用createUser(),第三个Then块捕获方法抛出的异常。注意groovy拥有一个称之为GStrings的特征,该特征可以在引用的字符串中插入参数,如${user.name}。相应产品代码如下:

public void createUser(User user){
User existing = userDao.findByName(user.getName()); if(existing == null){
userDao.createUser(user);
} else{
throw new RuntimeException(String.format("User with name %s already exists!", user.getName()));
}
}

提示

  • 最重要也是最容易被遗忘的提示,阅读spock文档!
  • 可以命名spock块,例如将given命名为“Some variables”,有助于开发者在测试代码中更加清楚的表达含义
  • 当对mock对象方法调用次数不关心时,可以使用_ * mock.method()
  • 在then块中可使用下划线来通配方法及类,例如,0 * mock._ 表示期望mock对象的任何方法都未被调用,或0 * . 表示期望任何对象的任何方法都未被调用
  • 通常按given,when,then编写测试,但实际上从when开始编写测试会更加容易发现测试需要的given和测试的输出结果(then)
  • expect块对于测试不需要对mock对象进行断言的简单方法更加有效
  • 当对于传递给mock对象的参数不关注时,可以使用通配符参数
  • 拥抱groovy闭包Embrace groovy closures! They can be you’re best friend in assertions!
  • 当希望在整个测试类中只运行一次,可以复写setupSpec和cleanupSpec

结论

测试代码是为了协助开发者的,而不是起相反作用,groovy在这方面提供了很多快捷方式来帮助开发者写出更加优雅的测试代码。完整代码可参考https://gist.github.com/jameselsey/8096211

思考

翻译这篇文章是受到了《使用 Groovy 语言替代 JUnit 来为 Java 程序编写单元测试》和《The Coding Kata: FizzBuzzWhizz in Modern Java》两篇文章的启示。除了赞叹两篇文章中采用的测试框架的易用,也深深地被groovy所吸引,其作为DSL的特质不论是对于追求编写更好测试用例的精益开发者还是对于刚入门测试用例的新手开发者来说都是容易掌握和使用的。我们期望测试用例的目标就是能够作为产品代码的 living docs,最佳的效果就是完全摆脱编程语言的语法束缚,成为纯粹的书写或口头表达方式,这样就能“望文生义”。Groovy在这方面确实对于Java测试用例编写起到了促进作用,再加上groovy与Java的无缝融合,及自身拥有的语法特性,在团队中推广groovy替代传统Java测试框架的唯一阻力就剩下大多数开发者是否愿意学习一门新的编程语言。

Groovy/Spock 测试导论的更多相关文章

  1. Groovy Spock环境的安装

    听说spock是一个加强版的Junit,今天特地安装了,再把过程给大家分享一下. 首先说明,我的Java项目是用maven管理的. 我用的Eclipse是Kelper,开普勒. 要使用Spock之前, ...

  2. 使用Groovy+Spock轻松写出更简洁的单测

    当无法避免做一件事时,那就让它变得更简单. 概述 单测是规范的软件开发流程中的必不可少的环节之一.再伟大的程序员也难以避免自己不犯错,不写出有BUG的程序.单测就是用来检测BUG的.Java阵营中,J ...

  3. 使用Groovy+Spock构建可配置的订单搜索接口测试用例集

    概述 测试是软件成功上线的安全网.基本的测试包含单元测试.接口测试.在 "使用Groovy+Spock轻松写出更简洁的单测" 一文中已经讨论了使用GroovySpock编写简洁的单 ...

  4. 在Spring Boot项目中使用Spock测试框架

    本文首发于个人网站:在Spring Boot项目中使用Spock测试框架 Spock框架是基于Groovy语言的测试框架,Groovy与Java具备良好的互操作性,因此可以在Spring Boot项目 ...

  5. Spock测试套件入门

    目录 Spock测试套件 核心概念 整体认识 前置.后置 同junit的类比 Feature 方法 blocks 典型的用法 异常condition then和expect的区别 cleanup bl ...

  6. Groovy+Spock单元测试

    一.导入依赖 Spock是基于JUnit的单测框架,提供一些更好的语法,结合Groovy语言,可以写出更为简洁的单测. <!-- groovy依赖 --> <dependency&g ...

  7. Compile Groovy/Spock with GMavenPlus

    在之前的博文里曾使用GMaven插件编译Groovy/Spock,这次使用GMavenplus插件,更加方便. 具体步骤 1. 导入Spock和Groovy依赖 <dependency> ...

  8. [每日一学]apache camel|BDD方式开发apache camel|Groovy|Spock

    开发apache camel应用,最好的方式就是tdd,因为camel的每个组件都是相互独立并可测试的. 现在有很多好的测试框架,用groovy的Spock框架的BDD(行为测试驱动)是比较优秀和好用 ...

  9. [编译原理]用BDD方式开发lisp解释器(编译器)|开发语言java|Groovy|Spock

    lisp是一门简单又强大的语言,其语法极其简单: (+ 1 2 ) 上面的意思 是:+是方法或函数,1 ,2 是参数,fn=1+2,即对1,2进行相加求值,结果是:3 双括号用来提醒解释器开始和结束. ...

随机推荐

  1. Log4j读取配置文件并使用

    /** 设置配置路径从环境变量读取     * PropertyConfigurator类加载.properties文件的配置    * DOMConfigurator加载.xml文件的配置     ...

  2. Python 的 “Magic” 方法

    在以前的文章中,我聊过了Python的 __getitem__ 和 __setitem__ 方法.这些方法被称为“魔法”方法.特殊方法或者dunger方法(译者:国内书籍用“魔法”一词较多).那么,什 ...

  3. eclipse 重装了tomcat后配置路径

    在Windows->Preferences->Server->Runtime Environments把先前的工程Servers删除掉

  4. 【刷题】洛谷 P4234 最小差值生成树

    题目描述 给定一个标号为从 \(1\) 到 \(n\) 的.有 \(m\) 条边的无向图,求边权最大值与最小值的差值最小的生成树. 输入输出格式 输入格式: 第一行两个数 \(n, m\) ,表示图的 ...

  5. BZOJ 2668: [cqoi2012]交换棋子

    2668: [cqoi2012]交换棋子 Time Limit: 3 Sec  Memory Limit: 128 MBSubmit: 1112  Solved: 409[Submit][Status ...

  6. hdu6057 Kanade's convolution 【FWT】

    题目链接 hdu6057 题意 给出序列\(A[0...2^{m} - 1]\)和\(B[0...2^{m} - 1]\),求所有 \[C[k] = \sum\limits_{i \; and \; ...

  7. springboot项目添加jsp支持

    一.创建springboot项目 使用 http://start.spring.io/ 快速创建一个springboot项目下载并导入 二.添加依赖 在pom.xml中添加支持jsp的依赖如下: &l ...

  8. bzoj 2839 : 集合计数 容斥原理

    因为要在n个里面选k个,所以我们先枚举选的是哪$k$个,方案数为$C_{n}^k$ 确定选哪k个之后就需要算出集合交集正为好这$k$个的方案数,考虑用容斥原理. 我们还剩下$n-k$个元素,交集至少为 ...

  9. P3942 将军令

    P3942 将军令 梦里,小 F 成了一个给将军送密信的信使. 现在,有两封关乎国家生死的密信需要送到前线大将军帐下,路途凶险,时间紧迫.小 F 不因为自己的祸福而避趋之,勇敢地承担了这个任务. 不过 ...

  10. numpy/arrayobject.h”: No such file or directory

    import numpyimport pyximportpyximport.install(setup_args={"script_args":["--compiler= ...