CLOVER🍀

That was when it all began.

Selenium WebDriverとMavenを使って、受け入れテストの自動化を行う

せっかくSelenium WebDriverを使ったのなら、Mavenの組み込みJettyを使って受け入れテストを自動化したいところ…。

って、この本の受け売りですけどね。

Apache Maven 3クックブック Javaソフトウェア開発のための特選レシピ集

Apache Maven 3クックブック Javaソフトウェア開発のための特選レシピ集

ただ、この本に載っているのは、SeleniumでもWebDriverではないので、ちょっと古い?あと、Jettyに関する設定もJetty 6で止まっている様なので、いくつか読み替えてみました。

作ったサンプルの構成は、こんな感じです。

webdriver-webapp-example/src/test/java/webdriver/webapp/example/WebDriverIntegration.java
webdriver-webapp-example/src/test/java/webdriver/webapp/example/SimpleLogicTest.java
webdriver-webapp-example/src/main/webapp/WEB-INF/web.xml
webdriver-webapp-example/src/main/webapp/WEB-INF/view/index.jsp
webdriver-webapp-example/src/main/webapp/WEB-INF/view/next.jsp
webdriver-webapp-example/src/main/java/webdriver/webapp/example/SimpleLogic.java
webdriver-webapp-example/src/main/java/webdriver/webapp/example/SimpleServlet.java
webdriver-webapp-example/pom.xml

*「webdriver-webapp-example」はプロジェクト名です

まずは、pom。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>webdriver.webapp.example</groupId>
  <artifactId>webdriver-webapp-example</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>webdriver-webapp-example Maven Webapp</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <build>
    <finalName>webdriver-webapp-example</finalName>
    <plugins>
      <!-- コンパイル設定 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.0</version>
        <configuration>
          <source>1.7</source>
          <target>1.7</target>
        </configuration>
      </plugin>

      <!-- Jetty Pluginの設定(integration-testフェーズ前後で起動/停止) -->
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>8.1.8.v20121106</version>
        <executions>
          <execution>
            <id>start-jetty</id>
            <goals>
              <goal>run</goal>
            </goals>
            <phase>pre-integration-test</phase>
            <configuration>
              <scanIntervalSeconds>0</scanIntervalSeconds>
              <daemon>true</daemon>
            </configuration>
          </execution>
          <execution>
            <id>stop-jetty</id>
            <goals>
              <goal>stop</goal>
            </goals>
            <phase>post-integration-test</phase>
            <configuration>
              <stopKey>stop</stopKey>
              <stopPort>9999</stopPort>
            </configuration>
          </execution>
        </executions>
      </plugin>

      <!-- integration-testフェーズでの実行設定 -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>2.13</version>
        <executions>
          <execution>
            <id>integration-test</id>
            <goals>
              <goal>integration-test</goal>
            </goals>
            <configuration>
              <includes>
                <include>**/*Integration.java</include>
              </includes>
            </configuration>
          </execution>
          <execution>
            <id>verify</id>
            <goals>
              <goal>verify</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-java</artifactId>
      <version>2.28.0</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

超長い(笑)。この時点で、ちょっと嫌に…。

まあ、依存関係にサーブレットAPIとJUnit、Seleniumを足しているのはいいとして…Jettyプラグインを使って結合テストの前にJettyを起動させ、終わったらシャットダウンするように設定しています。

      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>8.1.8.v20121106</version>
        <executions>
          <execution>
            <id>start-jetty</id>
            <goals>
              <goal>run</goal>
            </goals>
            <phase>pre-integration-test</phase>
            <configuration>
              <scanIntervalSeconds>0</scanIntervalSeconds>
              <daemon>true</daemon>
            </configuration>
          </execution>
          <execution>
            <id>stop-jetty</id>
            <goals>
              <goal>stop</goal>
            </goals>
            <phase>post-integration-test</phase>
            <configuration>
              <stopKey>stop</stopKey>
              <stopPort>9999</stopPort>
            </configuration>
          </execution>
        </executions>
      </plugin>

しかしまあ、「jetty-maven-plugin」って…。

続いて、falesafeプラグインを使って結合テスト時に実行するテストの設定。

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>2.13</version>
        <executions>
          <execution>
            <id>integration-test</id>
            <goals>
              <goal>integration-test</goal>
            </goals>
            <configuration>
              <includes>
                <include>**/*Integration.java</include>
              </includes>
            </configuration>
          </execution>
          <execution>
            <id>verify</id>
            <goals>
              <goal>verify</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

今回は、ファイル名が「Integration.java」で終わるものを結合テスト対象としています。

つまり、以下の動作を想定しています。

フェーズ 動作させるテストコード
単体テスト(test) ファイル名が「Test.java」で終わるもの
結合テスト(integration-test) ファイル名が「Integration.java」で終わるもの

作ったアプリの動作は、1度サーブレットが受けてフォワードする簡単なものです。

SimpleServlet -> FORWARD -> index.jsp
(サブミット後)
SimpleServlet -> FORWARD -> next.jsp

まあ、遷移先はパラメータ指定してますが…。

サーブレットは、中で簡単なJavaクラスを呼び出しているのでこちらを単体テストの対象にします。WebDriverは、ブラウザ越しにサーブレットをテストします。

用意したソースは、それぞれこちら。
src/main/java/webdriver/webapp/example/SimpleServlet.java

package webdriver.webapp.example;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/simple")
public class SimpleServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        String next = request.getParameter("next");
        if (next == null) {
            next = "index.jsp";
        }

        String value = request.getParameter("value");
        if (value != null) {
            request.setAttribute("decoratedValue", new SimpleLogic().decorate(value));
        }

        request.getRequestDispatcher("/WEB-INF/view/" + next).forward(request, response);
    }
}

src/main/javawebdriver/webapp/example/SimpleLogic.java

package webdriver.webapp.example;

public class SimpleLogic {
    public String decorate(String value) {
        return "***" + value + "***";
    }
}

src/main/webap/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

</web-app>

src/main/webapp/WEB-INF/view/index.jsp

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
  <head>
    <title>index.jsp</title>
  </head>
  <body>
    <p>入力フォーム</p>
    <form action="/simple" method="get">
      <label>入力値:</label><input type="text" name="value" />
      <input type="hidden" name="next" value="next.jsp" />
      <br />
      <input type="submit" value="送信" />
    </form>
  </body>
</html>

src/main/webapp/WEB-INF/view/next.jsp

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
  <head>
    <title>next.jsp</title>
  </head>
  <body>
    <span id="result">${decoratedValue}</span>
  </body>
</html>

単体テストコードはこちら。
src/test/java/webdriver/webapp/example/SimpleLogicTest.java

package webdriver.webapp.example;

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

import org.junit.Test;

public class SimpleLogicTest {
    @Test
    public void Helloを渡した場合() {
        SimpleLogic sut = new SimpleLogic();
        String expected = "***Hello***";
        assertThat(sut.decorate("Hello"), is(expected));
    }
}

結合テストコードはこちら。
src/test/java/webdriver/webapp/example/WebDriverIntegration.java

package webdriver.webapp.example;

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;

import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;

public class WebDriverIntegration {
    @Test
    public void WebDriverを使ったテスト() {
        WebDriver driver = new FirefoxDriver();
        try {
            driver.get("http://localhost:8080/simple");

            assertThat(driver.getTitle(), is("index.jsp"));
            assertThat(driver.findElement(By.name("next")).getAttribute("value"), is("next.jsp"));

            driver.findElement(By.name("value")).sendKeys("Hello");
            driver.findElement(By.tagName("form")).submit();

            assertThat(driver.findElement(By.id("result")).getText(), is("***Hello***"));
        } finally {
            driver.quit();
        }
    }
}

テストの内容は、超適当…。

とまあ、この設定で

$ mvn test

で単体テストまでが実行され、

$ mvn integration-test

で単体テストとSelenium WebDriverを使った結合テストが実行されます。
*単体テスト1ケース、結合テスト1ケースが実行されます

今回は単体テスト側の設定は特にしていませんが、こちらも名前とかで絞るのであればmaven-surefire-pluginで設定するんでしょうね。
http://maven.apache.org/surefire/maven-surefire-plugin/examples/inclusion-exclusion.html

あとは、src/test/javaに一緒にするんじゃなくて、src/it/javaにしてしまうとか…。

結合テスト用の「maven-failsafe-plugin」は、情報が少なーい。

面倒だろうなぁと思ってやってみましたが、実際に面倒でした。特にJettyと結合テストを組み合わせるところが、もう…。