More Related Content Similar to Webアプリのシナリオテスト自動化を運用に乗せるまでの10のステップ
Similar to Webアプリのシナリオテスト自動化を運用に乗せるまでの10のステップ (20) Webアプリのシナリオテスト自動化を運用に乗せるまでの10のステップ9. アプリ構成
【言語】 Java8 + Lombok
【フレームワーク】 Spring Boot
【テンプレート】 Thymeleaf
【フロント】 SCSS, ECMAScript 2015, Knockout.js
【ビルド】 Maven, node.js (gulp)
【IDE】 IntelliJ
11. 導入 - Spock
Maven Gradle
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.1-groovy-2.4-rc-1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.1-groovy-2.4-rc-1</version>
</dependency>
apply plugin: 'groovy'
testCompile 'org.spockframework:spock-core:1.1-groovy-2.4-rc-1'
testCompile 'org.spockframework:spock-spring:1.1-groovy-2.4-rc-1'
12. Spock でテスト書いてみる
src/test/groovy/ 以下に .groovy ファイルを作ります。
テスト名はこのように日本語で書くのがSpock流(?)
実行は IDE に応じて Ctrl + Shift + F10 などで。
class SampleSpec extends Specification {
def "サンプルテスト"() {
setup:
// 初期処理
assert count > 0
}
}
← Spock だとsetupブロックが必要
← アサーション処理
13. 導入 - Gradle
Spock は Maven からも使えますが、Groovy ベースというこ
ともあって Gradle との相性がいいです。
というわけで、Gradle 導入しましょう!
Maven のプロジェクトで Gradle を導入するのは、比較的簡
単です。
※ Maven + Gradle のハイブリッド構成
16. Maven Gradle
ROOT
|
|-- common
| |
| |-- pom.xml
|
|-- app-a
| |
| |-- pom.xml
|
|-- app-b
|
|-- pom.xml
ROOT
|
|-- common
|-- app-a
|-- app-b
|
|-- build.gradle
|-- settings.gradle
← ここに全ての設定
← サブプロジェクト名の
一覧
20. 導入 - Geb
準備が整ったところで、Geb を導入します。
別途 WebDriver が必要になります。
今回は ChromeDriver を採用します。
build.gradle
compile 'org.gebish:geb-spock:0.13.1'
compile 'org.seleniumhq.selenium:selenium-chrome-driver:2.53.1'
compile 'org.seleniumhq.selenium:selenium-support:2.53.1'
compile 'org.seleniumhq.selenium:selenium-api:2.53.1'
22. Geb の設定ファイル
src/test/resources/ の下に
GebConfig.groovy ファイルを作ります。
import org.openqa.selenium.chrome.ChromeDriver
import geb.report.ScreenshotReporter
import java.util.concurrent.TimeUnit
System.setProperty("webdriver.chrome.driver", "./driver/chromedriver_mac")
driver = {
def driver = new ChromeDriver()
driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS)
driver
}
reportsDir = "./build/reports/tests/capture"
reporter = new ScreenshotReporter()
← WebDriver バイナリの配置場所
← タイムアウト設定
← return driver の略
← キャプチャ関連
23. Geb でテスト書いてみる
GebReportingSpec を使うと、画面キャプチャが撮れる。
※ 出力場所は GebConfig に記述
class SampleGebSpec extends GebReportingSpec {
def "画面テスト"() {
setup:
go "http://localhost:8080/mypage/"
$('.main_button').click()
waitFor { $('.next_button').size() > 0 }
report "画面A"
}
}
← ブラウザでURLを開く
← 要素(ボタン)を指定してクリック
← 特定の要素が出現するまで待つ
← 画面キャプチャを撮る
GebReportingSpec を使うと画面キャプチャが撮れる!
29. クリックに失敗する 解決策
このような場合、WebElement のラップを外し
素の Selenium の click イベントを発動させます。
import org.openqa.selenium.WebElement
import org.openqa.selenium.interactions.Actions
def rawClick($el) {
def actions = new Actions(browser.driver)
if ($el instanceof WebElement) {
actions.click($el).build().perform()
} else {
def $fel = $el.firstElement()
actions.click($fel).build().perform()
}
}
← Actions クラスを使う
← 要素を指定して直接クリックイベントを発動
30. クリックに失敗する 解決策2
さらに、その要素がスクロール外にある場合は以下のように
します。
def moveToClick($el) {
def actions = new Actions(browser.driver)
def $fel = $el.firstElement()
actions
.moveToElement($fel, ($fel.getSize().width / 2).intValue(), ($fel.getSize().height / 2).intValue())
.build().perform()
$fel.click()
} ↑ 要素までスクロールさせた後クリックする。
確実にクリックできるように、要素の真ん中にオフセットを指定
35. テストケースを作るのが大変 解決策
・フォームへの入力補助
formInput(
'lastName': '山田',
'firstName': '太郎'
)
・イベントハンドラ
findClickableElement('reportObSubmit').click()
・共通的な画面処理(ユーザ登録など)
registerUser()
・ランダム文字列生成
def email = createRandomEmail()
・ポップアップ関連
waitShowPopup($('#jsi-popup-apply'))
・正規表現で指定可能なURLのアサーション
waitForUrl(~/events¥/[0-9]+/)
← これは必須。
name属性やID指定、JSライブラリ対応など
← イベントハンドラは
JSライブラリに合わせた対応が望ましい
← 複数のシナリオで使う処理は共通化する
← DBのユニーク制約への対応
← 使っているポップアップのライブラリに応じて
← これが無いとアサーションが難しい場合がある
38. テストレポートにキャプチャが無い 解決策
やや強引ですが、Gradle スクリプト内でレポートHTMLにキ
ャプチャ画像を貼り付けています。
task appendCaptureToReport() << {
def File basedir = project.projectDir
def root = new File(basedir, 'build/reports/tests/capture')
// キャプチャ画像ファイル一覧を取得
def buffs = [:]
root.eachFileRecurse(FileType.FILES) {
buffs[dir] << picName
}
… コードが長いので、全文は gist で
https://gist.github.com/naoki-iwami/c5ceba0b02bee3b6bbcd1545c111ab98
}
43. テストの前準備が大変 解決策
task('e2test', type: Test) {
def Process applicationProcess
doFirst {
killAllBootApplication.execute()
replaceSchema.execute()
applicationProcess = startBoot('zero-candidate', 'jp.bizreach.zero.candidate.CandidateStarter')
e2testPcLocal.execute()
}
finalizedBy(killApplicationTask)
}
← test タスクを継承したタスクを作成
← (1) 既に起動済のWebアプリのプロセスをKILL
← (2) テストデータ初期化
↑ (3) Webアプリを起動
← (4) シナリオテストの実行
← (5) WebアプリのプロセスをKILL
※ doAfter でやると、テスト失敗時に実行されない
44. テストの前準備が大変 解決策(続き)
task killAllBootApplication(type: Exec) {
executable 'sh'
args '-c', '/usr/bin/pgrep -lf java | grep "jp.*Starter" | cut -d" " -f1 | xargs kill -9'
ignoreExitValue true
}
task replaceSchema(type: Exec) {
workingDir "$rootProject.projectDir/script"
commandLine './replace_schema.sh'
}
task killApplicationTask << {
applicationProcess.destroy()
}
← Exec タスクで外部プロセスを実行できる
← 実行するディレクトリを指定
← finalizedBy から呼び出されるタスク
タスク内で別タスクを呼び出したり
45. テストの前準備が大変 解決策(続き)
def startBoot(subProjectName, mainClassName) {
ProcessBuilder builder = new ProcessBuilder("./gradlew", ":${subProjectName}:bootRun")
builder.redirectErrorStream(true)
builder.directory(project.projectDir)
builder.environment().put('MAIN_CLASS_NAME', mainClassName)
def process = builder.start()
InputStream processStdout = process.getInputStream()
BufferedReader reader = new BufferedReader(new InputStreamReader(processStdout))
def readyStatusString = 'Tomcat started on port'
def line
while ((line = reader.readLine()) != null) {
println line
if (line.contains(readyStatusString)) {
println 'application ready.'; break
}
}
return process
}
↑ サブプロジェクトのGradleタスク実行
← Webアプリの起動
← アプリの初期処理が完了するまで待つ
タスク内で関数を呼び出したり
47. スマホでテストしたい 解決策
GebConfig.groovy
def mobileEmulation = new HashMap<String, String>()
mobileEmulation.put("deviceName", "Apple iPhone 5")
def chromeOptions = new HashMap<String, Object>()
chromeOptions.put("mobileEmulation", mobileEmulation)
def capabilities = DesiredCapabilities.chrome()
capabilities.setCapability(ChromeOptions.CAPABILITY, chromeOptions)
driver = new ChromeDriver(capabilities)
driver.manage().window().setSize(new Dimension(320, 568))
← デバイス名を指定(WebDriver依存)
← ChromeDriver作成時に capabilities を指定
← 画面解像度の指定
49. スマホとPC両方でテストしたい 解決策
build.gradle
task('e2testMobileLocal', type: Test) {
systemProperty 'enableE2eMobileTest', 'true'
filter {
includeTestsMatching "jp.bizreach.zero.common.scenario.mobile.*"
}
}
task('e2testPcLocal', type: Test) {
systemProperty 'enableE2ePcTest', 'true'
filter {
includeTestsMatching "jp.bizreach.zero.common.scenario.pc.*"
}
}
← シナリオテスト(スマホ用)タスク
← テストするパッケージを指定
← シナリオテスト(PC用)タスク
← テストするパッケージを指定
タスク毎にテスト実装パッケージを分ける
60. 全画面キャプチャが崩れる 解決策
def captureFull(label, prop = [:]) {
capture label
def fixedHeaderHeight = 60
def fixedFooterHeight = 0
if (prop['footer']) {
fixedFooterHeight = prop['footer'].getHeight()
}
def shootingStrategy = new CustomizeViewportPastingDecorator(
new SimpleShootingStrategy(), fixedHeaderHeight, fixedFooterHeight).withScrollTimeout(100)
def shot = new AShot()
.shootingStrategy(shootingStrategy)
.takeScreenshot(driver)
def image = shot.image
File outputfile = new File(getReportGroupDir(), 'FULL-' + label + ".png")
ImageIO.write(image, "png", outputfile)
}
https://gist.github.com/naoki-iwami/4cf5e6aed62b39bb7fd15dbf5adc0940
← ヘッダは60pxで固定
← フッタは画面に応じて変わるので
引数に [ ‘footer’: $(‘.footer’) ]
のように渡す
↑ ヘッダとフッタの高さを指定して
ShootingStrategy を作成