32
入門Geb+Betamax 2011/11/22 JGGUGサポートスタッフ 須江 信洋 http://twitter.com/nobusue http://d.hatena.ne.jp/nobusue 資料の内容は個人としての意見・見解を述べたものであり、 所属する企業・組織が内容を保証するものではありません。

G*workshop 2011/11/22 Geb+Betamax

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: G*workshop 2011/11/22 Geb+Betamax

入門Geb+Betamax

2011/11/22

JGGUGサポートスタッフ 須江 信洋

http://twitter.com/nobusue

http://d.hatena.ne.jp/nobusue

※資料の内容は個人としての意見・見解を述べたものであり、

所属する企業・組織が内容を保証するものではありません。

Page 2: G*workshop 2011/11/22 Geb+Betamax

自己紹介

須江 信洋(すえ のぶひろ) Twitter: @nobusue

http://www.facebook.com/profile.php?id=732337788

かれこれ10年位、JavaEE関連の仕事をしてます

G*(Groovy関連技術)との関わり Groovyコミュニティ(JGGUG)サポートスタッフ

「プログラミングGROOVY」執筆チーム

「Groovy イン・アクション」翻訳チーム

Groovyで作ったBot飼ってます(@hatena_groovy)

2

Page 3: G*workshop 2011/11/22 Geb+Betamax

本日のお題

テスト自動化について

Geb入門

Betamax入門

Geb+Spock+Betamaxでgrails test-app

3

Page 4: G*workshop 2011/11/22 Geb+Betamax

テスト自動化、しましょうか?

Vモデル

4

http://ja.wikipedia.org/wiki/V%E3%83%A2%E3%83%87%E3%83%AB

ここはJUnitなどで

わりかし自動化

できている

ここは未だに

人海戦術が

主流

Page 5: G*workshop 2011/11/22 Geb+Betamax

自動化できれば・・・

テスト実行に伴う人的コストが不要になる リファクタリングに取り組み易くなる

バグフィックスや機能修正によるリリース頻度を上げられる

ミドルウェアやOSのFix適用に躊躇しなくてよくなる

テストの品質を上げられる 手作業に完全ということはありえない

手作業は監査できない

5

Page 6: G*workshop 2011/11/22 Geb+Betamax

とはいえ・・・

受け入れテスト(UAT)の完全自動化は大変 ならば、せめてスモークテストだけでも自動化を

人手によるテストやパフォーマンステストの前の関門として、CIのプロセスに組み込んでみては?

6

http://en.wikipedia.org/wiki/Smoke_testing

Page 8: G*workshop 2011/11/22 Geb+Betamax

Selenium1とWebDriver Selenium1の課題 テスト・ドライバーがブラウザ上で稼働するため、ブラウザのサンドボックスの制限を受ける

原理的に対応が難しい機能がある ファイルアップロードなど Ajax対応

WebDriver テスト・ドライバーがブラウザ外部で稼働するため上記の制限を受けない

Headless Driver(HtmlUnit)に対応 詳細な比較については以下http://www.asukaze.net/etc/webdriver/

8

Page 9: G*workshop 2011/11/22 Geb+Betamax

WebDriver: Java APIサンプル

9

public class Selenium2Example { public static void main(String[] args) { WebDriver driver = new FirefoxDriver(); driver.get("http://www.google.com"); WebElement element = driver.findElement(By.name("q")); element.sendKeys("Cheese!"); element.submit(); System.out.println("Page title is: " + driver.getTitle()); (new WebDriverWait(driver, 10)) .until(new ExpectedCondition<Boolean>() { public Boolean apply(WebDriver d) { return d.getTitle().toLowerCase().startsWith("cheese!"); } }); System.out.println("Page title is: " + driver.getTitle()); driver.quit(); } }

Page 10: G*workshop 2011/11/22 Geb+Betamax

10

ちょっとめんどい

Page 11: G*workshop 2011/11/22 Geb+Betamax

そこでGeb(じぇぶ)ですよ

Groovyで構築されたWebDriverのラッパー jQueryライクなNavigator APIを提供 Page Objectパターンによる構造化

WebDriver単体でも使えるが、より使いやすく

2011/11/22時点での最新バージョンは0.6.1 GitHub上の最新は0.7.0-SNAPSHOT

多様なテストフレームワークと統合可能 Spock,EasyB JUnit3/4,TestNG Cucumber(Cuke4Duke)

11

http://www.gebish.org/

Page 12: G*workshop 2011/11/22 Geb+Betamax

jQuery-like Navigator API

12

// CSS 3 selectors $("div.some-class p:first[title='something']") // Find via index and/or attribute matching $("h1", 2, class: "heading") $("p", name: "description") $("ul.things li", 2) // 'text' is special attribute for the element text content $("h1", text: "All about Geb") // Use builtin matchers and regular expressions $("p", text: contains("Geb")) $("input", value: ~/¥d{3,}-¥d{3,}-¥d{3,}/) // Chaining $("div").find(".b") $("div").filter(".c").parents() $("p.c").siblings()

Page 13: G*workshop 2011/11/22 Geb+Betamax

Page Objectパターン

13

class LoginPage extends Page { static url = "http://myapp.com/login" static at = { heading.text() == "Please Login" } static content = { heading { $("h1") } loginForm { $("form.login") } loginButton(to: AdminPage) { loginForm.login() } } }

class AdminPage extends Page { static at = { heading.text() == "Admin Section" } static content = { heading { $("h1") } } }

Browser.drive { to LoginPage assert at(LoginPage) loginForm.with { username = "admin" password = "password" } loginButton.click() assert at(AdminPage) }

ログイン画面

管理画面

テスト

Page 14: G*workshop 2011/11/22 Geb+Betamax

Gebの例: はてなキーワード検索

14

@Grapes([ @Grab("org.codehaus.geb:geb-core:0.6.1"), @Grab("org.seleniumhq.selenium:selenium-firefox-driver:2.12.0") ]) import geb.Browser Browser.drive { go "http://d.hatena.ne.jp/keyword/" assert title == "はてなキーワード - 話題の言葉がわかる、みんなで編集するキーワード" $("form.header-search").word = "Groovy" $("form.header-search").find("input", name:"submit").click() assert title == "はてな検索: Groovy" }

Page 15: G*workshop 2011/11/22 Geb+Betamax

GebとSpockのインテグレーション

Gebと連携するSpockのテストケースとして以下が提供される geb.spock.GebSpec / GebReportingSpec

browserインスタンスの注入 WebDriverのBrowserクラスの初期化が不要

evidence取得の自動化 GebReportingSpecを利用すると、テストケースのメソッド終了時にスクリーンショット(PNG)が自動取得される

15

Page 16: G*workshop 2011/11/22 Geb+Betamax

Geb ドキュメント&サンプル

The Book Of Geb http://www.gebish.org/manual/current/

かなり詳しいドキュメント

Gebソースコード https://github.com/geb/geb

Geb/Gradleサンプル https://github.com/geb/geb-example-gradle

16

Page 17: G*workshop 2011/11/22 Geb+Betamax

ChromeDriver利用時の注意点

ChromeDriverを実行可能にしておくこと http://code.google.com/p/selenium/wiki/ChromeDriver

バイナリをダウンロード

http://code.google.com/p/chromium/downloads/list

パスに追加 or システムプロパティ webdriver.chrome.drive を設定

17

Page 18: G*workshop 2011/11/22 Geb+Betamax

GebReportingSpecの例

18

class GoogleSpec extends GebReportingSpec { def "the first link should be wikipedia"() { when: to GoogleHomePage and: search.forTerm "wikipedia" then: at GoogleResultsPage and: firstResultLink.text() == "Wikipedia" when: firstResultLink.click() then: waitFor { at WikipediaPage } } }

Page 19: G*workshop 2011/11/22 Geb+Betamax

GebSpec利用時の注意点

GebReportingSpecを利用する場合はレポート出力先のディレクトリを指定しておく必要がある 最も簡単なのはシステムプロパティを使うこと

groovy -Dgeb.build.reportsDir=/tempdir GebSpockReporting.groovy

19

Page 21: G*workshop 2011/11/22 Geb+Betamax

Betamax

WebサービスのRecord/Playback Proxy HTTPリクエスト/レスポンスをtapeに記録

tapeを利用してWebサービスをエミュレート

JUnit/Spockとのインテグレーション

vcr(Ruby)のクローン https://www.relishapp.com/myronmarston/vcr

vcrのcassetと互換性あり?(未確認)

21

http://robfletcher.github.com/betamax/

Page 22: G*workshop 2011/11/22 Geb+Betamax

Hello, Betamax

22

<任意のディレクトリ>

│ build.gradle

└─src

└─test

└─groovy

MySpec.groovy

"gradle test"を実行すると、 src/test/resources/betamax/tapesにtapeが作成される

同じリクエストに対してはBetamaxがtapeを再生する デフォルトではURLとHTTPメソッドの対で同一性を判定

Page 23: G*workshop 2011/11/22 Geb+Betamax

build.gradle

23

apply plugin: "groovy"

repositories {

mavenCentral()

}

dependencies {

groovy("org.codehaus.groovy:groovy-all:1.8.4")

testCompile("com.github.robfletcher:betamax:1.0")

testCompile(

"org.codehaus.groovy.modules.http-builder:http-builder:0.5.1") {

exclude module: "groovy"

exclude module: "httpclient"

}

testCompile("org.spockframework:spock-core:0.5-groovy-1.8") {

exclude module: "groovy-all"

}

}

Page 24: G*workshop 2011/11/22 Geb+Betamax

MySpec.groovy

24

import betamax.*

import spock.lang.*

import org.junit.*

import groovyx.net.http.RESTClient

import org.apache.http.impl.conn.ProxySelectorRoutePlanner

class MySpec extends Specification {

@Rule Recorder recorder = new Recorder()

@Shared RESTClient http = new RESTClient()

def setupSpec() {

http.client.routePlanner = new ProxySelectorRoutePlanner(

http.client.connectionManager.schemeRegistry, ProxySelector.default)

}

@Betamax(tape="my tape")

def "various types of response data"() {

when:

def response = http.get(uri:"http://grails.org/")

then:

response.status == 200

}

}

Page 25: G*workshop 2011/11/22 Geb+Betamax

その他のサンプル

今のところまとまったドキュメント等はないので、ソースコードからビルドして試すのがおすすめ https://github.com/robfletcher/betamax

そろそろGitには慣れておいた方がいい

ビルド時の注意点 SonaTypeのアカウント設定

テストが一部通らない・・・が無視してOK

25

Page 26: G*workshop 2011/11/22 Geb+Betamax

SonaTypeのアカウント設定

素直にビルドすると以下のエラーが A problem occurred evaluating root project 'betamax'.

Cause: No such property: sonatypeUsername for class: org.gradle.api.internal.artifacts.publish.maven.deploy.groovy.DefaultGroovyMavenDeployer

SonaTypeのアカウントなんて持ってないよママン!

以下で対処してください gradle.propertiesを作成(値は空白で可) [gradle.properties] sonatypeUsername= sonatypePassword=

26

Page 27: G*workshop 2011/11/22 Geb+Betamax

テストが一部通らない

時刻の扱いに問題あり(Timezoneを考慮していない)だが、テストケース側の問題なので無視してOK 一応pull requestしたけど放置されてます。。。

他にもいくつか問題あり。ぜひパッチを!

27

Page 28: G*workshop 2011/11/22 Geb+Betamax

Grails + Geb + Betamax

ソースのexamples/grails-betamax以下

28

← ユニットテスト(Spock+Betamax)

← 機能テスト(Spock+Geb+Betamax)

← tape(Twitter API呼出し)

Page 29: G*workshop 2011/11/22 Geb+Betamax

tape

YAMLでHTTPリクエスト/レスポンスを記録

29

!tape

name: twitter success

interactions:

- recorded: 2011-08-31T13:12:42.293Z

request:

method: GET

uri: http://search.twitter.com/search.json?q=betamax&rpp=10&page=1

headers:

Accept: application/json

Accept-Encoding: gzip,deflate

Host: search.twitter.com

Proxy-Connection: Keep-Alive

response:

status: 200

headers:

Age: '0'

Cache-Control: max-age=15, must-revalidate, max-age=300

Content-Encoding: gzip

Content-Type: application/json;charset=utf-8

Date: Wed, 31 Aug 2011 13:12:42 GMT

Expires: Wed, 31 Aug 2011 13:17:42 GMT

Server: hi

Vary: Accept-Encoding

Via: 1.1 varnish

X-Varnish: '2033991101'

body: |-

{

"completed_in": 0.072,

"max_id": 108888091493801987,

Page 30: G*workshop 2011/11/22 Geb+Betamax

tapeの利用価値

Web APIにアクセスできない状況でもテストができる 新幹線で移動することが多い人向け

テストを高速化できる いちいちサーバーにアクセスしなくてすむ

テスト結果を安定化できる サーバー側の変化からテストを分離できる

レアケースに対するテストを容易にできる TwitterのAPI呼出し制限に引っ掛かった場合など

tapeを再利用(資産化)できる

30

Page 31: G*workshop 2011/11/22 Geb+Betamax

Betamaxはまり道

基本的にはRecorderと@Betamaxだけで いけるはず! 組合せによっていろいろ起こります。。。。

とりあえずGroovy-1.8/HTTPBuilderの組合せがおすすめ

素のURLクラスを使うと、 java.io.IOException: Premature EOF ???

400 Bad Request ???

品質向上にご協力下さい

31

"Please raise issues on Betamax’s GitHub issue tracker. Forks and pull requests are more than welcome." by Rob Fletcher

Page 32: G*workshop 2011/11/22 Geb+Betamax

32

ご静聴 ありがとう ございました