ラベル テスト の投稿を表示しています。 すべての投稿を表示
ラベル テスト の投稿を表示しています。 すべての投稿を表示

2013年4月11日木曜日

Conditionのカスタム

ちょっと前にまとめた Wait.until()と、その引数の condition. あの時まとめた既存の物じゃ, 正直今やってるテストがカバーしきれないと思いカスタムしてみたのでメモメモφ(・ω・ )

Conditionのカスタム


といっても正直コピペなんだけどね…

1. 状況

対処したい状況は, 「画像リソースでチェックボックスを表現してる. しかもAjaxで表示を書き換える仕様」といった状況. 
当然 elementToBeSelected系列は使えないし, stalenessOfもページがリロードされるわけでもないから使えない.

なので, 「チェックされているボックスの画像が, x枚になるまで」待機, みたいな形にしてみた.

2. メソッド

ベースにしたのは titleIs. 一番シンプルだし, 戻り値 Booleanだし.
  public static ExpectedCondition<Boolean> countBy(final By by, final int expectedSize) {
    return new ExpectedCondition<Boolean>() {
      public Boolean apply(WebDriver driver) {
        return driver.findElements(by).size() == expectedSize;
      }
    };
  }
本当にちょびっとの改造. 引数は Byロケーターと期待する数.
なんてことはなく, titleIsで「タイトルが引数と一致する」という条件を書いてた部分を, 「Byで指定したWebElementの size()が, 引数の expectedSizeに一致する」に書き換えただけ. この程度なら意外と簡単.
ちなみにちゃんとロケーターを引数にして, 中で driver.findElements()で取得しなおしてやりましょう.
WebElementや List<WebElement>を直で渡そうとすると, 取得のし直しがない = ページの方は変更があっても size()変わらない = ちゃんと動かない なんていうただのストッパーが出来上がります.




とまぁこんな風に近いメソッドをひっぱり出してきてちょこっと改造してやるだけでも, 一層テストしやすくなるかも. +(・ω・0)*

2013年3月15日金曜日

Wait.until()で指定できる状態の種類

Selenium2でテストを書いていく際, 何かとお世話になるのが Wait<WebDriver>.until(). 「引数に指定した状態になるまで」待機してくれるのだが, この状態の指定のメソッドが多すぎる上に, いちいち英語読むのがダルげふんげふん説明が単調なのでまとめてメモメモφ(・ω・ )
ちなみに 「何言ってんだコイツ?(・д・`)」と感じられたら, こちらの本家へジャンプ.
またこちらでメソッドの中身を見るのもいいかも.

Wait.until()の指定できる「状態」


1. 準備


・インポートと宣言

import org.openqa.selenium.support.ui.Wait;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;

WebDriver driver = new ChromeDriver();
Wait<webdriver> wait = new WebDriverWait(driver, 10);
まずは準備. newする際に, 待機を指示する WebDriverの指定と, 最大待機時間を宣言. 今回は 10秒を指定. もちろん 10を 20に置き換えれば最大 20秒待機となる. この待機時間を過ぎると,
org.openqa.selenium.TimeoutException
が発生し, テストはそこで failとなる.
逆にこの最大待機時間内に conditionが整えば, わざわざ最大待機時間まで待機せずに先に進んでくれる.
Thread.sleep(10000);
とかするよりよっぽどスマート.


・until

wait.until(ExpectedCondition condition);
wait.until()を呼び出す. この引数に以下に紹介する conditionを設定して渡してやればOK.
たとえば titlesで状態を設定したいなら,
wait.until(titles("期待するタイトル"));
としてやる.
使い方自体はシンプルなのだが, 問題はこの「期待する状態の設定」の方になる.


2. conditionの設定

・titles

public static ExpectedCondition<java.lang.Boolean> titleIs(java.lang.String title)
「ページのタイトルが titleに一致するまで」待機.
・titleContains

public static ExpectedCondition<java.lang.Boolean> titleContains(java.lang.String title)
「ページのタイトルに titleが含まれるまで」待機.
・presenceOfElementLocated

public static ExpectedCondition<WebElement> presenceOfElementLocated(By locator)
「引数に指定した locatorがDOMに現れるまで」待機. ただし待機終了時に, 必ずしもそれが画面に表示されているとは限らない.
・visibilityOfElementLocated

public static ExpectedCondition<WebElement> visibilityOfElementLocated(By locator)
↑に近く, 「引数に指定した locatorがDOMに現れるまで」待機.こちらは画面に表示されたうえで, それが0より大きい幅高さを持って存在している状態になるまで待機する.
・visibilityOf

public static ExpectedCondition<WebElement> visibilityOf(WebElement element)
visibilityOfElementLocatedに近いが, こちらは「引数に指定した elementがDOMに現れるまで」待機. ”画面に表示される”の定義も visibilityOfElementLocatedと同じ.
・presenceOfAllElementsLocatedBy

public static ExpectedCondition<java.util.List<WebElement>> presenceOfAllElementsLocatedBy(By locator)
「引数に指定した locatorが, ページ上に少なくとも一つ存在するまで」待機. 見えるかどうかは問わないらしい.
・textToBePresentInElement

public static ExpectedCondition<java.lang.Boolean> textToBePresentInElement(By locator, java.lang.String text)
「引数に指定した textが, 指定した locatorに textとして現れるまで」待機. 要するに指定した locatorでgetText()したら textがとれるようになるまで待機?
・textToBePresentInElementValue

public static ExpectedCondition<java.lang.Boolean> textToBePresentInElementValue(By locator, java.lang.String text)
↑に近いが, こちらは「引数に指定した textが, 指定した locatorの valueとして現れるまで」待機.
・frameToBeAvailableAndSwitchToIt

public static ExpectedCondition<WebDriver> frameToBeAvailableAndSwitchToIt(java.lang.String frameLocator)
「指定した frameLocatorに WebDriverが移動するまで」待機?そもそもframeがよくわかってないし, ここを見た分には, このメソッドでそのまま移動しそうな switchTo()が呼ばれてるけどどういうことなんだろう…
・invisibilityOfElementLocated

public static ExpectedCondition<java.lang.Boolean> invisibilityOfElementLocated(By locator)
今度は「引数に指定した locatorが, 見えなくなるかDOM上からいなくなるまで」待機. "見えなくなる"の定義はないのかな…(・ω・`)
・invisibilityOfElementWithText

public static ExpectedCondition<java.lang.Boolean> invisibilityOfElementWithText(By locator, java.lang.String text)
「 textで指定した要素が, locatorで指定したエレメントから, 見えなくなるかDOM上からいなくなるまで」待機, …でいいのかな.
・elementToBeClickable

public static ExpectedCondition<WebElement> elementToBeClickable(By locator)
「 locatorで指定したエレメントが, 画面上に表示され, かつクリックできる状態になる」まで待機.
・stalenessOf

public static ExpectedCondition<java.lang.Boolean> stalenessOf(WebElement element)
「 elementがDOMに付属しなくなるまで」待機. ページのリロードなどでDOMが置き換わるなどするときに, "指定した elementがDOMからちゃんと消えるまで"待つ, ってこと? 参考
refreshed

public static <T> ExpectedCondition<T> refreshed(ExpectedCondition<T> condition)
このメソッド…無くなってる…?まぁ正直どういう動きか微妙に想像つかないけど(・ω・`)
・elementToBeSelected

public static ExpectedCondition<java.lang.Boolean> elementToBeSelected(WebElement element)
「指定した elementが選択された状態になるまで」待機. チェックボックスとかに使うんだろうけど, どこで判断してるんだろう…
・elementSelectionStateToBe

public static ExpectedCondition<java.lang.Boolean> elementSelectionStateToBe(WebElement element, boolean selected)
ほぼ↑と同じ気が…. 「指定した elementが selectedで指定した状態に選択 された/されていない 状態になるまで」待機. こっちで統一してもいい気がする…. 説明文とかコピペを疑うし…
・elementToBeSelected

public static ExpectedCondition<java.lang.Boolean> elementToBeSelected(By locator)
↑の locator版?「 locatorで指定したエレメントが選択された状態になるまで」待機, 当たりか. 説明すら放棄されましたorz
・elementSelectionStateToBe

public static ExpectedCondition<java.lang.Boolean> elementSelectionStateToBe(By locator, boolean selected)
「 locatorで指定したエレメントの選択状態が, selectedで指定した状態になるまで」待機, でいいのかな? もはや説明不要といわんばかりの勢い
・alertIsPresent

public static ExpectedCondition<Alert> alertIsPresent()
おそらく「アラートが出るまで」待機. 見たまんまでも説明がないと不安になりますよ…('A`)
not

public static ExpectedCondition<java.lang.Boolean> not(ExpectedCondition<?> condition)
シンプルに否定. 「引数に指定した conditionの notの状態になるまで」待機. …ってこのメソッドも無くなってるし( ゚д゚ )使えそうとか思ったのに…(;ω;`)


とりあえず以上. なんか無くなってるのか違うとこ見てるのかわからないのが2つほどあったけど…






枠とか試してみたけど…レイアウトって難しい(・ω・`)

2013年3月13日水曜日

WebElementをもっと楽に探したい

テストを作っているとよく出てくる問題が
「同じようなタグの要素がいっぱいある…」「どう1個だけを指定したらいいんだ…」
なんて状態. リストが複数あるなかその中の要素を1個だけ選べとか言われても下手するとBy.xpath()使っても20や30も Listになって出てきたり, そもそもリスト以外の要素が引っかかったり…
そんな時だいぶ楽になる要素の探し方.

子要素の探索

要するにリストのように全く同じ要素が複数作成されるときは, 親要素に入れて管理している場合がほとんど(だと思う(・ω・`)). ならば親要素を特定してからその中を探索してやればすっごい楽になるよね!というお話. まぁ単純な話です. ちなみにコード中に出てくる Byについてはこちらにもメモがございます(宣伝
たとえば, 
<div class="parent1">
  <span class="child"></span>
  <span class="child"></span>
</div>
<div class="parent2">
  <span class="child"></span>
  <span class="child"></span>
  <span class="child"></span>
</div>
なんて要素の階層があったとする.
2つ目の divの中の2つ目の spanを探したい時, 直接探そうとするとかなりめんどくさい.
なので,
WebElement parent = driver.findElement(By.className("parent2"));
WebElement child = parent.findElemens(By.className("child")).get(1);
と, 先に親エレメントを特定してから, その下のエレメントを探してやることができる. コード的にだいぶ意味が通りやすくなるはず. なおもちろん driverは WebDriver.
これを「class="child"だけで見れば 4番目だから Listで取得して .get(3)だ!」なんて考えて
WebElement child = driver.findElement(By.className("child")).get(3);
こんなコードを書くとまず他の人には伝わらない. まして上の divに要素増えた時には…
Σ(||゚Д゚)ヒィィィィ

また, まとめて
WebElement child = driver.findElement(new ByChained(By.className("parent2"),By.className("child"))).get(1);
なんていう書き方も. これは
new ByChained(By locator, …)
を用いることで, 順に探している(らしい).


ただ問題はここまでのこと,  By.xpath()を使うとなんかうまくいかない…(・ω・`)なんでだろ







一番楽かつ安定なのは classなりわかりやすくつけて指定してやることなのよね…

2013年2月28日木曜日

Cookieをaddして「続きからはじめる」

テスト作成中, ログインフォームをすっ飛ばして途中からテストを開始する準備がしたかったので

addCookieのちっちゃな落とし穴

ログイン情報を Cookieに持たせているので, 途中からはじめるなら
「addCookieとかあったよなー」
import org.openqa.selenium.Cookie;
  @Before
  public void setUp() throws Exception {
        driver.manage().addCookie(new Cookie(key, value));
なんてぼんやりaddしてたら
org.openqa.selenium.WebDriverException: Could not set the cookie (WARNING: The server did not provide any stacktrace information)
なんて怒られた.
どうやら「まだページ準備できてねーよどのページにCookie作る気だよ!щ(゚Д゚#щ)ゴルァ!」
とかいうことらしい.
なので
import org.openqa.selenium.Cookie;
  @Before
  public void setUp() throws Exception {
        driver.get(適当なページ);
        driver.manage().addCookie(new Cookie(key, value));
と, 何かログイン前に入れるトップページあたりを一度表示してやると addできる.
さすがに Googleトップとかに addしても意味無いけど.

これでaddCookieできますた.




Cookieよくわかんね('A`)

2013年2月26日火曜日

fakeApplicationを使ってテストの初期値を作ろう

Selenium2でテストを続けるうちに出てきた問題.
DBの初期値やらリセットやらどうしよう…

前にjUnitでテストの段階わけのようなことができたので
「じゃぁ@Beforeでデータ入れて@Afterで消せばいっかなー(・з・)」
なんて考えてたら…

fakeApplication()で本体のメソッドを使う

当然本体ではDBにアクセス→参照やら挿入やらやってるもんだから, このメソッドもらえないかなーなんて考えてとりあえずテストの中で呼ぶ

→ There is no started application

…('A`)なんだこれ
どうやらテストは jUnit, 本体は Playの領分らしく, そのままではまたいで呼び出すことはできないらしい. あらめんどくさい. 呼び出せるアプリが起動していないから There is no started application(開始しているアプリはありません) ってことなのか?
そこで見つけたのが fakeApplication()など play.test.Helpersシリーズ. どうやら本体を仮に動かして中のメソッドをいじれるようにしてくれるっぽいという印象だけの理解('A`)
これ使えばわざわざテスト用に起動しなくていいのかも…
import static play.test.Helpers.fakeApplication;
import static play.test.Helpers.running;

running(fakeApplication(), new Runnable() {

      @Override
      public void run() {
         // 初期値作るメソッドやらなんやら
      }
    });
こんな感じ.

ただし今回は, confファイルで使うテーブルを指定しており, これをテスト用のテーブルを使う, と一緒に宣言を変えたいという追加設定.
最初は「別の confを宣言するのか?」なんて考えてまた迷走.
結局たどり着いたのが,
Map<String String> map = new HashMap<String String>();
map.put("confの中で使うテーブルを宣言している項目", "テスト用テーブルの設定");

running(fakeApplication(map), new Runnable() {
// 以下略
Mapを使ってアプリ起動時の application.confを一部書き換えて読み込む方法.
例えば db.default.driverなんていう, 使用する DBの Driverの宣言. これをテスト時には別のものに書き換えたい!なんてときは
map.put("db.default.driver", "別のドライバ")
としてやれば, fakeApplication()で呼ばれるアプリ中では "別のドライバ" を使ってくれる. まぁテスト時だけドライバ変えるのも珍しすぎる気はするけど… いい例えが思いつかなかった…
なお複数 putしていっぱい書き換えることも可能.
そして, 元になるのは通常起動時に読まれる application.confのはず.


そして git嫌い('A`)

2013年2月25日月曜日

By.xpath中心のメモ

Selenium2でテスト作成時, 嫌でも使わなければならないものが findElement(By).
このByについてメモメモφ(・ω・ )

Byの指定

WebDriver.findElementは今開いているページからその引数で指定した要素( WebElement)を返すメソッド. なのでこの引数を正しく指定しないとエラーに泣くことになる.


By.id("id")
そのままidで要素を探す. なので必ずひとつで返るはず. 安定感は抜群.
By.className("class")
classで探すときは .class()ではなくこれ. っていうか .class()なんてない. 入力補完を使っていればまず無いけど, .id()等で考えが固まっているとつまらないミスでモニターとにらめっこすることになる.
By.name("name")
nameで探す. id以外は複数返る恐れがあるので注意. 複数あるはずなのに .findElement()で探した場合, ページソースを上から探して最初の要素だけ返ってくる. ちゃんと全部みたいなら
List<webdriver> elements = WebDriver.findElements(By by)
を使いましょう.
By.linkText("text")
ちょっと注意. 名前の通り<a>タグで囲まれたリンクのテキストだけを探す. ボタンの文字なんかは検索対象外なので注意.

By.xpath(String path)
この String pathの部分書き方が今回のメイン. たとえばこんなボタンが html上にあったとする.
<button class="btn" onclick='history.back()'>キャンセル</button>
なんてことは無いキャンセルボタン. ただidが振られておらず, classもありきたりな名前でもしかしたら複数取得されるかもしれない.
これを指定したいときは
WebElement element = driver.findElement(By.xpath("//button[@class='btn' and @onclick='history.back()']"));
となる
// → \\ではないので注意
button[条件] → 探したいのが<button>なのでbutton[条件]. もちろん<input>を探したいなら input[条件]となる.
@class='btn' → そのまま, <button>の中の要素 class="btn"を指す. @が無いとバグるので注意.
and → 複数の条件を設定する際に使う. すなおに and.
@onclick='history.back()' → メソッドだろうとお構いなしに条件に指定できる. まぁ文字列見てるとかなんだろうけど.
以降も and @~で条件をどんどん絞り込める.

これで指定できるはず. 一番いいのは idつけてやれば一意に定まるだろうけど, 自分じゃ修正に手が出せないときなんかはこれで. もちろん条件を増やせば絞り込めるけど, 特に classを複数設定しているような要素の場合, スペースや全半角でも条件がずれるので注意.



読みにくっ('A`)

2013年2月22日金曜日

Selenium2のテスト時間短縮?

前々回あたりでSelenium2のテストを書き始めたが, 毎回毎回ブラウザを立ち上げては消すのがなんとも時間がかかる.
なのでせめて一つのテストファイルの中ではブラウザの立ち上げ→終了は1回にして, 同じウィンドウを使い回せないかと考えた.
これでいいんだろうか…('A`)


Selenium2のテストの余計な時間の短縮


正直8割 jUnit4による解決

  private static WebDriver driver;

  @BeforeClass
  public static void setUpBeforeClass() throws Exception {
    driver = new FirefoxDriver();
  }

  @Before
  public void setUp() throws Exception {
    driver.get(initUrl);
  }

  @After
  public void tearDown() throws Exception {
    driver.manage().deleteAllCookies();
  }

  @AfterClass
  public static void tearDownAfterClass() throws Exception {
    driver.quit();
  }

Firefox版. 単純にjUnitのアノテーションに頼れば何とかなんじゃね?というだけのお話.
アノテーションのおかげで

@BeforeClass → (@Before → @Test → @After) * n → @AfterClass

という順序で呼ばれるので

ブラウザ起動 → (初期状態作成 → テスト → 事後処理) * n → ブラウザ閉じる

とすればいい.
コード的にも前々回使ったコードがほとんど.
driver.manage().deleteAllCookies();
このメソッドで作られてしまったクッキーを削除しているくらい. これを@Afterで行なうことで各テストで作られたクッキーを削除してやる.
@Beforeのところでテストしたい画面の初期状態を作ってやれば, 一つのテストファイル中は同じウィンドウを使いまわしてテストができるはず.

これで毎回テストメソッドごとにブラウザ立ち上げては閉じるなんて時間が大幅にカットできる.
実際変更前のテストごとにウィンドウを開く方法だと6分かかっていたテストも, 3分強程度まで短縮できている.
せいぜい30~40程度ののテストでこれなのだからさらにテストが増えたときの差は大きいはず!
+(・ω・0)*



どうみてもSelenium2の問題ではありません. 本当にありがとうございましたorz

2013年2月20日水曜日

PlayのアプリをSelenium2でテスト

やられた…
PlayとSeleniumって仲ええんやん…
最初からPlayの中にSeleniumいてるんやん…orz
しかしいざ本体のプロジェクトの中で以前と同じようにテストを作る→jUnit Test

…なんか真っ赤になってるー

妙なエラー吐いてテストしてくれなくなりましたorz

Playのアプリ本体にテストを入れ込む

A. エラーの原因


要するに「テストプロジェクト」を別に立てずに一括で管理したいなー(チラッ という要望から入り込んだ今回の泥沼.. 冒頭の通りただ本体に放り込んだら…
java.lang.NoSuchMethodError: org.apache.http.conn.scheme.Scheme.<init>(Ljava/lang/String;ILorg/apache/http/conn/scheme/SchemeSocketFactory;)V
 at org.openqa.selenium.remote.internal.HttpClientFactory.getClientConnectionManager(HttpClientFactory.java:59)
 at org.openqa.selenium.remote.internal.HttpClientFactory.<init>(HttpClientFactory.java:48)
 at org.openqa.selenium.remote.HttpCommandExecutor.<init>(HttpCommandExecutor.java:117)
 at org.openqa.selenium.remote.service.DriverCommandExecutor.<init>(DriverCommandExecutor.java:46)
 at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:165)
 at org.openqa.selenium.chrome.ChromeDriver.<init>(ChromeDriver.java:107)
 at SampleTest.setUp(SampleTest.java:21)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
 at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
 at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30)
 at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
 at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

長い・・・
正直1行目しか読んでない.
「そんなメソッド知らねーよ( ・A・)=Cく ´Д`;)」とか言われたので何か足りないのかと考えて探し回った挙句たどり着いた答えがここの一番下の書き込み.
なんかダブって競合してんじゃね?
Playのバージョンに依るのかはわからないけど, Playプロジェクト作成時, なにやらファイルをいくつかダブらせてパスをお通しになるらしい.(尚この時点での使用verは2.1-RC1)
なのでビルドパスから外してやりましょう. ※1
外すのは
commocs-codec.jarの1.3
htmlclient.jarの4.0.1
httpcore.jarの4.0.1
もちろん他との兼ね合いも考慮すべきだろうけど, 今回はこの3つにverのこだわりは無いので古いほうをカット.

これでOK.

B-1. 実験(適当なPlayプロジェクトの作成)

実際に試してみた Playも最新2.1.0にアップデート
まずは適当にPlayプロジェクト作成→eclipseプロジェクトにする.
プロジェクトを作りタイところでcmdを開き
play new SeleniumTest
ちょっと長いけど前にまとめて?いるので今回はさらっと
SeleniumTestなる適当なプロジェクトを作成. cdコマンドかなんかで作ったプロジェクトに入ってから
play eclipse
eclipseプロジェクトとして認識してもらえるようにする.
ついでに
play run
して走らせておく. でないとテストできない(・ω・`)
これで localhost:9000 にPlayのチュートリアルを表示し続けるアプリが起動しているはず.
走らせたら今度は eclipseでプロジェクトを importする.

B-2. 実験(テスト作成)

importに成功したらまずやるのはビルドパスの修正. 上に述べたとおり余計なパスを削除する.
プロジェクトのビルドパスを開き,
commocs-codec.jarの1.3
httpclient.jarの4.0.1
httpcore.jarの4.0.1
の3つを探して Removeする. 数が多くて目が痛くなる…('A`)

削除したらテストコードの作成
testディレクトリなんて折角作ってくれているしここに入れましょう. (play testコマンドに反応するので注意)

テストコードはシンプルに
public class SampleTest {

  WebDriver driver;
  String url = "localhost:9000";

  @Before
  public void setUp() throws Exception {
    driver = new FirefoxDriver();
    driver.get(url);
  }

  @After
  public void tearDown() throws Exception {
  }

  @Test
  public void test() {
    assertTrue(driver.getPageSource().contains("Welcome"));
  }
}

ただ localhost:9000をFirefoxで開いて 「Welcome」の文字列が表示されているかのテスト.

B-3. 実験(実行)

コードができたら 実行→jUnit test.

ようやっと走ってくれました.







無駄に遠回りしたのにまとめると実は3行でおkレベルだったときの疲労感…('A`)疲れた




※1 : 02/26追記 : だからといって全部が全部「ダブってんじゃね?」とか疑うのはやめましょう. まずはほんとに無いのか確かめましょう. 地獄を見ます('A`)

2013年2月12日火曜日

Selenium2使ってみた

Playで作ってるものも形になってきたのでいよいよテスト. そこで挙がった名前が

Selenium2

しかし「名前ちょっと聞いたことあるかなー」程度の知識だったのでイロイロ調べて簡単に使ってみた記録をメモメモφ(・ω・ )

Selenium2使ってみた

1. そもそもSelenium2って?

SeleniumはWebアプリケーションのUIテスト用フレームワーク. 簡単に言えば, 「ページ上でこう動いて欲しいなーなんて動きをコードで作って自動でテストしてくれる」ようなものを作れる. なんかニホンゴヘンダorz
2があるからには1があり, そのバージョンアップ. 具体的にはSimon Stwart氏作成の「WebDriver」をSelenium1に統合したものが2ということらしい. WebDriverの持つオブジェクト指向なAPIを取り込んでさらにできるようになったらしい. 1を知らないからなんとも言えないけど.
以上薀蓄

2. 言語と対応ブラウザ

言語は
javaC#RubyPython
の4種類. 1の時代にはPHPとかもあったらしいけどWebDriver統合時にWebDriverの方が対応していなかったため切り捨てられたとか(・ω・`)

また対応ブラウザは
Firefox
Google Chrome
IE
Opera
Apple iPhone
Android ブラウザ
Safari(ver2.21.0より)
となかなか豊富.BlackBerryは対応予定とだけ見たけど未確認. もう対応してるのかね('A`)
まぁ基本は上3つか.
ちなみにこれらはもちろんこのメモ書いてる時点でのお話.
この時点でのバージョンは2.29.0
マイナーチェンジしすぎだろ・・・(・ω・`)

3. Selenium-ServerとWebDriver

この辺正直よくわからないヽ('A`)ノ
Selenium-Serverは実際にブラウザを動かす係. これが無いとテストできない.
対応ブラウザにいっぱい書いたが, 実は標準ではIE と Firefox しかサポートしていないという事実. Opera/Chromeを使うなら別途サードパーティ製のServerが必要になるので注意. まぁ割と簡単な手順なんだけどね.
特にChromeに関しては比較的安心して使えるとかなんとか.
iPhoneとAndroid, Safariは未確認.

4-a. 入手

ここからまずは必要なファイルを入手.
必要なのは
Selenium-java-"ver".zip
Selenium-server-"ver".zip
の2つ. "ver"の部分にはもちろん数字が入ります.
他にも先に書いたとおり, Chromeでテストするにはこちらから
chromedriver_win_"ver".zip
が必要. "ver"の部分がバージョンじゃなかったらごめんなさい. この時点では26.0.1383.0. バージョンか?これ.

2014/01/24 追記
何やら場所が変わったみたいです
こちら
上記のページの 「Download chromedriver」 の先になります
追記・2
きちんと最新をもらってこないと、
unknown command 'waitforalltabstostoploading'
でググることになりそうです orz

4-b. 導入

必要なものを入手したら解凍, 今度はテストプロジェクトを作る.
プロジェクトにSelenium-javaおよびSelenium-serverに入っているsrcs以外のjarをとにかくパスを通す. 適当にぶち込んで設定から読み込んでやればOK. この時両方に「libs」ディレクトリがあるが, 中身は同じなのでどちらかひとつを入れればいい.
具体的には
selenium-java-"ver".jar
Selenium-server-"ver".jar
Selenium-server-standalone-"ver".jar
libsディレクトリの中身全部
を全部通す.
すっごいにぎやかになります.
ついでにどこかにchromedriver.exeも放り込んでおこう. なるべくコード中でパスがわかりやすいところに.(自分はパスすっごい苦手なので直下に「selenium」ディレクトリ作って全部放り込む始末orz)
これで準備はOK.

5. 簡単なコード1

ためしに簡単なコードで動きを見る. ちなみにもちろんJava. 他の言語書けない(・ω・`) テスト対象はPlayのWebアプリケーションなので, 事前に play runしておく.
public class Example {
  public static void main(final String[] args) {
    WebDriver driver = new FirefoxDriver();
    driver.get("http://localhost:9000");
    driver.quit();
  }
}
まだローカルでplayアプリを走らせている状態. 懐かしきmain関数.
3行目で使用するブラウザを選択. 今回はFirefoxでやってみる. これがWebDriver統合の強み?
4行目で立ち上げたブラウザにURLを入力, getしてもらう. このgetメソッド, ブラウザにきちんと表示されるまで先に処理が進まないので注意. 逆に言うとこのメソッドが終わった時点で画面は表示されているものとしてこの先の処理を書いていける. 実にありがたい.
5行目はブラウザの終了メソッド. これをしないといつまでもブラウザが居座り続けます. 今はいいけどテストがの量が増えたときは・・・Σ(||゚Д゚)ヒィィィィ

これを実行→JavaApplicationで実行するとFirefox起動→アプリ表示!→終了!!となる. まだテストには程遠い・・・
なお今回はPlayで作ったアプリのテストなので localhost:9000 だけど,  http://www.google.co.jpにするとグーグルのトップを開いて即終了します

6. 簡単なコード2

こんどはChromeでテストがしたい. ので4-b. にあるとおりドライバを用意しましょう.
コードもjUnitTestcaseを使用. jUnit4で.

@Test
public class Example {
  public static void main(final String[] args) {
    System.setProperty("webdriver.chrome.driver", "selenium/chromedriver.exe");
    String strURL = "localhost:9000/";
    WebDriver driver = new ChromeDriver();
    driver.get(strURL);
    WebElement element = driver.findElement(By.id("id"));
    element.sendKeys("id");
    element = driver.findElement(By.id("password"));
    element.sendKeys("pass");
    element.submit();
    assertThat(driver.getTitle(), is("ホーム"));
    driver.quit();
  }
}
ほぼまんまiUnit.  importするのは
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
のあたり. なんか isみたいに打ち消し線出るけど大丈夫なのだろうか….

なおこのアプリは localhost:9000 を入力すると, idと パスワードを入力してログインするだけの簡単なページに出る.

4行目が拾ったchromedriverの宣言. これが無いとChromeではテストができない. まだ直下/seleniumにしまってあるorz
6行目で今度は WebDriverは ChromeDriverで newする.
7行目で getしたらいよいよテスト.
8行目,  findElement()は引数の条件に一致する, 現在開いているHTMLの中で最初に見つかった要素を返す.
引数の By.id("id")は, HTMLタグの id のこと. 他にも name等でも検索できる. がそのHTML中に該当するものが無ければエラーが返るので注意. ここでログイン idを入力するフォームを探している.
9行目 sendKey()は引数の値(文字列)を, 指定したフォームに入力するメソッド. この場合 8行目で見つけた idの入力フォームに "「id」 と入力する, というだけの動き.
この時も element が入力を受け付ける要素ではない場合エラーが返る.
10, 11行目も同様に, こんどはログインパスワードの入力フォームを取得 → 「pass」と入力.
12行目の submit()で elementのある formを submitする. この場合は最後に elementにはパスワード入力フォームが入っているので, これを子要素に持つ formがあればそれを submitする. やっぱりそんなものが無ければエラー. 何かとエラーが返ってくる.
13行目がいよいよチェック. ここまでの入力でログインが済んだはずなので, タイトルは ホーム になっているはず, というテスト.

これを今度は 実行→jUnitTestCaseで実行.
おなじみのウィンドウが登場し, テストの進行状況と結果を表示してくれるはず.

他にも, HTML文の中に指定の文字列が含まれているか探す
driver.contains()などもある.

とりあえずここまでで "それっぽく" Selenium2は動かせるはず.






次はどこをテストするかだなー(・ω・`)

2012年9月25日火曜日

sinonを使ったjavascriptのテスト

前回でjavascriptの開発環境もできたので, いざコーディング!


・・・まではよかったのだが・・・
ここで課題が2つ.

1. テスト(特に通信系)どう書くの?
2. コーディング規約は?

とりあえず普通のテストはいいとして…通信…Mock?
とか調べてたら出てきたのが Sinon.js
ライブラリのように使え, いろいろと内蔵しているじゃじゃ馬
いろいろありすぎて手に負えませんorz

とりあえずjQuery.ajaxが使いたかったので, 有効そうな2つをピックアップ.

A. FakeServer
名前の通り偽者のサーバを立てる. 

var sinonTestCase = sinon.testCase({
    setUp : function() {
        this.server = sinon.fakeServer.create();
    },
    tearDown : function() {
        this.server.restore();
    },
    "test server":function () {
        this.server.respondWith("GET", "/some/article/comments.json",
                                [200, { "Content-Type": "application/json" },
                                 '[{ "id": 12, "comment": "Hey there" }]']);
        var callback = sinon.spy();
        jQuery.getJSON("/some/article/comments.json", callback);
        this.server.respond();
        sinon.assert.calledWith(callback, [{ id: 12, comment: "Hey there" }]);
    },
});

TestCase("Fakeserver test case", sinonTestCase); 

これで "Fakeserver test case" という名前のテストクラスが出来上がる.
setUp でfakeServerを立ち上げ
tearDown でその偽サーバを破棄している.
偽者とはいえ通信系, 破棄しておかないと特に通しのテストの時に…
"test server" がテストクラス名. 名前は test から始めないとテストとして認識されないので注意.
sinon.fakeServer.responsedWith で偽サーバのresponseの内容を決める.
sinon.spyは仮のcallback関数. これをテストしたい関数に組み込むことで, 後々sinon.assertが使えたりする. 詳しくはAPIで
server.respond() を呼ぶことでfakeServerが応答し, 先に設定したresponseを返す.

これにより, callbackにfakeServerからの応答が入る.

あとはsinon.assertを用いてきちんと応答が受け取れたことを確認する.


…すっごい不安だ('A`)

B. Stub
あるクラスの ある関数を 一時的に置き換える
stubのよるテスト例
var sinonTestCase = sinon.testCase({

    "test with stub" : function() {
        var json = [{"hoge":"hoge","foo":"foo","bar":"bar"},
        {"hoge2":"hoge2","foo2":"foo2","bar2":"bar2"},
        {"hoge3":"hoge3","foo3":"foo3","bar3":"bar3"}];

        var stub = sinon.stub(jQuery, "ajax");
      stub.yieldsTo("success", json);
  
        jQuery.ajax({
            success: function (data) {
                assertEquals(json, data);
            }
        });
       stub.restore();
    }
});

TestCase("Stub test case", sinonTestCase);

こちらは"Stub test case"クラス.
8行目にてstubを作成, 9行目にてjQuery.ajaxというメソッドの返り値を指定している.
11行目から実際にテストしたいメソッドを呼び, 値をチェックする.

こっちのほうが使いやすい気はした(・`ω・´) (キリッ
restore()は忘れずに.(setUp&tearDownに入れたほうがいい気がした(^ω^;).

参考:SinonAPI

明らかにAPIのが正しいのでそちらを参考に…


コーディング規約についてはまた後日…(・ω・)ノシ

AWS CDKで立てたEC2インスタンスのTimeZoneとかいじりたかった話

EC2を立てることはできたけど、立てたインスタンスは UTCのままだし設定ファイルとかいちいちscpしてくるのはダルい。 当初UserDataでなんとかしようとしたものの、「書く量がヤバいしメンテしにくい」と悩んでいたところ見かけたのが  AWS::CloudFormation:...