G*Magazine Vol.1

Grails Plugin 探訪 第2回 ~Spock プラグイン~

URL: http://www.grails.org/plugin/spock

プラグインのバージョン: 0.5

対応するGrailsのバージョン: 1.2以上

はじめに

今回紹介するGrailsプラグインは、Spockプラグインです。

このプラグインを組み込むことにより、Spockを使ってテストケースを記述することができます。

Spockとは

Spockは、JavaやGroovy用のテストと仕様のためのフレームワークです。

Spockについては、次のURLを参照してください。

http://spockframework.org/

プラグインのインストール

プラグインのインストール手順は、Grailsのバージョンによって異なります。

Grails 1.2.xの場合

Grails 1.2.xの場合、次のとおりです。

$ grails install-plugin spock 0.5-groovy-1.6

Grails 1.3.xの場合

Grails 1.3.xの場合、次のとおりです。

$ grails install-plugin spock 0.5-groovy-1.7

または

$ grails install-plugin spock

これ以降、Grails 1.3.6を前提として説明します。

テストのケースの作成

本をあらわす次のドメインクラスがあります。

package pluginspock

class Book {

    String title
    Integer price

    static constraints = {
        title()
        price(min: 0)
    }
}

このドメインクラスのテストケースを作成します。

ファイル自体は、test/unitディレクトリに作成します。

クラス/ファイル名は、"Spec"あるいは"Specification"で終わる必要があります。

ここでは、test/unit/BookSpec.goorvyとします。

package pluginspock

import grails.plugin.spock.UnitSpec

class BookSpec extends UnitSpec {

    def '本のタイトルで検索できる'() {
        setup:
            mockDomain(Book)

        when: '本の保存'
            new Book(title: title, Price: price).save()

        then: 'mockDomainを使ってタイトルで検索'
            def book = Book.findByTitle(title)
            book != null
            book.title == title
            book.price == price

        where:
            title = 'Groovyイン・アクション'
            price = 3500
    }

}

Spockプラグインでは、SpockのSpecificationクラスを継承したテストクラスを提供しています。

ユニットテスト用にUnitSpecというクラスがありますので、このクラスを継承してドメインクラスのテストケースを作成します。

テストケースの実行

テストケースの実行は、test-appコマンドで実行します。

$ grails test-app

Spockのテストケースだけ実行することもできます。

$ grails test-app :spock

いろいろなテストケースクラス

Spockプラグインは、UnitSpec以外にも、複数のテストクラスを提供しています。

どのようなテストクラスがあるのか、簡単に説明します。

UnitSpec

ユニットテスト用のテストクラスです。UnitSpecを継承するテストケースは、test/unitディレクトリに作成します。

package pluginspock

import grails.plugin.spock.UnitSpec

class BookSpec extends UnitSpec {

    def '本のタイトルで検索できる'() {
        setup:
            mockDomain(Book)

        when: '本の保存'
            new Book(title: title, Price: price).save()

        then: 'mockDomainを使ってタイトルで検索'
            def book = Book.findByTitle(title)
            book != null
            book.title == title
            book.price == price

        where:
            title = 'Groovyイン・アクション'
            price = 3500
    }

    def 'titleのバリデーション'() {
        setup:
            mockForConstraintsTests(Book)

        when:
            def book = new Book(title: title, price: 3500)

        then:
            book.validate(['title']) == validationResult

        where:
            title << [null, 'Groovyイン・アクション']
            validationResult << [false, true]
    }

    def 'priceのバリデーション'() {
        setup:
            mockForConstraintsTests(Book)

        when:
            def book = new Book(title: 'Groovyイン・アクション', price: price)

        then:
            book.validate(['price']) == validationResult

        where:
            price << [-1, 0, 1]
            validationResult << [false, true, true]
    }

}

ControllerSpec

コントローラ用のテストクラスです。

ドメインクラスBookのコントローラクラス

package pluginspock

class BookController {

    static allowedMethods = [save: "POST", update: "POST", delete: "POST"]

    def index = {
        redirect(action: "list", params: params)
    }

    def list = {
        params.max = Math.min(params.max ? params.int('max') : 10, 100)
        [bookInstanceList: Book.list(params), bookInstanceTotal: Book.count()]
    }

    // 以下省略
}

に対するテストクラスは次のとおりです。ControllerSpecを継承するテストケースは、test/unitディレクトリに作成します。

package pluginspock

import grails.plugin.spock.ControllerSpec

class BookControllerSpec extends ControllerSpec {


    def 'indexアクションのテスト'() {
        when:
            controller.index()

        then:
            redirectArgs.action == 'list'
            redirectArgs.params.size() == 0
    }

    def 'listアクションのテスト'() {
        when:
            Book.metaClass.static.list = { params ->
                books
            }
            Book.metaClass.static.count = {
                books.size()
            }

        then:
            controller.list() == [bookInstanceList: books, bookInstanceTotal: books.size()]
        
        where:
            books = [
                new Book(title: 'Groovyイン・アクション', price: 3500),
                new Book(title: 'Grailsイン・アクション', price: 3600),
            ]
    }

}

TagLibSpec

タグリブ用のテストクラスです。

ドメインクラスBookを引数にとるタグリブクラス


package pluginspock

class BookTagLib {

    def print = { attrs, body ->
        def book = attrs.book
        out << "<book title="${book.title}" price="${book.price}">"
        out << body()
        out << '</book>'
    }

}

に対するテストクラスは次のとおりです。TagLibSpecを継承するテストケースは、test/unitディレクトリに作成します。

package pluginspock

import grails.plugin.spock.TagLibSpec

class BookTagLibSpec extends TagLibSpec {

    def 'bookタグのテスト'() {
        expect:
            print(book: book) { 'オススメです。' } == '<book title="Groovyイン・アクション" price="3500">オススメです。</book>'

        where:
            book = new Book(title: 'Groovyイン・アクション', price: 3500)
    }

}

IntegrationSpec

インテグレーションテスト用のテストクラスです。

サービスクラス

package pluginspock

class BookService {

    static transactional = true

    def save(title, price) {
        new Book(title: title, price: price).save(flush: true)
    }

    def update(book, title, price) {
        book.title = title
        book.price = price
        book.save(flush: true)
    }
}

に対するテストクラスは次のとおりです。IntegrationSpecを継承するテストケースは、test/integrationディレクトリに作成します。


package pluginspock

import grails.plugin.spock.IntegrationSpec

class BookServiceSpec extends IntegrationSpec {

    def bookService

    def 'saveメソッドのテスト'() {
        when:
            bookService.save(title, price)

        then:
            Book.findByTitleAndPrice(title, price) != null
            Book.count() == 1

        where:
            title = 'Groovyイン・アクション'
            price = 3500
    }

    def 'updateメソッドのテスト'() {
        when:
            def book = bookService.save(oldTitle, oldPrice)
            bookService.update(book, newTitle, newPrice)

        then:
            Book.findByTitleAndPrice(newTitle, newPrice) != null
            Book.count() == 1

        where:
            oldTitle = 'Groovyイン・アクション'
            oldPrice = 3500
            newTitle = '改訂版Groovyイン・アクション'
            newPrice = 4000
    }

}

GroovyPagesSpec

GSP用のテストクラスです。

先述のBookTagLib#printを使用したGSPコードのテストクラスは次のとおりです。GroovyPagesSpecを継承するテストケースは、test/integrationディレクトリに作成します。

package pluginspock

import grails.plugin.spock.GroovyPagesSpec

class BookTagSpec extends GroovyPagesSpec {

    def 'printタグのテスト'() {
        when:
            template = '<g:print book="${book}">これは良い本です。</g:print>'
            params = [book: book]

        then:
            output == '<book title="Groovyイン・アクション" price="3500">これは良い本です。</book>'

        where:
            book = new Book(title: 'Groovyイン・アクション', price: 3500)
    }

}

機能テストについて

Spockプラグインでは、機能テストは提供していません。機能テスト用のプラグインとして、Gebプラグインなどがあります。

最後に

Spockはとても強力なテストフレームワークですので、GrailsアプリケーションのテストケースをSpockで書いてみてはいかがでしょうか?

著者紹介

  • 杉浦孝博
  • 最近はGrails を使用したシステムの保守をしている自称プログラマ。日本Grails/Groovy ユーザーグループ事務局長。共著『Grails 徹底入門』、共訳『Groovy イン・アクション』

G*Magazine Vol.1

Grailsをコントロールせよ! Part 1
Griffon 不定期便 〜第2回 バインディング編〜
CodeNarcを利用してGROOVYのコード品質を上げる ~第1回 CodeNarcとは~
GebではじめるWebテスト 〜第1回 導入編〜
Grails Plugin 探訪 第2回 ~Spock プラグイン~
リリース情報 2011.02.11
ぐるーびーたん 第1話