oinume journal

Scratchpad of what I learned

WebアプリケーションのE2EテストをGoで書く

これはGo Advent Calendar 2016の18日目の記事です。今回はGoでE2Eテストを行うためのライブラリagoutiについて書きます。

GoでE2Eテストを書く理由

WebアプリケーションのサーバーサイドをGoで書いている場合、GoでE2Eテストを書くメリットとして

  • JavaScriptが得意ではないエンジニア(自分)でもE2Eテストがガリガリかける
  • E2Eテスト実行時のカバレッジが取れる=サーバーのコードのどこを通ったかがわかる

があると思っている。特に2番めの理由が大事で、「E2Eテストを全部回した結果、サーバーのこの部分のコードは通っている」とわかるのはけっこう大きなメリットなのではないかと。どこがテストされている・いないを把握することで、「ここはE2Eテストだと難しいからユニットテストでカバーしよう」というような戦法が取りやすいと思う。

じゃあ具体的にGoでどうやってE2Eテストを書くのか、ということを次に説明する。

Go製WebDriverクライアントagouti

GoにはagoutiというSelenium WebDriverのクライアントがあるので、今回はこれを使う。agouti自体は単にWebブラウザを操作するライブラリなので、あわせてChromeDriverなどをインストールする必要がある。MacにChromeDriverをインストールするのはHomebrewでbrew install chromedriverしてインストールするのが楽で、それ以外のOSではChromeDriverのサイトからダウンロードしてすればOK。

一例として、下記のようなagoutiを使うGoのコードを書いて動かすと、Chromeが起動して https://ifconfig.co/ のページが表示されて5秒スリープする。

    webDriver := agouti.ChromeDriver() // ChromeDriverを使う
    if err := webDriver.Start(); err != nil {
        return err
    }
    defer webDriver.Stop()

    page, err := webDriver.NewPage()
    if err != nil {
        t.Error(err)
    }
    // Navigateで指定したURLにアクセスする
    if err := page.Navigate("https://ifconfig.co/"); err != nil {
        t.Error(err)
    }
    time.Sleep(5 * time.Second)

agoutiでWebアプリケーションをテストする

もう少し実践的な例として、フォームに入力された値を表示する簡単なWebアプリをテストするケースを考える。ディレクトリ構成は

  • cmd: サーバーの実行ファイル用
  • app: Webアプリケーションの本体
  • e2e: E2Eテスト置き場

という感じになっていて、全てのコードはgo-e2e-test-sampleのリポジトリに上げてある。

この、単にフォームで送信された名前を表示するだけのWebアプリケーションのコードはこんな感じ。

そして、E2Eテストのコードはこんな感じ。TestIndex/にアクセスしている。

ポイントとしては

  • testMainでサーバーとChromeDriverを起動
  • TestIndex関数内でwebDriver.NewPageでPageを生成し、Page.Navigateで起動したサーバーのURLにアクセス
  • Page.FindByXPathでHTMLの要素を検索してformをSubmit

というような流れになっている。なお、今回はXPathを使ってHTMLの要素を検索しているが、CSS Selectorを使うFindなんかもある。詳しくはgodocを見れば詳しく書いてある。

そして、このe2e_test.gogo testコマンドで実行すると、Chromeが立ち上がってテストが実行される。

$ go test -v ./e2e

カバレッジを取ってみる

では次にカバレッジを取って、どの部分がE2Eテストによって実行されているのかを調べてみる。go testの引数に-coverpkg-coverprofileをつけて実行する。

$ go test -v -cover -coverpkg=./app -coverprofile=cover.out ./e2e

これでcover.outにカバレッジのためのファイルができるので、go tool coverでHTMLとして表示してみる。

$ go tool cover  -html cover.out

ブラウザが立ち上がって下記のような画面が表示されるので、今回のE2Eテストで実行されているコードがひと目でわかる。

go_tool_cover_html

まとめ

  • agouti便利
  • go testでE2Eテストを実行できるようにしておくとgo tool coverなどのGoエコシステムがそのまま使える

Better Heroku Schedulerを探したらCustom clock processesにたどり着いた

これはHeroku Advent Calendar 2016の11日目の記事です。

HerokuではHeroku Schedulerというcronのようなサービスがあるのですが、使い込むうちに「Daily, Hourly, Every 10 minutesの単位でしかジョブを動かせない」という制限がつらくなってきたので、より良い代替であるCustom clock processesを試してみたという話。

Custom clock processesとは?

clockというプロセスを立ち上げっぱなしにして、そのプロセス内でスケジューリングされたジョブを実行する仕組み。Heroku Schedulerは実行される時だけプロセスが立ち上がるけど、clockの場合は常にプロセスが常駐する。

各言語のScheduler

HerokuのCustom clock processesのドキュメントで触れられているのはJavaとPythonの2種類だけど、各言語ごとにだいたいcron likeなSchedulerが用意されている。今回は自分が慣れているPythonのAPSchedulerを試してみた。

APSchedulerの設定

まずPythonのモジュールのインストールのために、requirements.txtというファイルを用意する。

echo APScheduler > requirements.txt

次にProcfileに以下を追記する。

clock: python clock.py

ここで指定されているclock.pyがジョブの設定が書かれたファイルになる。どのように記述するべきか、詳しくは公式のドキュメントに書かれている。以下は1分毎にhello worldを出力する設定。

from apscheduler.schedulers.blocking import BlockingScheduler
import subprocess

scheduler = BlockingScheduler()

@scheduler.scheduled_job('interval', minutes=1)
def timed_job():
    print("Run notifier")
    subprocess.run("hello world", shell=True, check=True)

scheduler.start()

最後に、APSchedulerを動かすのにはPythonが必要なので、buildpackを追加。

heroku buildpacks:add https://github.com/heroku/heroku-buildpack-python

あとはclock.pyをコミットしてディプロイすればOK。

ローカルで試したい場合

ローカルで試してみたい場合は以下のようにAPSchedulerをインストールして、clock.pyを直接呼び出せばOK。

pip install -r requirements.txt
python ./clock.py

Custom clock processesとFree Dyno Hours

Heroku Schedulerは実行する際にプロセスが起動されて、プロセスが動いていた時間だけ無料分のFree Dyno Hoursを消費するのだけど、custom clock processesはworkerのようにclockプロセスが常駐する形になるので、起動していた時間分だけFree Dyno Hoursを消費するようなので注意が必要。webと併用していると倍速でFree Dyno Hoursを消費するので気をつけましょう。

まとめ

Heroku Schedulerよりきめ細かい時限式のジョブを定義したいときはCustom clock processesを使おう!

mysqldumpで特定のレコードだけエクスポートする

忙しい人向けまとめ

  • mysqldumpの--whereオプションを使うと特定のレコードだけmysqldumpできる
  • --whereにはLIMIT句も指定できる
  • --whereオプションで大量のデータから一部だけをmysqldumpすることが可能

本文

mysqldump、データだけエクスポートしたりCREATE TABLEだけエクスポートできたり細かいところまで気が利いているなぁと思うツール。今日知ったのは --whereでdumpするレコードを限定できるよ、ということ。さらに、--whereにはLIMITも指定できるので、「特定の条件にマッチするレコード100件だけ」みたいなエクスポートもできる。

以下、具体例。

$ mysql -u <user> -p<password> <database>

mysql> CREATE TABLE hoge (
  id INT NOT NULL,
  name VARCHAR(255) NOT NULL,
  PRIMARY KEY (id)
);

mysql> INSERT INTO hoge VALUES (1, 'foo'), (2, 'bar'), (3, 'baz');

こんな感じでレコードを作って、--where 'id=1'を指定してmysqldumpしてみる。

$ mysqldump -u<user> -p<password> <database> hoge --where 'id=1'

(snip)
LOCK TABLES `hoge` WRITE;
/*!40000 ALTER TABLE `hoge` DISABLE KEYS */;
INSERT INTO `hoge` VALUES (1,'foo');
/*!40000 ALTER TABLE `hoge` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
(snip)

id=1のレコードだけmysqldumpされる。では次にORDER BYとLIMITをつけてみる。

$ mysqldump -u <user> -p<password> <database> hoge --where 'id>=2 ORDER BY id DESC LIMIT 1'

(snip)
INSERT INTO `hoge` VALUES (3,'baz');
(snip)

となって、idの降順でソートして1件だけmysqldumpができた。

まとめ

プログラムの動作検証のためにデータをmysqldumpしたいけど、本番データだとレコード数が多くて丸ごとmysqldumpするのがつらい時に--whereをつけると幸せになれるはず。

新しいMacで設定している項目 - macOS Sierra編

5年間MacBookPro 13-inch, Early 2011のRetinaじゃないヤツをSSD変えたりして延命しつつ使ってたのだけど、さすがに限界だったので新しいMacBookPro 13inch Retina のTouchBarなしモデルを買った。

久しぶりに自宅のMacをセットアップしたので、やったことをメモしておこうかなと。キーバインドを変えるために使っていたKarabinerがSierraでは使えなくなっていたのがつらかった。

トラックパッドでタップだけでクリックできるようにする

システム環境設定 -> トラックパッド -> タップでクリック

これを有効にするとトラックパッドでクリックしなくてもよくなるので楽。

トラックパッドでの右クリック

システム環境設定 -> トラックパッド -> 副ボタンのクリック(右下隅)

自分は2本指でやるのは苦手なので副ボタンのクリック(右下隅)にしている。ただ、新しいMacBookProだとトラックパッドが大きいので右下隅のタップだとつらいかもしれない。

キーのリピート速度

システム環境設定 -> キーボード

下記の設定のスライダーを一番右にする。

  • キーのリピート速度
  • リピート入力認識までの時間

ただ、これでも遅いのでさらに速くしたい場合はKarabinerまたはSierraの場合はKarabiner Elementsを使って設定する。

Karabiner Elementsでキーリピートをさらに速くする

を使う。

brew cask install karabiner
または
brew cask install karabiner-elements

でインストール可能。自分は下記のようにキーリピートの設定をしている。これでWindows並にカーソル移動や文字の削除が速くなる。

Karabiner-Elements

たまにこれを設定しないでカーソル移動が激遅の人がいるので見つけたら教えてあげるようにしてる。

同じアプリで別ウィンドウになっている場合の切り替えのショートカットをoption + tabにする

Chromeでたくさんウィンドウを開いていたりする場合にWindowsみたいに切り替えられるようにする。

システム環境設定 -> キーボード

ショートカットタブのキーボードを選んで、次のウィンドウを操作対象にするoption + tabにする

Tabで全ての項目を移動できるようにする

ショートカットタブでフルキーボードアクセスをすべてのコントロールにチェックにする。こうしないとSafariとかでTabで移動するときにチェックボックスとかに移動できない。

Dockの設定

最近のPCは横幅が大きいので、下ではなく左に置く。

  • サイズ小さめ
  • 拡大する
  • 画面上の位置:左

コンピュータ名を変える

デフォルトだと変な名前になっていることが多いので変える。

共有 -> コンピュータ名

から変えられる。(なんでこんなわかりづらいところにあるのか...)

Finderのウィンドウタイトルでフルパスを表示する

ターミナルから下記のコマンドで設定できる。

defaults write com.apple.finder _FXShowPosixPathInTitle -boolean true

スクリーンショットの保存先の変更

デフォルトだとデスクトップになっているが、このままだとデスクトップが悲惨なことになるので自分は~/Documents/screenshotというディレクトリを作って保存するようにしている。ターミナルで以下のコマンドで設定できる。

defaults write com.apple.screencapture location ~/Documents/screenshot
# この設定を反映する
killall SystemUIServer

なお、以下のコマンドで設定値を削除することができる。

defaults delete com.apple.screencapture location

最後に

そろそろこの辺の手動設定をどうにかして自動化したい…

DMM英会話でお気に入りの先生の空きレッスンがあった時にメール通知するlekcijeというサービスを作った

www.lekcije.com

lekcijeという、DMM英会話で講師に空きレッスンがあった時にメール通知するサービスを作った。もともとは下記で書いたものがベースになっていて、これに画面を付け足したという感じ。毎日家に帰ってから自分の空き時間でチマチマ作っていたので半年ぐらいかかってしまった。

oinume.hatenablog.com

作った動機としては

  • DMM英会話はレッスンが終わらないと次の分の予約ができない
  • 人気の講師はすぐ予約が埋まってしまう
  • でも意外とキャンセルが出るので、それを逃したくない

という感じ。もちろんお金をかければ1枚500円もするレッスンチケットを買いまくって予約するということはできるのだろうけど、さすがにそれは金銭的につらかったので技術で解決しようという試み。

実装に関して

本当はReactとかも使いたかったんだけど、どうしても時間が取れなくてSPA的なモノは諦めて、サーバーサイドでHTMLを生成するというオールドタイプな実装になっている。Herokuを使っているのでアメリカの東海岸からHTMLが配信されるのがつらいけど、静的ファイルはHTTP2を使って配信するようにしてパフォーマンス面には気を遣った。

久しぶりにWebサービスを作ったのだけど、普段の仕事ではサーバーサイドのコードしか書かないので、ロゴ作ったりキャッチコピー考えたりすることはいい勉強になった。プロの人はすごいなぁと思うばかりです。というわけで、DMM英会話をやっている人がいたらぜひ使ってみて下さい。

使った技術

  • HTTP2
    • Let's encryptでSSL証明書を取得
  • Frontend
    • bootstrapだと素っ気ないので、bootswatchを使った
    • ビルドにはWebpack使った(つらい)
  • Server (Go)
    • Web Applicaton Framework: gojiを使ったけど、素のnet/httpでもよかったかもしれない。もはやルーティング回りしかgojiを使っていない
    • ORMapper: gorm (つらい)
    • Vendoring: glide
    • Logging: uber-go/zap
    • Scraping: xmlpath
      • goqueryのコールバックスタイルがどうも好きになれなかったので。
    • Hot reloading: reflex
      • 開発時のホットリローディング
  • Database: MySQL 5.7
    • Database migration: goose使ったけど、sql-migrateにする予定
  • E2E test
    • agouti: Selenium WebDriver的なヤツ
  • Nginx: 静的ファイルの配信
  • メール送信: Sendgrid
  • Heroku
  • ngrok
    • スマホで動作確認したい時に便利過ぎた