Seleniumで問い合わせフォームをテストしてみる
今回は前回作った問い合わせフォームをSeleniumでテストしてみようと思います。
1. Spring Bootで問い合わせフォームを作ってみる
2. 1で作った問い合わせフォームをSeleniumを使ってテストしてみる ←イマココ!
3. Jenkinsを使って自動デプロイ及び自動テストできるようにする
はじめに
このテストは、URLにアクセスし、フォームに値を入れ、データを送信し、データベースに期待される値が入っているかをテストします。
準備
フォルダ構成
Gradleの設定
group 'spring-boot-web-test' version '1.0-SNAPSHOT' apply plugin: 'java' apply plugin: 'idea' sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' // https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java compile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.4.0' // https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-firefox-driver compile group: 'org.seleniumhq.selenium', name: 'selenium-firefox-driver' // https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-chrome-driver compile group: 'org.seleniumhq.selenium', name: 'selenium-chrome-driver' // https://mvnrepository.com/artifact/org.dbunit/dbunit compile group: 'org.dbunit', name: 'dbunit', version: '2.5.3' // https://mvnrepository.com/artifact/org.slf4j/slf4j-simple compile group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.25' // https://mvnrepository.com/artifact/mysql/mysql-connector-java compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.40' }
seleniumと一緒にwebdriverも入れています。
直接データベースをいじるのでdbunitも入れています。
あとログ関係でslf4-simpleというものを使うのでそれも一緒に入れています。
WebDriver
ブラウザを操作するライブラリー群です。
今回はChromeを使うことにしました。
以下のサイトからChromeDriverをダウンロードしてプロジェクト直下に配置します。
Downloads - ChromeDriver - WebDriver for Chrome
FirefoxDriverを使うためのgeckodriverはこちらにあります。
Releases · mozilla/geckodriver · GitHub
テストの作成
テストの実行順序
- データベースのバックアップする。
- テストデータを挿入する。
- ブラウザを起動する。
- テストを実行する。
- スクリーンショットを撮り、ブラウザを閉じる。
- バックアップしたデータを戻す。
練習を兼ねてJUnitの機能をたくさん使おうと思います。
Application.java
//import省略 public class Application { private static String JDBC_URL = "jdbc:mysql://192.168.33.10/sample"; private static String USER = "myapp"; private static String PASSWORD = "myapp"; public static String TABLE_NAME = "inquiry"; static IDatabaseTester iDatabaseTester; static IDatabaseConnection iDatabaseConnection; private static Connection connection; Application() throws Exception{ } @ClassRule public static TestRule dbRule = (statement, description) -> new Statement() { private File file; //テストを行う前処理と後処理を書くことができる。 @Override public void evaluate() throws Throwable { dataBackUp(); statement.evaluate(); //実際のテストを実行 restore(); } private void dataBackUp() throws Exception { System.out.println("TestRule databackup"); connection = DriverManager.getConnection(JDBC_URL, USER, PASSWORD); iDatabaseConnection = new DatabaseConnection(connection, connection.getSchema()); iDatabaseConnection.getConfig().setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new MySqlDataTypeFactory()); iDatabaseTester = new DefaultDatabaseTester(iDatabaseConnection); QueryDataSet partialDataSet = new QueryDataSet(iDatabaseConnection); partialDataSet.addTable(TABLE_NAME); file = File.createTempFile("prepare_backup",".xml"); FlatXmlDataSet.write(partialDataSet, new FileOutputStream(file)); } private void restore() throws Exception { System.out.println("TestRule restore"); iDatabaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT); iDatabaseTester.setDataSet(new FlatXmlDataSetBuilder().build(file)); DatabaseOperation.CLEAN_INSERT.execute(iDatabaseConnection, new FlatXmlDataSetBuilder().build(file)); } }; }
オーバーライドしているevaluate()の処理にテストを実行する前や後の処理を書くことができます。
そしてstatement.evaluate()でテスト実行することになります。
dataBackUp()は今のデータベースの状態をtmpファイルに保存しています。
restore()はtmpファイルに保存したデータをデータベースに戻しています。
@ClassRuleはテストクラス単位ごとに実行されます。
なので実行するテストのクラスごとに初めにデータベースのバックアップ処理を行い、最後にデータを戻すようにしています。
DatabaseOperation.CLEAN_INSERTはデータセット(バックアップしたファイル)で指定したテーブルのデータが全て削除され、データセットのデータがDBにインサートされます。
CLEAN_INSERTだけではなくDELTE_ALLやNONEなどがあります。
ExternalRule.java
//import省略 public class ExternalRule extends ExternalResource { private TestDescription testDescription; private IDatabaseTester iDatabaseTester; private IDatabaseConnection iDatabaseConnection; private static String xmlPath = "datasets/fixtures/fixture.xml"; ExternalRule(TestDescription description, IDatabaseTester iDatabaseTester, IDatabaseConnection iDatabaseConnection) throws Exception{ this.testDescription = description; this.iDatabaseTester = iDatabaseTester; this.iDatabaseConnection = iDatabaseConnection; } @Override protected void before() throws Throwable { System.out.println("ExternalRule before"); iDatabaseConnection.getConnection().createStatement().executeUpdate("DELETE FROM " + Application.TABLE_NAME); iDatabaseConnection.getConnection().createStatement().executeUpdate("ALTER TABLE " + Application.TABLE_NAME + " AUTO_INCREMENT = 1"); DatabaseOperation.CLEAN_INSERT.execute(iDatabaseConnection, new XmlDataSet(getClass().getResourceAsStream(xmlPath))); } }
ExternalResourceクラスを使うことでテスト単位ごとの前処理、後処理を書くことができます。
今回はbefore()のみ定義していて、テーブルの中身を削除して、オートインクリメントの初期化を行っています。
そのあとはデータセットに定義された値をデータベースに挿入しています。
そのデータセットはresources/datasets/fixtures/fixture.xmlに記述していますが、今回は特に最初にテストデータを挿入する必要はないのでコメントアウトしています。
TestDescription.java
//import省略 public class TestDescription extends TestWatcher { private Description description; WebDriver driver; public Description getDescription() { return description; } @Override protected void starting(Description d) { description = d; // System.setProperty("webdriver.gecko.driver", "geckodriver.exe"); // driver = new FirefoxDriver(); System.setProperty("webdriver.chrome.driver", "chromedriver.exe"); driver = new ChromeDriver(); System.out.println("TestDescription starting"); } @Override protected void finished(Description description) { File file = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); try { FileUtils.copyFile(file, new File("./sccreenshot.png")); } catch (IOException e) { e.printStackTrace(); } driver.quit(); System.out.println("TestDescription finished"); } }
TestWatcherクラスを使うことで、テスト実行前、前提条件不成立時、テスト成功時、テスト失敗時、テスト終了後のタイミングで実行したい処理を実装できます。
今回は、初めにブラウザを起動します。そして最後にスクリーンショットを撮ってブラウザを閉じています。
InquiryFormTest.java
今回テストをするにあたり、ページオブジェクトパターンを使いました。
seleniumはテキストボックスに値を入れて送信するだとか、ラベルの文字が合っているかなどのテストをするわけですが、それぞれの要素をとってこなければいけません。
そこでその定義をクラスに分けて定義するという感じです。
・InquiryFormPage.java
//import省略 public class InquiryFormPage { @FindBy(xpath = "//label[text()='名前']/following-sibling::div/input") public WebElement nameTextBox; @FindBy(xpath = "//label[text()='メールアドレス']/following-sibling::div/input") public WebElement emailTextBox; @FindBy(xpath = "//label[text()='内容']/following-sibling::div/textarea") public WebElement contentTextarea; @FindBy(xpath = "//button[text()='送信']") public WebElement confirmButton; @FindBy(xpath = "//button[text()='送信']") public WebElement submitButton; @FindBy(xpath = "//*[@id=\"complete\"]/p[1]") public WebElement complateMessege; }
@FindByでその要素を取得する条件を書きます。
xpathのほかにidで取得したり、classで取得したりできます。
内部的には使われるタイミングになったら取得してくるようです。
次にやっとテストを書くクラスが出てきます。
・InquiryFormTest.java
//import省略 public class InquiryFormTest extends Application { private static String TABLE_NAME = "inquiry"; private static String expectedDataXmlPath = "datasets/testdata/sample.xml"; private InquiryFormPage inquiryFormPage; //先ほど定義したRuleを使うための宣言 @Rule public TestDescription testDescription = new TestDescription(); @Rule public ExternalRule externalRule = new ExternalRule(testDescription, iDatabaseTester, iDatabaseConnection); public InquiryFormTest() throws Exception { } @Before public void setUp() { inquiryFormPage = PageFactory.initElements(testDescription.driver, InquiryFormPage.class); System.out.println("InquiryForm setup"); } @Test public void inquiryFormTest() throws Exception { System.out.println("test1"); testDescription.driver.get("http://localhost:8080/form"); inquiryFormPage.nameTextBox.sendKeys("name"); inquiryFormPage.emailTextBox.sendKeys("sample@sample.com"); inquiryFormPage.contentTextarea.sendKeys("aaaaaaaaaaaa"); inquiryFormPage.confirmButton.click(); inquiryFormPage.submitButton.click(); Assert.assertEquals("お問い合わせを受理しました。", inquiryFormPage.complateMessege.getText()); IDataSet expected = new XmlDataSet(getClass().getResourceAsStream(expectedDataXmlPath)); ITable expectedTable = expected.getTable(TABLE_NAME); IDataSet actual = iDatabaseTester.getConnection().createDataSet(); ITable actualTable = actual.getTable(TABLE_NAME); Assertion.assertEquals(expected, actual); Assertion.assertEquals(expectedTable, actualTable); } }
@beforeのsetUp()で使うページオブジェクトの定義をします。
これはテストごとに実行されます。
@Testを使ってテストを書いていきます。
流れとしては、
- URLにアクセスし
- フォームに値をいれ
- 送信を押し
- 問い合わせ受理の画面が出ているか
- データベースに期待される値が入っているか
のテストをしています。
最後の期待されるデータを定義しているのがresources/datasets/testdata/sample.xmlになります。
・sample.xml
<!DOCTYPE dataset SYSTEM "dataset.dtd"> <dataset> <table name="inquiry"> <column>id</column> <column>name</column> <column>email</column> <column>content</column> <row> <value>1</value> <value>name</value> <value>sample@sample.com</value> <value>aaaaaaaaaaaa</value> </row> </table> </dataset>
実行
前回作ったものを起動しつつ、今回作ったテストを実行してみてください。
テストが成功すればバーが緑色になると思います。
さいごに
githubのっけておきます。
github.com
次回は今回までに作ったものをJenkinsと連携させてみようと思います。