ハバナ式継続的デリバリー

Python, Ubuntu, JavaScript 色々知らなかった!のメモ

SeleniumでExplicitWaitは何のために使うのか?

SeleniumのWait(待機) -- 暗黙的待機/明示的待機

Seleniumは端末を操作するときに処理が返ってくるまでの待ち時間を設定できる。
その際、ImplicitWait(暗黙的待機)とExplicitWait(明示的待機)の2種類仕組みがあることは本とかブログでよく語られることだ。

ExplicitWait -- 明示的待機

明示的待機はWebDriverのPythonバインディングのコードを例に取ると、

from selenium.webdriver.support.ui import WebDriverWait

WebDriverWait(driver, 8).until(
    lambda x: x.find_elements_by_css_selector('.hello')
)

こんな風にすると、'hello'というcssクラスを持つエレメントがDOMの中に現れるまで最大8秒まで待つ。

単純に、time.sleepで8秒待つんじゃなくて、エレメントが登場すれば8秒以内に処理が戻ってくる点が良い。

ImplicitWait -- 暗黙的待機

対して、ImplicitWaitというやつは何に使うのかというと、そのSeleniumセッションで最低何秒待つかを設定するために使う。

Selenium-WebDriverの仕組みとしては、find_なんちゃらといったDOM要素を探すメソッドを呼ぶと、探そうとしている要素が出現するまで待つ。
デフォルトだと永遠に待つ。だから、絶対に出現しない要素を取り出そうとするとずっと待ちになって処理が終わらない。

だから、だいたい4秒以内にテスト対象のアプリケーションが応答すると見込みをつけて

from selenium import webdriver

driver = webdriver.Firefox()
driver.implicitly_wait(4)
# fooをクリックした後にbarをクリック
driver.find_element_by_css_selector('.foo').click()
driver.find_element_by_css_selector('.bar').click()

こんなコードを書くのである。

こうすると、最初のクリック対象であるfooが画面に出現するまで4秒待って、それでもfooが出現しなければ例外が出て処理が失敗する。
fooが4秒以内に出現すればbarを探しに行く。

そうすると、あまり細かいことを気にしなければ暗黙的待機だけ設定しておけば良いじゃないかということになる。

ExplicitWaitの想定利用ケース

で、結局明示的待機はどういうときに使うべきなのかというと、

通常より遅い処理が走る操作を実行するとき

foo, barのボタンは大抵4秒で処理が終わるけど、bazボタンを押したときの処理は遅くて8秒かかるといった場合に明示的待機と暗黙的待機を使い分ける。

コードを示すと

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait

driver = webdriver.Firefox()
driver.implicitly_wait(4)
# fooをクリックした後にbarをクリック
driver.find_element_by_css_selector('.foo').click()
driver.find_element_by_css_selector('.bar').click()

# bazをクリック(待ち時間が長い)
WebDriverWait(driver, 8).until(
    lambda x: x.find_elements_by_css_selector('.baz')
).click()

こんなかんぎ(カツヒコ風に)。

まずこれが最も一般的な利用ケース。

ある処理を実行して特定の要素がDOMから無くなることを確かめるとき

例えば

  • 添付ファイルをアップロードすると削除用のゴミ箱アイコンが画面に表示される。
  • ごみ箱アイコンを押すと、ファイルが削除されて画面からアイコンも消える。

こういうアプリを検証するときに

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait

driver = webdriver.Firefox()
driver.implicitly_wait(4)

# 添付済みファイルを削除できることを確認
driver.find_element_by_css_selector('.file-del').click()
# ごみ箱アイコンが画面からなくなっていることを確かめる。
WebDriverWait(driver, 8).until(
    lambda x: not x.find_elements_by_css_selector('.file-del')
)

こんなことをする。

参考リンク・引用

調べたら、こんなブログよりもちゃんと説明しているブログが沢山あった。

Selenium 2で非同期処理を待機する5つの方法 - CODESCRIBBLE

5. implicitlyWait() を設定する

implicitlyWait()で待機時間を設定したDriverは、それ以降全ての要素検索で暗黙的に待機を行います。
非常に簡潔に待機処理を実現できるメソッドではありますが、その反面、要素の検索時にしか利用できないという短所もあります。例えば、要素が非表示になるまで待機したり、ページのタイトルが変更されるまで待機するといった場合は、このメソッドでは対応できません。

Selenium WebDriverのwaitを活用しよう│ソフトウェアテストラボ|アプリテスト|スマートフォンテスト|株式会社シフト

明示的な待機(Explicit Wait)

明示的な待機はテストケース内の個別の箇所に対して好きな条件で待機を指定する機能です。この機能を使う場面はおおまかに二種類に分かれます。
 1. 「要素が現れる」以外の条件で待機したい場合
 2. 待機時間を個別に設定したい場合

findコマンドでカレントディレクトリを除外する

findでカレントディレクトリを除外せず大失敗

foo
├── bar
│   └── two.txt
├── baz
│   └── three.txt
└── one.txt

こういう階層構造で

  • fooの所有権をroot:root
  • fooの中のディレクトリの所有権をwww:www
  • ファイルの所有権は変えない

に変更しようとして以下の通り作業して大失敗。

$ sudo chown root:root foo
$ cd foo
$ find . -type d -exec sudo chown www:www "{}" ¥;

これをやってしまうとfindの結果に'.'が含まれて、fooもwww:wwwに所有権変更されてしまった!

試しにfindだけやってみると

.
./bar
./baz

この通りカレントディレクトリがヒットする。

これ、実は本番環境でwebサーバーからファイルサーバーのファイルを配信するときにwwwユーザーに権限が無くてファイルが参照できないというトラブル対応の最中にやらかして結構焦った。

findのandとnotでカレントディレクトリを除外

他にうまいやり方があるのかもしれないけど、とりあえずfindの-a(and)と!(not)を使って対処。

$ sudo chown root:root foo
$ cd foo
$ find . -type d -a ! -name '.' -exec sudo chown www:www "{}" ¥;

YAMLは便利: アンカーとエイリアス

PythonといえばINIファイルですよね?

Pythonって言ったらINIファイルじゃないですか。
13.2. ConfigParser — 設定ファイルの構文解析器 — Python 2.7ja1 documentation

組み込みでINIファイルのパーサーあるし。

でもINIファイルは入れ子構造とかを処理するときに都合が悪いですよね。
あと、データの型変換のルールとか自分で決めなきゃいけないし(getintとかgetfloatとか)。
リストとか辞書も表現できないし。

YAMLはすごい

YAMLはリストや辞書が定義できる上、アンカーエイリアスが使えるところがすごい。
Rubyist Magazine - プログラマーのための YAML 入門 (初級編)

group:
  - &developers
    - foo
    - bar
  - &superusers
    - foo


development.example.com:
  title: 開発環境
  hostname: 127.0.0.1
  members: *developers
  tags: development


example.com:
  title: 本番環境
  hostname: 127.0.0.1
  members: *superusers
  tags: production

こういう風に書いておくと、development.example.comの'members'を取り出したとき['foo', 'bar']が取れます。

PyYAMLがアンカー・エイリアスをちゃんと処理してくれる

PythonYAMLをパースするときにPyYAMLを使ってみたんですけど、
PyYAML 3.11 : Python Package Index

http://pyyaml.org/wiki/PyYAMLDocumentation#Aliases

リンクのドキュメントでもアンカー・エイリアスの例が書いてあります。
実際使ってみると、ちゃんと処理出来ました。

Pythonのsetは算術演算子が使える

タグを複数指定してデータを絞り込む

複数のタグが付いたデータにタグを指定して絞り込む処理を書いていて、タグを複数指定した場合にすべてのタグを持っているデータだけ取り出すにはどうしたら良いかわからなくなって調べた。

例えば、以下のリストの中で、

entries = [
    {'apple', 'fruit', 'lemon'},
    {'apple', 'computer', 'mac'},
    {'mac', 'hamburger', 'lemon'},
]
  • 指定した文字列を含む要素だけ取り出したい。
  • 但し、文字列は複数指定できる。

こういうことがしたいのだ。
(上記の例では、「lemonとfruit」を指定したら1番目の要素が返ってくる)

これが「タグは1つしか渡せない」のであれば簡単で

tag = 'apple'
result = [x for x in entries if tag in x]

このようにinを使えば済む。

Pythonのsetにはスーパーセット、サブセットを表す算術演算子が実装されていた

この通り、<<=を使うと左辺が右辺に含まれることを調べられる。

http://docs.python.jp/2/library/stdtypes.html#set.issubset

set <= other
  set の全ての要素が、 other に含まれるか確認します。

set < other
  set が other の真部分集合であるかを確認します。つまり、 set <= other and set != other と等価です。

Pythonのマニュアルにちゃんと書いてありました。


以上

プログラマの、これは、ybi-4 (ヤバイヨ)

状態

  1. 失敗すると深く落ち込む。
  2. 仕事が手に付かなくなる。
  3. 恐怖心でコードを書き始める。
  4. 二度と同じ間違いを犯したくないと思うあまり、狭い範囲での正しさに固執する。
  5. 思考から論理性と客観性が無くなる。

性質

  1. 本質的に矛盾していること・不可能なことを頑張って解決しようとする。
  2. 同等・同質に扱ってはならない事象を混同する。
  3. 失敗の有無にかかわらず、いつも期待(=自分にとって最も都合の良い想定)で考える。

習慣

  1. 直感に頼りすぎ。
  2. 論理的に考えない。
  3. 思考と直感を取り違えている。
  4. 思い込みをしていると疑わない。
  5. 嫌な事実から目をそらす。
  6. 都合の悪い事実は意図的に認識から除外する。

能力

  1. 事象の整理ができない。
  2. 場合分けして考えられない。
  3. その判断が思い込みに過ぎないことを確かめる術を知らない。
  4. 物事を厳密に分けて扱えない。

最後に

や〜ばい

supervisorctlのreloadは設定ファイルの再読み込みではなく、supervisordの再起動

supervisorctlでreloadやっちまった(手遅れ)

プロセス監視ツール「Supervisor」はデーモンとクライアントの2構成になっているが、今回はクライアントアプリの方の話。

supervisorctlコマンドでは監視対象の設定ファイル再読み込みやプロセス再起動ができるが、

  • reread
  • restart
  • reload

このように似たような名前のサブコマンドが複数あって、みんな同じに見える。
「ただのエイリアスなの?」と思ってよく考えず実行すると大変なことになるので注意である。

わたしは、reloadを監視対象の設定ファイルの再読み込みと思い込んでいて、特に気にもせず

$ sudo supervisorctl reload mysite

と実行してしまった。

ところが返ってきたメッセージは

Restarted supervisord

である。そう、supervisordの方が再起動してしまうのである。

supervisorctlのヘルプを見よう

supervisorctl help ??? とするとサブコマンドのヘルプが見られる。

まず、reread

cultureeen@ubuntu:~$ sudo supervisorctl help reread
reread 			Reload the daemon's configuration files

設定ファイルの再読み込みである。
そして、これは特定の名前を指定して実行するコマンドではない。
全部再読み込みするのだ。

試しに実行してみると

cultureeen@ubuntu:~$ sudo supervisorctl reread
monitoring: disappeared  # ファイルが消されて存在しなくなった場合など
portal: changed  # 変更された

このように、すべてのプロセスの設定を読み込み直す。

次に、restartだが

cultureeen@ubuntu:~$ sudo supervisorctl help restart
restart <name>		Restart a process
restart <gname>:*	Restart all processes in a group
restart <name> <name>	Restart multiple processes or groups
restart all		Restart all processes

この通り、プロセスの再起動である。そして、個別に再起動できる(全部まとめて再起動もできる)。

最後に、reloadである。

cultureeen@ubuntu:~$ sudo supervisorctl help reload
reload 		Restart the remote supervisord.

この通り、デーモンの方を再起動してしまう。もちろん個別のプロセス名を渡しても無駄である。

名前を指定できるのはrestartだけ

せっかくなのでもう一度まとめておこう。

$ sudo supervisorctl restart mysite

これはOK。mysiteプロセスを再起動する。

$ sudo supervisorctl reread mysite

引数"mysite"は無意味。すべての設定ファイルが読み込み直される。

$ sudo supervisorctl reload mysite

引数"mysite"はもちろん無意味。supervisordの方が再起動する。

ちなみに、

$ sudo supervisorctl restart  # 何も指定しない

とすると以下の通りエラーになる。全部再起動したいときは"all"を明示的に指定しろと。

Error: restart requires a process name
restart <name>		Restart a process
restart <gname>:*	Restart all processes in a group
restart <name> <name>	Restart multiple processes or groups
restart all		Restart all processes

supervisordを再起動せず監視対象を増やすときはrereadの後add

更に、supervisordを再起動せず監視対象を増やしたいときはまずrereadしてaddをするそうだ。

適当なスクリプトをデーモン化するのにSupervisorが便利 - id:anatooのブログ

supervisordを再起動せずに追加したい場合は、以下みたいにrereadで設定ファイルを再読み込みしてからaddする。

$ supervisorctl reread
$ supervisorctl add tiarra

最後にひとりごと

"reread"って名前、わかりにくいし言いづらいから単に"read"とか"reconfigure"にすればいいのに(小声)

この一年を思い起こして

習慣や考え方を変えることはとても難しい

一年間、転職先で仕事をして思ったことがある。
習慣や考え方を変えることはとても難しい。
仕事の範囲でJavaのプログラミングを覚えた人にPythonのプログラミングを短期間で覚えてもらおうとした。
無理だった。
プログラミングの経験が浅い人にもPythonを覚えてもらおうとしたが、やっぱり無理だった。

何がつらいって、やる気の無い人を教育することが苦痛なのだ。

  • 「なぜこれができない?」
  • 「どうしてこれを知らない?」
  • 「何度も重要だからと説明を重ねているのにやっぱり忘れてしまう。」

勉強会や講義をやっても、ダメな人はずっとこういう繰り返しになる。
教える方もだんだん嫌になってくる。
色々試して至った結論は、そういう人に期待せず、無駄な投資をしないことだった。

考えに賛同してくれる人とチームを組みたい

要領が良い人は知識を速く蓄え、少ない経験から多くを学ぶことができる。
だが、要領が悪い人は無価値なのかと言えばそうではない。
要領が悪くてもやる気があって、諦めない人なら歩みが遅くてもスキルアップしてくれる。

また、現状の問題点を解決したいとか、行き詰まってしまった状況を打破したいという考えに賛同してくれるかどうかは要領の良さと関係ない。
むしろ、人間関係・好き嫌いの問題なのかもしれない。

中小企業だと簡単には人材をゲットできない。
優秀な人材で少数精鋭のチームで迅速に課題をこなしていく。そんな風にはなかなかいかない。
しかし、優秀さや要領の良さよりも、自分たちの考えに賛同してくれるかどうかを重視すべきだとこの一年で思うようになった。

また、単にわたしを嫌いに思う人もいるだろう。
好かない者同士が一緒に仕事をするのも苦痛だ。
だから、そういう人とわたしは仕事をしない。
仲良くできない人と無理に仲良くしようとしなくて良いと思う。
自分に賛同してくれる人とチームを組めるよう頑張る方が利益があると思う。

人の向き不向きを補い合うチーム構成が良い

組織やチームで仕事をすると、チームメンバーの性格やスキルは結構ひとそれぞれ違う。
だらしないけど技術的な知識豊富で、調べものをするときも速い人。
意思決定や責任を負うことが嫌いでも、丁寧で地道にものごとを続けられる人。
向き不向きが異なる人同士を一つのチームに組み入れると、うまくやれば双方の得意な面を生かして不得意な面を相殺できる。

間違いやミスを指摘されることが嫌な人と一緒に仕事をするのは難しい

自分の間違いやミスを指摘されると怒ったり不快感を示す人がいる。
継続的な取り組みをこういう人と一緒に進めると色々障害や制約が出ると思う。
取り組みが長くなるほど、一定のスパンで振り返りと見直しを図る重要性が増す。
間違いやミスを指摘されるのが嫌な人に振り返りや見直しは難しい。

まとめ

沢山の課題や大きな課題に取り組むには一個人の努力ではなく「組織的に取り組むこと」が重要だと思う。
それには考えに賛同してくれる人を集めて、定期的に振り返りや見直しをして、各人がスキルアップしてくれれば良い。考えを整理するとこんなところだ。

これからも良い人と一緒に仕事を進めていきたい。