業務でSeleniumを導入し、結合テストをある程度自動化した時の備忘録

# はじめに
SpringBootをメインに扱った、若手による社内アジャイル開発施策でSeleniumを導入してCT自動化を推進したときの備忘録です。

# 解決・実現したかったこと
<ul>
<li>これまで手動でのCTによるヒューマンエラーがあり、スプリントレビューのときにバグをお披露目してしまったので、そういった事態を避けたい</li>
<li>アジャイルソフトウェア開発宣言に則って、証跡をドキュメントではなく、<b>動くソフトウェア</b>に残すことを重視したかった(下記リンク参照)</li>
<li>機能を増やしたりUIを変更したりなどの影響調査をしやすくするため</li>
<li>所属組織内でテスト自動化があまり推進されていなかったため、有識者が少ない印象があるのは懸念事項でした</li>
</ul>

などなど…
とにかく、変化に弱いし認知リソースをかなり削る手動CTを、楽なものにしたかったのです。

https://agilemanifesto.org/iso/ja/manifesto.html

# 導入→どのように運用したの？
### ①導入

プロジェクトの技術選定へのハードルがかなり低いので、公式ドキュメントを参考に、`Gradle`に依存関係を持たせます。
後はドキュメントに沿ってドライバーを入れたり、ビルドしたりしていきます(公式以外でも、意外と調べると記事が出てくるので割愛)。

案件だと、この時点で費用対効果や開発チームのキャッチアップなどの各種見積もりとかが必要になるかと思われます（今PJはそこの敷居を気にしなくてよかったので、導入）。

https://www.selenium.dev/ja/documentation/webdriver/getting_started/install_library/

### ②テスト運用の仕方

Spring bootを使ったプロジェクトで扱うにあたって、以下5つを行いました。

<ol>
<li>ページごと<b>JUnit</b>を実行できるファイルを作った</li>
<li><b>DBUnit</b>によってテストデータを格納したxmlを読み込んで、ユースケースに柔軟に対応した</li>
<li>DBの各項目の最大値は<b>application.properties</b>に定義して、境界値分析の際など必要なときに呼び出した</li>
<li>契約として、変化後にあるはずの要素があればテストをpassとした（例えば、ログイン操作のCTの際に、正常に処理ができていればログイン後にUserIdがある画面仕様なので、遷移先にUserIdがあればOK、のようにした）</li>
<li>各ページに共通して使う処理（例えばログインなどのCTを複数行う上で決まりきった操作）は、スーパークラスを定義してそこに定義し、サブクラスで共通処理を呼び出した</li>
</ol>

また、テストケースの設計としては、
<ol>
<li>各機能のユースケースや受け入れ条件を担保できる、正常系処理のテスト</li>
<li>境界値、同値</li>
<li>バリデーションチェック</li>
</ol>
この3つの観点から重点的に設計しました。

ただ設計をする際には、Excelとかドキュメントに記述ではなく、そのまま直にテスト実行ファイルにテストケースを書いて打鍵というラフなものにしてました。アジャイルの「動くソフトウェア」を意識していました。

上記をコードにするとこんな感じです。
すっごい簡単なコードを書いてますが、本来はもう少し複雑な要素判定等をしています。
ここでは参考までに。

```java
package jp.hogehoge;

import static org.junit.jupiter.api.Assertions.*;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;

import com.github.springtestdbunit.DbUnitTestExecutionListener;
import com.github.springtestdbunit.annotation.DatabaseSetup;

@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
// DBUnit
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DbUnitTestExecutionListener.class })
// 共通処理、@AfterEachなどはParentに記載
public class Child extends Parent {

    // 500文字をapprication.propertiesからimport
    @Value("${fuga.fiveHundred}")
    protected String fiveHundred;

    @Test
    // テストケースの読み出し
    @DatabaseSetup(value = "classpath:/hogehoge.xml")
    public void 例として正常系のテストケース() throws Exception {

        // 以下3行はどのテストケースでも使うので、本来Parentで書いてます
        WebDriver driver = new ChromeDriver();
        driver.get("URLをこちらに格納");
        Thread.sleep(1000);

        WebElement userName = driver.findElement(By.id("username"));
        WebElement password = driver.findElement(By.id("password"));
        WebElement loginBtn = driver.findElement(By.id("login-btn"));
        Thread.sleep(1000);

        userName.sendKeys("user");
        userName.sendKeys("12345678");
        Thread.sleep(1000);

        loginBtn.click();
        Thread.sleep(1000);

        WebElement pageTitle = driver.findElement(By.id("page-title"));

        // 要素をアサーション
        assertEquals("トップページ", pageTitle.getText());
    }

    // test2...
    // test3...
}

```
### ③その他
<ul>
<li>レビュー時は、レビュアーに再度テストを実行してもらって、確認及びコードの修正をしてもらいます。</li>
<li>master統合後やリグレッションテストなどを行う際にはCTを再度実行します（本来はGithub Actionsやjenkinsなどでこれらも自動化できるのが良いですが、今回は環境のリソースの都合でできませんでした）</li>
<li>UIでもバグを見つけた場合は特定のためにCTを再実行する場合があります</li>
</ul>


# 共通処理で書いたもの
上のコードでいう`Parent`クラスに書いていたものです。
コードは載せられませんが概要のみだと、

<ol>
<li>ページごとに必要なログイン</li>
<li>必要な要素のクリック</li>
<li>Java側でのJSの実行（DOMのAttributeの操作など）</li>
<li>DOMの取得</li>
<li>ドライバの起動、Headless化</li>
</ol>

などです。
定型的な処理は共通なものとして親クラスを作っていました。

# やってよかったと思ったこと
<ul>
<li>CTにかける時間が減った。<b>テストケースの実行中にまた別のテストケースを記述できて効率がいい。</b>手で動かすより早い</li>
<li>影響調査が楽になる</li>
<li>被りのあるプロセスを共通化しているため、DRY原則をテストで遵守できる（ユースケースがわかりづらくなってしまうので、し過ぎは良くないですが…下記参照）</li>
<li>自動でブラウザが動くのがかっこいいので、開発者体験が上がる（思いっきり主観）</li>
</ul>

などです。

https://speakerdeck.com/jnchito/number-vstat


# 改善点、問題点、解消できなかったこと
### テストに関しての知識が成熟していないと不安が残ったままテストを実装することになる
そもそもテストケース設計や周辺知識が少ないと、テストの漏れや証跡の弱さに繋がりやすい印象です。
今回も契約によるテストがメインとなっていたり、分析方法をあまり使えていなかったり、キャプチャを撮って証跡にしているとかもしていないので、正確なアプローチを知って不安を払拭したいですね。

### メイン技術(今回だとJava,SpringBoot)はうまく使えれば使えるほどよい
共通処理を親クラスに定義していましたが、継承関係を不要に持たせることに繋がることに対して、嫌悪感を持つ人もいるかと思います。ですので、もっと正しい方法があるかもしれないなと感じてます。
そこで、デザインパターンであったりモジュール化など、様々なメイン技術に関する知識を持つことが、自ずと良いSelenium運用に繋がるのではないかと思います。

### Seleniumに踏み込めていないので、実行時間の短縮などパフォーマンスチューニングができていない
`Thread.sleep`を慣習的に1秒にしたり、`session request`などを使った実行時間短縮などは知識や経験不足でできていないです。
どこかでこの技術的な負債を解消できるといいですね。

### 案件等で導入する際には様々な観点からの見積もりや準備が必要
CI/CDを動かすサーバの運用費、チームの学習工数、打鍵の工数、Seleniumと手動での実施の比較となるようなエビデンスレベルの高いデータの提示や導入するメリットの明確化など…
Selenium文化がない組織に導入したいときには、色々ハードルが高そうですね。

# まとめ
もっと良い書き方あるよ！それ違くない？など、ご意見等あれば参考にしたいので、コメントお願いします～