WebアプリのテストでUIテストと統合テストを切り離そうとしたけど、ちょっと考えなければならなかったハナシ

 Webアプリのテストで、Seleniumを使ったUIテストを書いておいた。テスト内容として、フォームを埋めて、POSTでコントローラが意図通りに動いて、のぞんだレスポンスが返ってくるかまでテストしていた。UIテストと同時に統合テストの役割をになっていたので、役割をフォームのテスト程度に抑えて、別の統合テストを用意したかった。だからPython + requests( +lxml)で、直接RESTリクエストを投げてコントローラを動かすテストを書いてみた。
https://github.com/hMatoba/tetsujin/blob/master/tetsujin/PythonIntegtationTest/tests/master_test.py


 ただGETを投げてレスポンスを確認するようなテストもSeleniumでやっていたのだが、これをrequests + lxmlにしたところパフォーマンスは数十倍を超えたのでまあいい手ごたえ。ただSeleniumを使ったテストでも、一つのメソッドにつき一秒もかかっていないのでそんな神経質にならんでもいいかとも思う。
 考えなければならんかったのはPOSTを行うフォームのテスト。POSTする内容をrequestsで作る。それをPOSTで投げる。なのだが、フォームなのでもちろんCSRF対策が入っていて、これを考慮せんといかんかった。
 ASP.NET Core MVCでのCSRF対策は備え付けのものを使った。
Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core
CSRF対策のトークンを確認したところ、フォームには”__RequestVerificationToken”の名前で、クッキーには”.AspNetCore.Antiforgery.9TtSrW0hzOs”のキーで値が入っていた。だが、長さ190文字中、先頭30文字弱のみ一致しており、それ以降は異なっていた。これに関して自分で適当に作ってフォームとCookieにセットするのはやめておいたほうがよさそう。ここはSeleniumを使ってブラウザでフォームを動かしたほうが、自分でごにょごにょやるより手っ取り早いだろう。

 まとめ。WebアプリのUIテストと統合テストをわけたかったので、RESTリクエストを作ってコントローラを動かすテストを追加してみた。GETでレスポンス確認する場合は狙い通りだった。いっぽうでPOSTでCSRF対策が入っている場合、フォームやクッキーにその対策の対策を入れる必要に気づいた。そうすると、そこのあたりはSeleniumを使ってブラウザを動かすテストにしておくのが余計なことを考えずに済んで手っ取り早いだろうという知見を得た。
comment: 0

TravisCIで環境にPython3(3.6)を入れる

 TravisCI上でSeleniumを使ってテストがしたい。テスト対象のアプリはC#で書いているが、経験上C#でSeleniumを動かすのきっつい。Pythonのほうが好み。Pythonを使う。
 TravisCIでコマンド”python”を打つと、Ubuntu環境だから現在はPython2.7が立ち上がる。でもいまはもう3系を使いたい。もう3.6でいく。ライブラリも入れたいからpipも用意する。
 .travis.ymlに下記を追加。
.travis.yml

addons:
apt:
sources:
- deadsnakes
packages:
- python3.6
- python3-pip


 ↑の追記によって、環境準備のタイミングでaptを使ってパッケージを入れてくれる。
https://travis-ci.org/hMatoba/tetsujin/builds/370164937#L510


 あとは普段のUbuntu環境を使うかのごとく、”python3”、”pip3”。
pip3 install requirements

python3 setup.py test
comment: 0

Seleniumで行っていたテストをPageObjectデザインパターンに書き換えてみる

 あるWebアプリのログインの成功と失敗を、Seleniumでテストしていた。HTML要素をIDから選択し、キーボード入力のエミュレートでログインIDとパスワードを入れて、と生々しいコードを書いていた。
 このたびはSeleniumでのテストにはPageObjectデザインパターンが推奨されているというのを知って、おもしろそうなので適用してみた。

 ログインの成功をテストしていた元のコード。若干生々しいDOMの操作をしている。
def test_login_success(self):

"""login success"""
self.driver.get(HOST + "/Auth/Login")

el1 = self.driver.find_element_by_name("_id")
el1.send_keys("testuser")

el2 = self.driver.find_element_by_name("password")
el2.send_keys("password")

el3 = self.driver.find_element_by_name("enter")
el3.click()

self.assertIn("Admin Page", self.driver.title)


 PageObjectデザインパターンの適用として、ログインフォームの入力からPOSTまでを行うクラスを用意する。
class LoginFormStory:

def __init__(self, driver):
driver.get(HOST + "/Auth/Login")
self._driver = driver

def enter_id(self, id_):
el = self._driver.find_element_by_name("_id")
el.send_keys(id_)
return self

def enter_password(self, password):
el = self._driver.find_element_by_name("password")
el.send_keys(password)
return self

def post_form(self):
el = self._driver.find_element_by_name("enter")
el.click()
return self

def is_authenticated(self):
if "Admin Page" in self._driver.title:
return True
else:
return False


 上記のクラスの用意によって、テストの進行をメッセージングで表現できる。
def test_login_success(self):

"""login success"""
is_authenticated = (
LoginFormStory(self.driver)
.enter_id("testuser")
.enter_password("password")
.post_form()
.is_authenticated()
)
self.assertTrue(is_authenticated)


可読性がめっちゃいい感じ。気に入った。
comment: 0

PythonのDeveloperスプリントに行った振り返り

 今月にあったPyConJPに付随していたdeveloperスプリントに行ってきた。テーマは自由なので、自分のライブラリに上がっていたIssueを一つ解決してきた。あと当日力を貸してくれた@mpenkovにもう一つ解決してもらってきた。
 ちなみにぼくのライブラリはスター数は60を超えるぐらいだが、NASAのリポジトリを見るとすみっこで使われていたりする。NASA。

 さてそもそも。Pythonコミュニティに参加すること自体が、前日のPyConJPで初めてだった。ConnpassでPyConJPへの参加登録をしたのち、そのあたりの情報を見ていたらDeveloperスプリントのページがあって、だれでも参加していいし、だれでもリーダーになってテーマを立てていいとあったので登録した。ちょうど手を付けていなかったIssueも二つほどあったので。
 一つはある規格を熟知していなければ解決のための機能を実装できなかったが、もう一つはPythonを書ければいけるだろうというレベルだった(それでもPython2.7, 3.3 - 対応のライブラリなのでそこまで易しいわけでもないが)。だから一つは自分でやって、もう一つをだれかにやってもらって、OSSへコントリビュートするきっかけとしてもらいたかった。さいわいそのだれかというのはすごく優秀な人が来て、こちらも勉強させてもらえて本当にラッキーだった。
 OSSは最高である。かつて職のなかったぼくに、プログラミングの上達の手段をくれた。Pythonの画像処理ライブラリであるPillowで、ある問題の解決のためのクラスを書いて相談してみたところ、「TravisCIを使ってみなよ(Coverallsもね)」と返信をくれて、ぼくのリポジトリへ初期設定のコードを書いてくれた。結局それがマージされることはなかった。しかしそれを機にCIを知り、よりプログラミングの奥へと学習を進めることができた。
 そんなことやらなんやらでOSSには感謝している。だからOSSにだれかが関わるきっかけを作りたかった。

 とりあえずだれかにコントリビュートしてもらいたかったのだが、ぼくのライブラリはとりわけ有名なわけでもない。だが価値のあるライブラリだ。これを使っている会社もあるぐらいだ。だからコミットの価値はある。スプリントのリーダーがテーマを書く欄に、NASAも使ってるというアピールを書かせてもらった。
 あとはGithubの機能を有効活用した。GistにIssue解決のために考えた道すじ、実際の解決法の実装の仕方、テスト実装箇所(自動テスト実装済み)、必要となる前提知識などを書いた。Projectsに、実際の進捗が一目でわかるようにカンバンを置いた。

 上記のような前準備をすることで、OSSにコミットしてみたいけど、自分でできるかわからないという人をどうにかこっちに引き込もうと画策してみた。その結果が出たのかはわからんが、当日に聡明なコミッタを一人得ることができた。
 後々その人はOSSへのコミット経験がないと知ったのだけど、当日は「TDDでいくよ」から始まって、とくになにも言わずにちゃんと文字列型、ユニコード型、バイト型あたりの2系と3系のカオスな世界を取りさばき、見事に2系3系の両対応で機能実装を仕上げてくれた。コードはぼくより数枚上手だったので勉強になった。いろいろありがとう。

 Developerスプリントは短時間で成果を出さねばならない。コミッタを得ようとしたぼくの準備が功を奏して、いろいろ明瞭化してあったのでスムーズに事は進み、予定のIssueはすべて解決できた。今度は自分のプロジェクトではないものに参加して、ほかの人はどう進めているのかを見てみたいところ。
comment: 0

このブログについて

ソース: https://github.com/hMatoba/tetsujin

 ブログのバックエンドを作るのが趣味になっているような気がする。
 はじめはPython on GoogleAppEngineで作って、それのデータベースをNoSQLにしてみたらどうだとか始めた。そのあとでTornadoやFlask、BottleといったPythonのフレームワークをいろいろやってみて、Node.jsはasync,awaitが入る前にやってちょっときついと思ってストップした。
 その後WPFで好きだったC#で作ってみたくなって、.NET Coreのリリース直後にAzureに乗るものを作った。そして今回、.NET Core 2.0のリリースがあったのを契機にDocker運用するものに作り直した。

 ASP.NET Core MVCを使ってみた感想。モデル、ビュー(Razor)、コントローラのみを使うような、SinatraやFlaskのような、うっすい使い方もできる。スキャフォルディングとかあるけど無理に使う必要はない。フレームワークに縛られてるような感覚は強くない。その点、うまくやれるかは書き手次第。


 このブログはタグに並べた要素が全部盛りで実装されている。
 C#が好きなので、それがオープンソースになったのがうれしかった。だからほかのオープンソース技術をメインに組み合わせて、C#はMSの用意したセット(WindowsOSでIISとかSQLServerとかって構成)じゃないと使えないなんてことはないというのを実践してみたかった。DebianでNginx、MongoDBなどって構成。
 使用サービスの構成としては、Github、TravisCI、Docker Hub、Hyper.shが主なところ。
・Github→バージョン管理
・TravisCI→CIサービス
・Docker Hub→ビルドしたコンテナイメージの置き場所
・Hyper.sh→Docker開発者が「これほぼDockerやんけ」と言うぐらいにDockerライクなコンテナホスティングサービス。もちろんDockerで作ったコンテナをホスティングできる
 使用サービスの構成はシンプルなほうがいいという一般論も存在するだろうけど、これはこれで楽をするための構成を敷いている。Dockerを使ったサーバの構成管理、自動テスト、CIサービス上での自動テストからの自動デプロイなどなど。アプリの開発継続に注力できる。

 Githubへのプッシュで、TravisCIでのビルド、テストが行われる。テストにパスしてなおかつブランチがmasterだったらならDocker Hubへビルドされたコンテナイメージがアップされる。そののち、Hyper.shでそのコンテナイメージをプル、デプロイまでのコマンドが自動で流れる。Docker Hubを経由しているので、コンテナイメージがビルドの産物として残るようになっている。


・運用お値段
 VPSは安い。でもOSの管理が必要だからヤダ。AWSとかAzureのイカしたPaasを使いたいけど高い。
 DockerコンテナホスティングをしてくれるHyper.shというサービスがあり、これで月2000円以下で運用できている。リバースプロキシコンテナ+アプリコンテナの2コンテナをランニングさせるコスト1000円に加え、個人的にいろんなアプリで使うMongoDBを仕方なくVPSで運用しているコスト600円程度となっている。VPSとメジャーどころのPaasの中間あたりのお値段。
 問題としてHyper.shが日本にもアジアにもリージョンがないことによるレイテンシがががというのがあるけども妥協。


・構成
 ファイルはAzureStorage
 ↑にも書いたけどコンテナホスティングで運用していて、走っているコンテナはリバースプロキシ用一台とアプリ用一台
 証明書はLet's Encrypt
 コンテナのOSは、リバースプロキシのほうがAlpine、アプリはDebian
 テストはxUnitによる単体テストが実行できるようにしてある
 それに加えてSeleniumでのE2Eテストも用意してある。Seleniumは、一度C#で動かしたらきつかったのでPythonで動かしている
 バージョン管理はGithub
 Githubへのデプロイで、TravisCIでのデプロイが走る
 TravisCIではとりあえずSeleniumでE2Eテストをやっている
 TravisCIでテストに成功したら、そこからHyper.shへ自動デプロイが始まる
 ただし自動デプロイが始まるのはmasterブランチへのコミット時のみ


 いろいろ構成していて半分は趣味で試したかったものであるが、同時にデプロイの自動化という一年前から突き詰めたかった課題への挑戦もしている。これのおかげで、今後このブログエンジンを改良していくのにせっせと手作業でデプロイをする必要がなくなった。


・なおしたい点
 dotnetコマンドが現状docker composeを使ったプロジェクトをビルドしてくれない。だから一部プロジェクトでCI環境でのビルドができず、開発環境でビルドしたものをDocker Hub経由で使っている。CI環境でビルドができないのはSDKの不足が原因。そのSDKはVisualStudio Communityに用意されているのだが、SDKがオープンソースになっていないという理由でdotnet CLIに入れるかどうかもたついている。
'17/9/28追記
docker composeが入っているプロジェクトのビルド方法を変えて対応完了

comment: 0