kuroneko's blog

とりとめもなく気づいたことを書いていく

seleniumによるテストは負荷テストには向かない

pythonseleniumが思ったよりサクサクっと動いてくれるし、 コードとしてシナリオも判定も定義でき、非常にわかりやすい(個人的には)ので、 このまま長期安定化試験で流すシナリオに利用してやればいいんじゃね? と思ったけど、向いている分野でないと思ったのでメモ。

サンプルコード

負荷テストなので

  • 並列実行できる
  • ループして一定期間動作し続ける

ができる必要がある。 サンプルとして下記を用意して試してみた。 (実際は、ページオブジェクト用のソースファイルを別に用意したわけだが)。

  • main.pyにてスレッド化、ループの管理
  • selenium_sample.pyにてunittestを利用して、シナリオを管理

◇main.py

import unittest
import selenium_sample
import threading
import time
from datetime import datetime

# スレッド数
t_number = 2

# スレッドの実行間隔(s)
t_span_time = 2

# ループ回数
roop = 2

# ループ間隔(正確には前回ループ内のスレッドが全て終了してからの間隔)(s)
roop_span_time = 5

# 想定テスト時間(s)
test_time = 40

class MyThread(threading.Thread):
    def run(self):
        # logging.info('Starting...')
        logger.debug('Starting...')
        loader = unittest.TestLoader()
        # テストモジュール単位で追加
        suite = unittest.TestLoader().loadTestsFromModule(selenium_sample)
        # テストスイートを実行
        unittest.TextTestRunner(verbosity=2).run(suite)
        # logging.info('End.')
        logger.debug('End.')
        return


# メイン処理
for r in range(roop):
    threads = []

    for i in range(t_number):
        t = MyThread()
        t.start()
        threads.append(t)
        if i < (t_number - 1):
             time.sleep(t_span_time)

    for thread in threads:
        thread.join(test_time)

    if r < (roop-1):
        time.sleep(roop_span_time)

selenium_sample.py

import unittest
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

class PythonOrgSearch(unittest.TestCase):

    def setUp(self):
        self.driver = webdriver.PhantomJS()

    def test_sample(self):
        driver = self.driver
        driver.get("http://www.python.org")
        self.assertIn("Python", driver.title)
        elem = driver.find_element_by_name("q")
        elem.send_keys("pycon")
        elem.send_keys(Keys.RETURN)
        driver.save_screenshot("org1.png")
        assert "No results found." not in driver.page_source

    def tearDown(self):
        self.driver.close()

if __name__ == "__main__":
    unittest.main()

実行環境

実行環境は以下のとおり。

メモリ不足

今回用意したコードの場合、phantomjsがスレッド単位で起動する形になる。
スレッド化する際にメソッド(t = threading.Thread(target=hoge,......))だと画面遷移等を絡ませるとうまく動かなかった為、シナリオのクラス自体のインスタンスをrunさせた(知識不足も多々あり。。。)。
その結果、スレッド1つあたり、80MBほどメモリをphantomjs.exeが喰ってしまう。これが結構痛い。 これにより、(物理メモリの空き容量)/80MBがスレッド数の目安(スレッド化できるとは言っていない)になる。

phantomjs.exeのPIDが残る

1ループだけの場合、main.pyが終われば自然とプロセスが全部落ちるのだが、ループさせる場合、空きメモリ容量が不足し出さない限り、リソースモニターを見ていてもメモリには残ったままに見えている。

  • プロセス名が灰色になっている
  • 足りなくなれば一気にメモリが空く(処理が終わったphantomjs.exeが消える)
  • 特にテストも失敗しないし、phantomjsのログにも何も出ない、screenshot等で確認しても無事に進んでいる

といった状況なので、self.driver.close()自体は上手くいっているように見えるのだが、、、、大丈夫か心配になる(何時間か動かしてみたが、特にエラーになることはなかった)。

CPUは割と余裕がある?

適度にsleepを入れるなどフルスロットルで動かさなければ、割と余裕がある(自分が試した別コードは画面表示に2秒ほどsleepを必ず入れているから、という話もある)。

個人的な結論

seleniumはあくまで結合試験観点で、ブラウザを介して試験したい機能試験として利用すべきであり、負荷をかけたい、ということであれば、jmeterあたりが良さそう。