Spock單元測試框架使用詳解

Spock(Spock官網(wǎng):http://spockframework.org/ )作為java和Groovy測試一種表達的規(guī)范語言,其參考了Junit、Groovy榛做、jMock忘晤、Scala等眾多語言的優(yōu)點,并采用Groovy作為其語法跃闹,目前能夠在絕大多數(shù)的集成開發(fā)環(huán)境(如eclipse,Intellij Ieda),構(gòu)建工具(如Maven颓鲜,gradle)等場景運行。Spock單元測試相對于傳統(tǒng)的junit典予、JMockito甜滨、EsayMock、Mockito瘤袖、PowerMock衣摩,由于使用了Groovy作為語法規(guī)則,代碼量少捂敌,容易上手艾扮,提高了單元測試開發(fā)的效率,因此號稱是下一代單元測試框架占婉。

本文以實戰(zhàn)的方式詳解怎樣使用Spock進行單元測試泡嘴,以便更好地理解Spock單元測試,至少能夠讓讀者能夠在選擇java單元測試面前多了一種選擇逆济。

1. 實戰(zhàn)

1.1 Spock的Maven依賴:

<!-- Mandatory dependencies for using Spock -->

    <dependency>

      <groupId>org.spockframework</groupId>

      <artifactId>spock-core</artifactId>

      <version>1.1-groovy-2.4-rc-3</version>

      <scope>test</scope>

    </dependency>

    <!-- Optional dependencies for using Spock -->

    <dependency> <!-- use a specific Groovy version rather than the one specified by spock-core -->

      <groupId>org.codehaus.groovy</groupId>

      <artifactId>groovy-all</artifactId>

      <version>2.4.7</version>

    </dependency>

    <dependency> <!-- enables mocking of classes (in addition to interfaces) -->

      <groupId>cglib</groupId>

      <artifactId>cglib-nodep</artifactId>

      <version>3.2.4</version>

      <scope>test</scope>

    </dependency>

    <dependency> <!-- enables mocking of classes without default constructor (together with CGLIB) -->

      <groupId>org.objenesis</groupId>

      <artifactId>objenesis</artifactId>

      <version>2.5.1</version>

      <scope>test</scope>

    </dependency>

1.2 構(gòu)造項目基本代碼

BizService.java(接口)酌予、BizServiceImpl.java(接口實現(xiàn)類)磺箕、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 接口實現(xiàn)類 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<PersonEntity> 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數(shù)據(jù)類 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
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

      xmlns:context="http://www.springframework.org/schema/context"

      xmlns="http://www.springframework.org/schema/beans"

      xsi:schemaLocation="http://www.springframework.org/schema/beans

      http://www.springframework.org/schema/beans/spring-beans.xsd

      http://www.springframework.org/schema/context

      http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.lance.spock.demo"/>

</beans>

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,在這個方法中的代碼塊會在測試用例執(zhí)行之前執(zhí)行,一般用于初始化程序以及Mock定義

    *   when:和then:  表示當...的時候,結(jié)果怎樣. 

    * @return

    */

    def setup() {

        println(" ============= test start =============")

        // 關(guān)聯(lián)Mock對象抛虫,由于本測試是基于接口的測試松靡,沒有相應(yīng)的setDao()方法,故采用此方法設(shè)置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: 代碼塊主要針對自己所在方法的初始化參數(shù)操作

        int age = 20

        when:

        bizService.isAdult(age)  // 當執(zhí)行isAdult方法時

        then:

        true  // 判斷when執(zhí)行bizService.isAdult(age)結(jié)果為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")  // 單元測試執(zhí)行結(jié)束后的清理工作,如清理內(nèi)存莱褒,銷毀對象等

    }

    @Unroll

    // 表示根君where的參數(shù)生成多個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:      // 其實實質(zhì)是執(zhí)行了兩次"where blocks test"方法击困,但是參數(shù)不一樣

        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執(zhí)行了一次insert,且插入的對象是否equals

    }

}

參考資料:

  1. 使用Spock框架進行單元測試(http://www.open-open.com/lib/view/open1439793373083.html)广凸;

  2. Spock官網(wǎng)(http://spockframework.org/).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阅茶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子谅海,更是在濱河造成了極大的恐慌脸哀,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扭吁,死亡現(xiàn)場離奇詭異撞蜂,居然都是意外死亡,警方通過查閱死者的電腦和手機侥袜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門蝌诡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人枫吧,你說我怎么就攤上這事浦旱。” “怎么了九杂?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵颁湖,是天一觀的道長。 經(jīng)常有香客問我例隆,道長甥捺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任镀层,我火速辦了婚禮镰禾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唱逢。我一直安慰自己羡微,他們只是感情好,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布惶我。 她就那樣靜靜地躺著妈倔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绸贡。 梳的紋絲不亂的頭發(fā)上盯蝴,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天毅哗,我揣著相機與錄音,去河邊找鬼捧挺。 笑死虑绵,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的闽烙。 我是一名探鬼主播翅睛,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼黑竞!你這毒婦竟也來了捕发?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤很魂,失蹤者是張志新(化名)和其女友劉穎扎酷,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遏匆,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡法挨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了幅聘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凡纳。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖帝蒿,靈堂內(nèi)的尸體忽然破棺而出惫企,到底是詐尸還是另有隱情,我是刑警寧澤陵叽,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站丛版,受9級特大地震影響巩掺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜页畦,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一胖替、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧豫缨,春花似錦独令、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至舍败,卻和暖如春招狸,著一層夾襖步出監(jiān)牢的瞬間敬拓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工裙戏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留乘凸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓累榜,卻偏偏與公主長得像营勤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子壹罚,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容