ブログBlog

    • Selenium WebDriverで実践的テストケースを作成する(Java編)

    • 2016年2月29日
    • Selenium WebDriver
皆さんこんにちは。 最近はすっかり在宅ワーカーになった中野です。 今回は、自動テストの代表格であるseleniumについて、 実践的なテストケースを作る目的で書きたいと思います。 seleniumにも、何種類か存在するのですが、取り上げるのは WebDriverについてです。 WebDriverはその名の通り、ブラウザをドライブ(操作)してくれます。 動かすために、スクリプトを書く必要がありますが、 簡単に始められるJavaを使ってみたいと思います。

環境設定

(1)eclipseをインストールします。 (2)seleniumのJarをライブラリに追加します。 ダウンロードサイトは以下です。 http://www.seleniumhq.org/download/ 解凍すると直下に selenium-java-2.52.0-srcs.jar selenium-java-2.52.0.jar libs配下にも複数のjarが展開されるので、それらを全て指定して下さい。

挙動確認

firefoxであれば、上記の設定のみで動かすことができます。 最もシンプルに以下のソースで挙動を確認してみましょう。 ・WebDriverSample.java
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;

public class WebDriverSample {
		
	public static void main(String[] args) {
		WebDriver driver = new FirefoxDriver();
		driver.get("https://www.google.co.jp/");
	}

}
実行すると、firefoxが起動して、googleのサイトが表示されます。 chromeを動かす場合は、他にchromedriverという実行ファイルが必要になります。 chromedriverは以下から取得できます。 http://chromedriver.storage.googleapis.com/index.html?path=2.21/ 今回はmacで動かしているので、 「chromedriver_mac32.zip」をダウンロードし解凍します。 chromedriverという実行ファイルになるので、 eclipseのプロジェクトの実行パス配下にdriverというフォルダを作り配置します。 ソースのfirefoxをchromeに置換し、 propertyにchromedriverを指定するという1行を加えて下さい。 変更した結果は以下のようになります。
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class WebDriverSample {
		
	public static void main(String[] args) {
		System.setProperty("webdriver.chrome.driver", "driver/chromedriver");
		WebDriver driver = new ChromeDriver();
		driver.get("https://www.google.co.jp/");
	}

}
実行して、chromeが起動すれば、成功です。

テスト準備

テスト対象のサンプルとして、以下のhtmlとjsを用意しました。 ・index.html
<!DOCTYPE html>
<html ng-app="App">
<head>
<meta charset="utf-8"/>
<title>seleniumSample</title>
<script src="angular.min.js"></script>
<script src="controller.js"></script>
</head>
<body ng-controller="Controller" >

  <div ng-show="loading">
    now loading...
  </div>

  <div ng-show="loadingfinish">
    会員登録
    <hr/>
    <form name="accountForm" ng-submit="onClick()" novalidate>

      <div>
        <label>お名前</label>
        <input id="user_name" name="user_name" ng-model="user.name" placeholder="お名前を入力" required />
        <br/>
        <span id="user_name_error" ng-show="accountForm.user_name.$error.required && buttonclick" style="color:#ff0000;" >お名前は入力必須です</span>
      <div>
      <br/>
      <div>
        <label>フリガナ</label>
        <input id="user_namekana" name="user_namekana" ng-model="user.namekana" placeholder="フリガナを入力" required />
        <br/>
        <span ng-show="accountForm.user_namekana.$error.required && buttonclick" style="color:#ff0000;" >フリガナは入力必須です</span>
      <div>
      <br/>
      <div>
        <label>性別</label>
        <input type="radio" ng-model="user.gender" ng-value="1" name="man" /><label>男性</label>
        <input type="radio" ng-model="user.gender" ng-value="2" name="woman" /><label>女性</label>
        <br/>
        <span ng-show="user.gender == null && buttonclick" style="color:#ff0000;" >性別は入力必須です</span>
      <div>
      <br/>
      <div>
        <label>メールアドレス</label>
        <input id="user_mail" name="user_mail" type="email" ng-model="user.email" placeholder="メールアドレスを入力" required />
        <br/>
        <span ng-show="accountForm.user_mail.$error.required && buttonclick" style="color:#ff0000;" >メールアドレスは入力必須です</span>
        <span ng-show="accountForm.user_mail.$error.email && buttonclick" style="color:#ff0000;" >メールアドレスの形式が正しくありません</span>
      <div>
      <br/>
      <div>
        <label>パスワード</label>
        <input id="user_password" name="user_password" type="password" ng-model="user.password" placeholder="パスワードを入力" required ng-minlength="6"/>
        <br/>
        <span ng-show="accountForm.user_password.$error.required && buttonclick" style="color:#ff0000;" >パスワードは入力必須です</span>
        <span ng-show="accountForm.user_password.$error.minlength && buttonclick" style="color:#ff0000;" >パスワードは6文字以上入力して下さい</span>
      <div>
      <div>
        <label>パスワード(確認用)</label>
        <input id="user_password_confirm" name="user_password_confirm" type="password" ng-model="user.password_confirm" placeholder="確認用のパスワードを入力" required />
        <br/>
        <span ng-show="accountForm.user_password_confirm.$error.required && buttonclick" style="color:#ff0000;" >パスワード(確認用)は入力必須です</span>
        <span ng-show="user.password != user.password_confirm && buttonclick" style="color:#ff0000;" >パスワード(確認用)がパスワードに入力した内容と異なります</span>
      <div>
        <br/>
        <hr/>
        <input id="register_button" type="submit" value="登録" style="padding: 15px 40px;font-size: 1.2em;background-color: #000;color: #fff;border-style: none;"/>

      </div>
    </form>
  </div>

</body>
</html>
・controller.js
angular.module('App',[])
.controller('Controller',['$scope','$timeout',function($scope,$timeout){
  $scope.loading = true;

  $scope.init = function(){
   $timeout(
     function(){
       $scope.loadingfinish = true;
       $scope.loading = false;
     },0 //初期表示を遅らせるtimeを指定
   );
  }
  $scope.onClick = function(){
    $scope.buttonclick = true;
  }
  $scope.init();
}]);

画面は会員登録をイメージしたものになっています。 登録ボタンを押すと、入力項目のチェックを行い、未入力の項目や形式が不正な項目について アラートメッセージが出ます。 サーバサイドは実装していませんので、クライアントサイドだけの挙動になります。 また、入力制御にはangularjsを使っているため、事前にangular.min.jsを取得して、 同じ階層に配備して下さい。

スクリプトでできること

準備ができたところで、テスト用のスクリプトを書いていきたいのですが、 事前に、webdriverで実現できることを確認しておきましょう。 ・HTML要素の取得 : findElement ボタンやテキストボックスなどHTML要素を操作するため、webdriverではWebElementという要素を取得できます。 サンプルの「お名前」をidを指定して取得する場合 WebElement user_name = driver.findElement(By.id("user_name")); nameを指定して取得する場合 WebElement user_name = driver.findElement(By.name("user_name")); で実現できます。 ・クリック : click テストを行う上で、ボタンを押したり、要素を選択したりと、画面をクリックする操作が必要になります。 サンプルの登録ボタンをクリックする場合 WebElement register_button = driver.findElement(By.id("register_button")); register_button.click(); で実現できます。 ・入力 : sendKeys テストを行う上で、テキストボックスに入力を行いたい場合があります。 WebElementではキーを送り込むという操作ができます。 サンプルの「お名前」に「名前」と入力する場合 WebElement user_name = driver.findElement(By.id("user_name")); user_name.sendKeys("名前"); で実現できます。 ・クリア : clear テストを行う上で、テキストボックスに入力されている値を一旦、初期化(クリア)したい場合があります。sendKeysでは入力されている値をクリアすることができないため異なる操作が必要になります。 サンプルの「お名前」をクリアする場合 WebElement user_name = driver.findElement(By.id("user_name")); user_name.clear(); で実現できます。 ・テキスト取得 : getText 処理結果がテキストに表示されるため、テキストを取得したい場合があります。 サンプルの場合、アラートの文言をspan要素で表示していますが、この文言を取得するには WebElement user_name_error = driver.findElement(By.id("user_name_error")); String str = user_name_error.getText(); で実現できます。 ・テキストボックスに入力された値の取得 : getAttribute 今回のサンプルでは必要になりませんが、テキストボックスに入力された値を取得したい場合があると思います。 これは、残念ながらgetText()では取得できません。getTextはinnerTextを返却するためです。 そのため、getAttribute()というHTMLの属性値を取得するメソッドを使います。 サンプルの「お名前」から値を取得する場合 WebElement user_name = driver.findElement(By.id("user_name")); String str = user_name.getAttribute("value"); で実現できます。

テストパターン洗い出し

基本的な操作がわかったところで、 テストを作成していきましょう。 サンプルの会員登録画面では 入力項目に入力を行い、登録ボタンを押して処理を行います。 このことからテストの観点としては a)画面のレイアウトが仕様に沿ったものであるか b)画面の操作(タブの移動など)が仕様に沿ったものであるか c)画面の入力制御が正しく行われるか d)画面から入力した内容がDBに正しく登録されるか が想定できます。 ただし、dについてはサーバサイドのため、a,bについては今回の趣旨とは異なるため cについてフォーカスして話を進めます。 cについては項目ごとにどういった入力制御が存在するのか洗い出します。 実務では仕様書などを参考に作業することになるかと思います。
項目 入力制御
お名前 入力必須
フリガナ 入力必須
性別 入力必須
メールアドレス 入力必須
メールアドレスとしての形式が適切か
パスワード 入力必須
6文字以上
パスワード(確認用) パスワードと同じ
これで洗い出しは完了です。

テストスクリプト実装

テストの枠組みとして、JUnitを使います。 JUnitで以下のように枠組みを作りました。 ・ChromeDriverTest.java
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

import static org.junit.Assert.assertEquals;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class ChromeDriverTest{
	static WebDriver driver;
	
	@BeforeClass
	public static void setUp(){
		System.setProperty("webdriver.chrome.driver", "driver/chromedriver");
		//テスト先のサイトに接続(ブラウザが起動します)    
		driver = new ChromeDriver();
		driver.get("http://192.168.33.10");	
	}
	
	@AfterClass
	public static void tearDown() {
		//ブラウザを閉じます    
		driver.close();
    }
	
	@Test
	public void test(){
		//ここに具体的にテストを記載する
	}
		
}

アノテーションで@Testが付与されている箇所がテストを行うメソッドになります。 このパターンを増やしていく感じになります。 また、便宜上htmlはvirtualboxにapacheを建てて動かしています。 webdriverはhtmlファイルを直指定しても動かせますので、サーバを構築するのが面倒であれば ファイルを直接指定するように変更して下さい。(driver.get(“http://192.168.33.10”);のところ) では、お名前についてテストケースを追加してみましょう。 対象となるパターンは、 (1)未入力の状態で登録ボタンをクリックして、エラーメッセージが表示されるか (2)名前を入力するとエラーメッセージが表示されなくなること これを詳細なロジックにすると (1)は  (1-1)登録ボタンの要素を取得しクリック  (1-2)お名前のエラーメッセージ要素を取得しテキストを取得  (1-3)エラーメッセージが「お名前は入力必須です」であること (2)は  (2-1)お名前の要素を取得し、「名前」と入力  (2-2)登録ボタンの要素を取得しクリック  (2-3)お名前のエラーメッセージ要素を取得しテキストを取得  (2-4)エラーメッセージが空白であること となります。 上記を記載したソースは以下の様になります。
	@Test
	//(1)未入力の状態で登録ボタンをクリックして、エラーメッセージが表示されるか
	public void testNameRequired(){
		//(1-1)登録ボタンの要素を取得しクリック
		WebElement register_button = driver.findElement(By.id("register_button"));
		register_button.click();
		//(1-2)お名前のエラーメッセージ要素を取得しテキストを取得
		WebElement user_name_error = driver.findElement(By.id("user_name_error"));
		String str = user_name_error.getText();
		//(1-3)エラーメッセージが「お名前は入力必須です」であること
		assertEquals(str,"お名前は入力必須です");
	}
	
	@Test
	//(2)名前を入力するとエラーメッセージが表示されなくなること
	public void testNameInput(){
		//(2-1)お名前の要素を取得し、「名前」と入力
		WebElement user_name = driver.findElement(By.id("user_name"));
		user_name.sendKeys("名前");
		//(2-2)登録ボタンの要素を取得しクリック
		WebElement register_button = driver.findElement(By.id("register_button"));
		register_button.click();
		//(2-3)お名前のエラーメッセージ要素を取得しテキストを取得
		WebElement user_name_error = driver.findElement(By.id("user_name_error"));
		String str = user_name_error.getText();
		//(2-4)エラーメッセージが空白であること
		assertEquals(str,"");		
	}
	
これをeclipseで実行するとJUnitのRunnerが走り、全て成功になるはずです。 エラーメッセージのテストは上記で作成できるようになりましたので 他の項目についても同様に展開すれば完成です。

より実践的なパターンへの対応

基本的なパターンは確認できるようになりましたが、 実際アプリケーションを動かすと、処理に時間がかかる場合があります。 ・待ち時間が発生するパターンへの対応  便宜上、サイトの読み込みに時間がかかったとして controller.jsの10行目の「0」を「5000」に変更して、 再度テストを実行してみましょう。 その結果、 org.openqa.selenium.ElementNotVisibleException: element not visible … というエラーが発生しました。 これは、テストが実行されたタイミングでまだ、画面の要素が表示されていないため 起きてしまうエラーです。 そのため、要素が表示されるまで待ってあげる必要があります。 WebDriverWaitとExpectedConditionsというクラスがあるので これらを組み合わせて使うことでWaitを実現します。 例えば 登録ボタンが表示されるまで待つには以下のように記述します。
 		WebDriverWait wait = new WebDriverWait(driver, 5000);//待ち時間を指定
		By registerButton = By.id("register_button");
		wait.until(ExpectedConditions.visibilityOfElementLocated(registerButton));
 
これをsetUp()の後ろに追加して実行すると、 ブラウザ起動後、登録ボタンが表示されるまで各メソッドの実行が待機されるため エラーが発生しないようになります。 ・idやnameを付与していない場合への対応 WebDriverを使ったスクリプトはWebElementを取得することが起点になります。 各要素にはidやnameを付与することが前提条件になりますが、そうでない場合も起こり得ると思います。 そういった場合には、xpathが使えます。 xpathはXML文書内で、特定のノードの位置を相対的に示すことができるpathです。 xpathはchromeの場合、ブラウザ上で右クリックし、要素の検証からHTMLを表示、該当の要素を選択した状態で、右クリックで「Copy」→「Copy XPath」を選択すると簡単に取得できます。 サンプルでは、性別のradioボタンにidがないので、xpathで指定して要素を取得してみます。
			WebElement radio_man = driver.findElement(By.xpath("/html/body/div[2]/form/div/div/div/div/div/input[1]"));
		radio_man.click();
 
このソースで、男性が選択されます。 これらを応用することでかなりのパターンのテストケースが作れるようになったのではないでしょうか。 アプリを開発する際にはコストをかけず、テストを回していきたいものですね。 以上で、今回は終わりです。

この記事を書いた人 : 中野健一

一覧へ戻る

開発についてのお問い合わせはこちら

お問い合わせ

JOIN OUR TEAM

積極採用しています。私たちと一緒に働きませんか?