読者です 読者をやめる 読者になる 読者になる

Knowlbo 開発者ブログ

株式会社Knowlboの開発者ブログです。

Selenium WebDriver で Web アプリのテストを自動化中 (PhantomJS 編)

開発部の本橋です。

先日の記事 で Chrome を自動操作しました。今回は Headless な Web ブラウザである PhantomJS を使って同じことをやってみます。 Headless であることで CI に組み込みやすくなったりと、幅が広がりますね。

PhantomJS の今後が微妙

いきなりですが、Chrome のベータ版で Headless モードが利用可能であるため、 PhantomJS の開発は終了しそうな感じです。

Phantom.jsのメンテナー、プロジェクトの将来に疑問を呈し、その座を降りる

そのうち Chrome の安定版にも降りてくると思われるので、もう少ししたらそちらを使うようになるかもしれません。

ひとまず今回は使用例も多い PhantomJS を使います。

PhantomJS (とPython) をインストール

Headless なので今回は Docker イメージでいけます。PhantomJSの公式な Docker イメージはないようなので DockerHub にある良さそうなイメージを使うか、もしくは以下のような Dockerfile を使って自分でイメージをビルドしましょう。

今回も Selenium を Python3 で使いたいので、Python も一緒にインストールします。

FROM debian:jessie

ENV PAHNTOMJS_VERSION 2.1.1

WORKDIR /tmp

RUN apt-get update \
    && apt-get install -y \
           curl \
           bzip2 \
           # linux用のバイナリには fontconfig が必要
           # http://phantomjs.org/download.html#linux-64-bit
           libfontconfig \
           python3 \
           python3-pip \

    # PhantomJS をインストール
    && curl -L -O https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-${PAHNTOMJS_VERSION}-linux-x86_64.tar.bz2 \
    && tar xvf phantomjs-${PAHNTOMJS_VERSION}-linux-x86_64.tar.bz2 \
    && ln -s $(pwd)/phantomjs-${PAHNTOMJS_VERSION}-linux-x86_64/bin/phantomjs /usr/local/bin/phantomjs \

    # selenium の Python バインディングをインストール
    && pip3 install selenium \

    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

ENTRYPOINT ["python3"]
CMD ["/mnt/run.py"]

run したときに /mnt/run.py を実行するようにしています。

イメージのビルドはこんな感じです。

$ docker build . -t my/selenium-phantomjs

Selenium で操作する

WebDriver の使い方は前回と同じなので 先日の記事 を見ていただくとして、1点だけ変わるところがあります。

# run.py

# ...略

if __name__ == '__main__':
    # PhantomJS を使うようにする。
    driver = webdriver.PhantomJS()

    # ウィンドウサイズを設定。
    # レスポンシブ対応しているサイトなんかだと
    # ウィンドウ幅が狭いとリンクテキストで要素を取得できない場合がある。
    driver.set_window_size(width=1200, height=1000)

run.py をカレントディレクトリに置いたら以下のコマンドでイメージを run します。/mnt/run.py を実行するように Dockerfile を書いたので、カレントディレクトリを /mnt にマウントします。

$ docker run -v $(pwd):/mnt my/selenium-phantomjs
..
----------------------------------------------------------------------
Ran 2 tests in 8.846s

OK

これで Headless に Selenium を使えるようになりました!

冒頭に書いたように Chrome の Headless モードが安定版に降りてきたらそちらも試したいですね。

なぜクラウド化するのか?

開発部の醍醐です。

春なので・・・新入社員の方々も会社に慣れてきたり(5月病になりそうだったり)している事と思われます。
そんな方々、もしくは私のようなベテランの域に入った方々を含め、あらためて

「システムをクラウド化する意味」

について整理してみたいと思います。

というのも、最近のソフトウェアシステム開発案件では「クラウド」が基本の前提条件となることも多くなってきているように思うからです。
私のような古参エンジニアの場合、オンプレがすべてであった時代から、現在のクラウド化の時代までを経験しているので、そのインフラ環境の変遷については身を持って経験しています。
しかし、特にここ2~3年の間にこの業界にジョインしたエンジニアの方々は、「こういう理由によりクラウドでシステムを作ろう」というフェーズをスキップし、「前提としてのクラウド」というケースが多くなっているのではないでしょうか。

1.自社内オンプレ運用

今でもこのケースは多々見られますし、現役の構成ですが、昔々はこれがすべてでした。
自社内にサーバーを配置し、ハードウェア構成を組み、ネットワーク構成を組み、OS環境を整え、アプリケーションをデプロイする、というケースです。
そしてこの場合は勿論、これらのインフラを構築する、また、運用し続けるインフラエンジニアが張り付くことになります。

2.オンプレ運用からデータセンター運用へ

自社内オンプレ運用からデータセンター運用への移行、が次の段階としてあります。
自社内に配置していたサーバーをデータセンター(N○○さん)などに移行することになります。
ケースはいろいろあると思いますが、代表的には「サーバー機器の設置場所を自社からデータセンターに移行する」という事になります。
これにより以下のような作業から解放されることになります。

  • 空調や電源などの低レベルのインフラ管理からの解放
  • 死活管理等の基本インフラ運用管理からの解放

3.IaaSによるクラウド化

IaaS = Infrastructure as a sreviceです。
Azureであれば Virtual Machine 、Amazon AWSであれば EC2 がこれに該当します。
クラウドのIaaS環境にシステムをパブリッシュして運用することになります。
IaaS環境では、特定のスペックの物理ハードウェア機器を持ちません。
AzureなりAWSなりがバックエンドで運用しているサーバー機器の上に仮想環境を構築することになります。
IaaSはハードウェア機器の仮想化(抽象化)である為、利用者は提供される仮想環境上に Windows Server のようなOS環境を構築することになります。
IISを構成し、必要であればSQL Serverを構成します。

オンプレやデータセンター運用との大きな相違点は「ハードウェア機器を自ら購入(レンタル)しなくてよい」という事。更にCPU・メモリ・ストレージは必要に応じてスケールアップ・スケールアウト、逆にスケールダウン・スケールインすることができるという事です。

IaaS化で得られたメリットは以下のようになります。

  • ハードウェアインフラ管理の放棄
  • 必要に応じて柔軟にハードウェア性能を変動させることができる
  • 使った分だけのコスト支払い

既存システムのクラウド化が容易

既存システム(もしくは既存アーキテクチャ設計のシステム)をクラウド化する場合、この後で説明するPaaSと比べ、IaaSへの移行は比較的容易です。
それはハードウェアの抽象化を行ったのみで、OS環境は従来のWindows Serverのままだからです。
搔い摘んだ細かな対比でいうと、企業内ネットワークのサブネットは Azureの VNet になり、Load Balancerは Azure Load Balancer や Traffic Managerとなり、基本的にはオンプレ時と同じような役割を果たすサービスがAzureでも提供されています。
特別なハードウェアの制御を行う様なシステムでない限り、アプリケーションから見た場合「ファイルシステム」も「ネットワーク」も「データベース」もオンプレ環境と何ら変わることがありません。
つまり、既存システム(もしくは既存アーキテクチャ設計のシステム)は、そのままIaaSクラウド化することが可能です。

OS環境の運用からは逃れられない

IaaSではハードウェアインフラは仮想化されますが、OS環境は利用者の管理下にあります。
つまり セキュリティパッチ等の Windowsアップデート は、利用者側で管理する必要があります。
サービス停止のスケジューリング、停止が許されないシステムであれば 複数台の冗長化を行った上でのアップデートスケジュール等の運用管理は利用者側で行う必要があります。

4.PaaSによるクラウド化

この数年の間に主にIaaSによるクラウド化が進んだように思います。
それは前述のように「既存システム(もしくは既存アーキテクチャ設計のシステム)を乗せ換えるのが容易」であることが影響しているように思います。
ドラスティックなソフトウェア アーキテクチャの置き換えではなく、ソフトウェア アーキテクチャは変更せず、緩やかにインフラに近い部分の抽象化、またそれによるコストカットをIaaS化により実現しました。
次のフェーズとして PaaS = Platform as a service という言葉が最近多く聞かれるようになっています。
単純に言うと「PaaSは”OS”という物自体をも抽象化」してしまいます。
Azureにおける PaaS は以下のようなものがあります。

  • App Service
  • SQL Database
  • DocumentDB
  • Azure Functions
  • 等々…

App Service

App Serviceは、ASP.NETアプリケーションをそのまま乗せて動作させるイメージになります。
Windows ServerとかIISとかいう概念はありません(バックエンドではWindowsがホストしていますが・・・またContainerとしDockerでホストすることも可能です)。

SQL Database

SQL Databaseは、SQL ServerのPaaS版です。
こちらも、利用者側からは Windows Server という概念は存在せず、「ただ、SQL Databaseというデータベースがサービスとして利用可能」というものになります。
オンプレのSQL Serverに対して、PaaSとして提供されているSQL Databaseのメリットの1つとして分かりやすい点には「レプリケーション設定の容易性」などがあげられます。
オンプレのSQL Serverであれば「2台の構成を組み、各SQL Serverに対してレプリケーション構成を組む」というインフラエンジニア的作業が必要になります。これに対して PaaS の SQL Database であれば、ポータル上からの比較的簡易な作業によりレプリケーション構成を組んでしまうことが可能です。

DocumentDB

DocumentDBは、NoSQLで無制限にスケール可能な、まさにクラウド時代のデータベースになります。
ポータル画面上から世界地図をクリックするだけで、国を超えたGeoレプリケーションも行われてしまいます。

Azure Functions

Azure Functionsは、いわゆるサーバーレスアーキテクチャの技術です。こちらも勿論、OSという概念は利用者からは見えません。
こちらもエラスティックにスケールします。

OSの抽象化

OSが抽象化されたことにより「OSに対するセキュリティパッチ適用のための再起動停止」という概念がなくなっています。
Azure内の本当のバックエンドではWindows Serverというインフラが存在し、そこに対するパッチ適用や再起動が行われていますが、その上で動作するPaaSサービスには特定マシン(OS)の再起動が直接的に影響しない仕組みが構成されています。

PaaS化のメリット

その詳細に触れていない項目も含まれますが、PaaS化のメリットは以下のようなものがあります。

  • OSの運用管理からの解放
  • サービス運用を継続したまま、エラスティックに高速でスケール変更が可能なインフラを利用可能
  • インフラ管理ではなくシステムのドメイン領域に注力可能

そのままではPaaS化できない

上記に示したような各種PaaS技術から分かるに、アーキテクチャの変化が伴う為、「このシステムは明日からPaaS化しよう!」とはなかなかいきません。
ただし、ASP.NETアプリケーション(特定のファイルシステムやCOM、またはWindowsサービスとの連携等を行っていない)をApp Serviceで動かすという事であれば、比較的スムーズに移行可能なものもあるでしょう。
また SQL ServerベースのシステムのDBを SQL Database にする、という事もそれほど大きな変更なく移行可能でしょう。とはいえ、通貨単位(国設定)等で不具合が出る箇所、調整を要する箇所が出てくるかもしれませんので動作検証は必要となります。
新規の、完全に自由なアーキテクチャを選択可能な状況でなければ、IaaSで動作しているシステムのWindowsサービス部分をFunctionsに移行する・部分的にApp Service化する等の順次移行、並行移行等、計画的な移行が必要になるでしょう。

(おまけ)可用性(耐障害性)について

Azureは2017年3月に3度の大規模障害を起こしたため、可用性(耐障害性)で不安を抱く状況が生まれている部分があるかもしれません。
一般によく言われていることですが、「基本的に障害は起こる」ことを前提としておく必要があります。
ハードウェアは壊れるし、人為的ミスも発生しうるという前提です。
IaaSではリージョン内での可用性セットを組んだ冗長化構成、または複数リージョンにまたがる冗長化設定を行う。
Azure Storageに関しても、リージョン内3レプリケーションの LRS から、別リージョンへの3レプリケーションの GRS等の選択検討も必要です。
SQL DatabaseやDocumentDBでも同様のレプリケーション設定が容易に可能です。
ただし、コスト上の問題が発生することもあります。常に複数のサービスをアクティブ状態で起動しておけるほどコストをかけられないケースもあります。
この場合は、障害発生時の素早い復旧のためのデプロイインフラを用意していくという事が1つの対応策です。
Infrastructure as codeによる自動デプロイであったり、App Service Linux ContainerであればDockerイメージで即時デプロイを可能にしておくことであったり、AzureではなくAWSへのデプロイも可能にしておく事も1つの手段です。
フェイルオーバーを正しく行うための手順等も、Azureでのノウハウや知識が必要になる部分もある為、やはり、いくらかの学習と経験が必要なところではあるでしょう。

まとめ

IaaS / PaaS含めクラウド化は、従来のインフラ管理の自動化(抽象化)を実現することができます。
オンプレ導入とクラウドのコスト比較をした場合、必ずしもクラウドが特別に安い状態にはならないように見えるケースがあります。ただし、自前では用意不可能な程のエラスティックなインフラをAzureやAWSは提供し、また、(ハードウェアの定期的なメンテナンス等の)物理的・人的運用コストの低減も実現されることも含めてコスト検討する必要があるように思います。
「データを社外に置く事はできない」等の、企業方針からクラウド化がNGとなるシステムもまだまだ多く存在します。しかし、クラウドのメリットは大きく、そして IaaS さらには PaaS が今後の主流になる事と考えます。

Selenium WebDriver で Web アプリのテストを自動化中

開発部の本橋です。

ブラウザ自動化ツールの Selenium WebDriver を使って Web アプリケーションのテスト自動化を試しています。

今回は弊社 Web サイトを例として、Python3 を使って Google Chrome を操作してみます。

の前に Selenium WebDriver とは

Selenium

一言で言えば Web ブラウザ自動化ツールです。以前提供されていた Selenium RC のアーキテクチャはすでに非推奨となり、現在は Selenium WebDriver の開発が活発に行われています。

Selenium WebDriver は W3C で WebDriver として標準化されています。

W3C WebDriver

Selenium WebDriver の Python バインディング

Selenium WebDriver は様々なプログラミング言語で利用できますが、今回は Python3 を使いました。ドキュメントはこちらを参考に。

https://seleniumhq.github.io/selenium/docs/api/py/api.html

環境構築

必要なものをインストールします。Mac で Homebrew を使うことを想定しています。

  • Python3 をインストール
    • Homebrew なら brew install python3
  • Google Chrome をインストール
    • Homebrew なら brew cask install google-chrome
  • ChromeDriver をインストール
    • Homebrew なら brew install chromedriver
  • selenium の Python バインディングをインストール
    • pip なら sudo pip3 install selenium

Selenium WebDriver 基本

アサーションはひとまず置いといて、Selenium WebDriver を使ってみます。

www.google.co.jp で「株式会社Knowlbo」を検索し、トップページから会社案内ページへ遷移してみます。

# selenium_test.py

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome()

# Google で Knowlbo を検索
driver.get('https://www.google.co.jp')
elem = driver.find_element(By.NAME, 'q')
elem.send_keys('株式会社Knowlbo')
elem.send_keys(Keys.RETURN)

# www.knowlbo.co.jp へのリンクが表示されるまで待ち、遷移
WebDriverWait(driver, 10) \
    .until(EC.presence_of_element_located((By.PARTIAL_LINK_TEXT, '株式会社ナルボ'))) \
    .click()

# www.knowlbo.co.jp のページが表示されるまで待ち、会社案内ページに遷移
WebDriverWait(driver, 10) \
    .until(EC.title_contains('株式会社ナルボ'))
driver.find_element(By.LINK_TEXT, '会社案内') \
    .click()
$ python3 selenium_test.py

unittest モジュールと組み合わせる

次は unittest モジュールを使ってアサーションしてみます。(www.google.co.jp からの遷移は省略します。)

# selenium_test3.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class WedDriverTest(unittest.TestCase):
    def _to_knowlbo(self):
        # トップページへ遷移
        driver.get('https://www.knowlbo.co.jp')
        WebDriverWait(driver, 10) \
            .until(EC.title_contains('株式会社ナルボ'))

    def setUp(self):
        self._to_knowlbo()

    def test_company_page_link(self):
        # トップページに会社案内のリンクが表示されることのアサーション
        elem = driver.find_element(By.LINK_TEXT, '会社案内')
        self.assertIsNotNone(elem, 'トップページに会社案内のリンクが表示されること')

    def test_company_page_content(self):
        # 会社案内ページへ遷移
        driver.find_element(By.LINK_TEXT, '会社案内') \
            .click()
        WebDriverWait(driver, 10) \
            .until(EC.title_contains('会社案内'))

        # 会社案内ページの内容のアサーション
        elem = driver.find_element(By.CSS_SELECTOR, '.page-title')
        self.assertEqual(elem.text, '会社案内', '会社案内ページが表示されること')

    def tearDown(self):
        pass

if __name__ == '__main__':
    driver = webdriver.Chrome()
    unittest.main()
$ python3 selenium_test2.py
..
----------------------------------------------------------------------
Ran 2 tests in 11.026s

OK

gif

Chrome を自動操作している gif 動画を取ってみました。イメージ伝わりますかね。

f:id:knowlbodev:20170421162117g:plain

WebDriver は動作に Web ブラウザが必要なのでCIと組み合わせるのは厳しい気がしますが、この調子でどんどん自動化していけたらいいなと思います。