orangain flavor

じっくりコトコト煮込んだみかん2。知らないことを知りたい。

Heroku上のDjangoアプリで静的ファイルをS3から配信する(前編)

HerokuにDjangoアプリケーションを置いて、静的ファイルもHerokuから配信した場合、USまでのレイテンシがあるので1ファイルごとに結構な時間がかかります。

試しに同じアプリケーションをさくらVPS(大阪)と、Heroku(US)に置いた場合、さくらでは 30ms〜150ms程度でロードできるのに対し、Herokuでは200ms以上かかりました。

HTML1ファイルだけならともかく、CSSやJS、画像のロードにも時間がかかるため、体感時間は結構変わります。

そもそも静的ファイルをアプリケーション・サーバーから配信するのはCPU、ディスク、ネットワークなどのリソースの面でも無駄が多く、推奨されていません。

そこで今回は、静的ファイルをAmazon S3のTokyoリージョンから配信してみました。いわゆる Web Storageパターン です。

長くなったので前後編に分けています。前編はHerokuに限らない一般的なDjangoアプリケーションの話をし、後編 ではHeroku特有の話をします。

ライブラリのインストール

$ pip install django-storages boto

django-storages はDjango用のカスタム・ストレージ・バックエンドを集めたライブラリです。

botoAWSPythonインターフェイスを提供するライブラリです。django-storagesのs3-botoバックエンドがbotoを使用する形になります。

requirements.txtにも忘れずに書いておきます。(バージョンはご利用のものに合わせてください)

boto==2.29.1
django-storages==1.1.8

S3バケットの作成

S3に適当な名前でバケットを作成して、Static Website Hostingを有効にしておきます。

今回は static.bus.capybala.com という名前にしました。 日本に置きたかったのでTokyoリージョンを選択しています。

Access Keyの作成

また、このバケットにファイルを追加できる権限を持つAccess KeyとSecret Access Keyを作っておきます。

以下の様なポリシーで良いでしょう。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::static.bus.capybala.com",
        "arn:aws:s3:::static.bus.capybala.com/*"
      ]
    }
  ]
}

Djangoの設定

settings.pyに以下の設定を追加します。

INSTALLED_APPS = (
    ...
    'storages',  # 行を追加
    ...
)

# S3バケットのURL(サブドメインのURLを使ってもどちらでもいいです。)
STATIC_URL = 'https://s3-ap-northeast-1.amazonaws.com/static.bus.capybala.com/'

# collectstaic時にS3を使う
STATICFILES_STORAGE = 'storages.backends.s3boto.S3BotoStorage'
# 作成したS3バケットの名前
AWS_STORAGE_BUCKET_NAME = 'static.bus.capybala.com'
# これをTrueにしたほうがファイル変更のチェックが速くなる
AWS_PRELOAD_METADATA = True

以下のようにDEBUGが有効かどうかによってSTATIC_URLをローカル配信と切り替えても良いでしょう。

if DEBUG:
    STATIC_URL = '/static/'
else:
    STATIC_URL = 'https://s3-ap-northeast-1.amazonaws.com/static.bus.capybala.com/'

AWSのクレデンシャル

AWSAccess KeyとSecret Access Keyは秘密の値なので、settings.pyには書きません。Twelve Factor に従って、環境変数AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYで渡します。

.envファイルに書いておきます。

AWS_ACCESS_KEY_ID=***
AWS_SECRET_ACCESS_KEY=***

アップロードのテスト

ここまでの設定をした上で、以下のようにcollectstaticを実行すると、静的ファイルがS3バケットにアップロードされるはずです。

$ foreman run python manage.py collectstatic --noinput

細かい話(アップロード時のエラー)

S3への大きなファイルのアップロード時にBroken pipeというエラーが発生する問題に遭遇したので、コメントにあるように~/.botoというファイルを作って対処しました。

$ cat ~/.boto
[s3]
host = s3-ap-northeast-1.amazonaws.com

なお、後編のHerokuでは特に必要ありませんでした。

前編はここまでです。後編ではHerokuを使います。

参考

Rubyによるデザインパターンを読んだ

恥ずかしながら10年以上プログラミングをやっていて、今までちゃんとデザインパターンを勉強したことがありませんでした。

それぞれのパターンはなんとなく知っているけど、ちゃんと理解できてはいないという状態でした。

デザインパターンを勉強しようとしたことは何度かありました。しかしJavaC++で解説した書籍・Webサイトでは、いちいち本質的ではない基底クラスやインターフェイスを作っているように見えて、理解が進みませんでした。

そこで読んだのが「Rubyによるデザインパターン」です。

Rubyによるデザインパターン

Rubyによるデザインパターン

この本の良いところ

この本が良いと思ったのは以下の点です。

  1. Rubyの動的な性質を活かしている
  2. パターンが厳選されており、説明の順序もわかりやすい
  3. 解決したい問題とそれを解決するコードがある

1. Rubyの動的な性質を活かしている

本書では、Rubyの動的な性質を活かすことで、無駄な基底クラスやインターフェイスなしにパターンを実現しています。

最初は Java に近いクラス設計をした上で、Rubyのパワーでリファクタリングしていき、簡潔なコードでデザインパターンを表現していきます。

ちなみに第2章にRuby自体の解説もあるので、Rubyに詳しくない人でも読めると思います。

2. パターンが厳選されており、説明の順序もわかりやすい

本書では、GoFの23パターンのうちの14パターン+Rubyオリジナルの3パターンを解説しています。

一度に多くのパターンを学んでも理解が追いつかなくなるので、よく使うものに絞られていて良かったです。

説明する順番も、パターン同士の結びつきが考慮されており、理解しやすいです。最初に Template Method というわかりやすいものを取り上げ、次に継承ではなく委譲で実現するために Strategy を使うといった具合です。

ABC順にAbstract Factoryから解説されると辛いです。

3. 解決したい問題とそれを解決するコードがある

それぞれのパターンについてまず問題があり、それを解決するよう設計を改善していくという流れで説明されているのでわかりやすいです。

なぜ他のやり方ではダメで、そのやり方を選んだのかがわかるので、理解が深まります。

Webサイトによっては、UMLやコードがほとんどで、背景が簡単にしか書かれていないことがありますが、そういうサイトは一度ちゃんと理解した人が見返すためのものなんだと気づきました。

さらに知るために

本書で、解説しているのはGoFの23パターンのうちの14パターン+Rubyオリジナルの3パターン(DSLメタプログラミング、Convention over Configuration)です。

GoFのパターンのうち、解説されていないのは以下の9パターンです。

  • Prototype
  • Bridge
  • Facade
  • Flyweight
  • Chain of Responsibility
  • Mediator
  • Memento
  • State
  • Visitor

これらを含めて、今後改めてGoF本を読んで勉強したいと思います。

オブジェクト指向における再利用のためのデザインパターン

オブジェクト指向における再利用のためのデザインパターン

まとめ

パターンによって、あるあるだなーとか、こんなやり方があるんだとか、感想はいろいろでした。ただ、全体を通して名前がついていることの重要性、そして「パターン」という言葉の意味を理解できました。

名前をつけて他人と共有できる「パターン」は、デザインパターンだけではないので今後も勉強していきたいと思います。

ここまで書いておいて非常にアレですが、本書は現時点で入手が困難です。実は、何年も前に良さげだなーと思って買った後、そのまま積んであったのです。 

新品の紙の本は売ってないし、電子書籍にもなっていないのが残念です。良書なので、ぜひ電子書籍で発売してほしいです。

ちなみに原著は電子書籍でも販売されていますが、読んだことないのでなんとも言えません。Amazon.co.jpのレビューには平易で読みやすいと書かれていますが、Amazon.comにはKindle版のフォーマットが非常に読みにくいというレビューもあります。

Design Patterns in Ruby (Addison-Wesley Professional Ruby Series)

Design Patterns in Ruby (Addison-Wesley Professional Ruby Series)

Mac OS X 10.9.2でGitHubにアクセスするとサーバー証明書のエラーが発生する場合の解決策

問題

Mavericks の最新のアップデート(10.9.2)をインストールした後、Google ChromeGitHubにアクセスした時に、SSLサーバー証明書のエラーが表示されるようになりました。

このように「実際の github.com に接続できない」と表示されます。

f:id:mi_kattun:20140301125547p:plain アドレスバーのアイコンをクリックして証明書を表示させると、以下のようにDigiCertによって署名されていることがわかります。

f:id:mi_kattun:20140301125615p:plain Bitbucketでも同じくエラーが表示されます。Bitbucketの証明書もDigiCertによるものです。

f:id:mi_kattun:20140301125558p:plain

Safariでも証明書のエラーが表示されました。 *1

f:id:mi_kattun:20140301125607p:plain ターミナルで、git cloneしたときにも以下のようにエラーが表示されます。

$ git clone https://github.com/rails/rails.git
Cloning into 'rails'...
error: SSL certificate problem: Invalid certificate chain while accessing 
https://github.com/rails/rails.git/info/refs?service=git-upload-pack
fatal: HTTP request failed

解決策

調べたところ、以下のページが見つかりました。 キーチェーンアクセスのログインの欄にある、DigiCertの証明書を削除すると直ったと書かれています。

osx - Can not clone any git repository from github - Super User

 fixed my problem by checking my Keychain (Application -> Utilities -> Keychain Access.app)

I figured out that I had a own Digicert Certificate in my loging Keychain. It looks like this was broken. I have removed it and after this everything works fine.

Now https://github.com has a green "button" in the url bar instead of an grey one. 

キーチェーンアクセスを確認してみると、確かに「ログイン」の欄にDigiCertの証明書があります。 f:id:mi_kattun:20140301125644p:plain

この「DigiCert High Assurance EV Root CA」を削除したところ、正常に表示されるようになりました。

注意:証明書の削除は間違って行うと、正常なWebサイトにアクセスした時にも警告が出るようになり、元に戻せません。自己責任で行ってください。

f:id:mi_kattun:20140301125624p:plain 証明書のツリーを見ると、一番上がオレンジ色のルート証明書になっていることがわかります。 f:id:mi_kattun:20140301125635p:plain

原因

原因はよくわかっていませんが、思い返すと2011年ごろにGitHubの証明書がエラーになるとかで、証明書を自分で追加したような気がします。

goto fail; goto fail;のバグが修正されたことで、以前自分で追加した証明書が不正だと判定されるようになったのかもしれません。

しかし、2011年の時点ではMavericksではなかったので、その時点では問題なかったはず。もう少し勉強しないと。。

*1:ちなみにFirefoxではエラーになることなく閲覧できました。

Python/FlaskアプリからOAuthでGitHub APIを使う

このようなサンプルが意外と見つからなかったので作りました。

GitHub API

GitHubWeb API を提供しており、リポジトリやユーザーなどをAPI経由で取得・操作することができます。

執筆時の2014年2月現在では、v3betaの2つのバージョンがあり、デフォルトはbetaです。ややわかりにくい名前付けですが、betaのほうが古いようで、2014年4月15日からv3がデフォルトになる予定です。

利用するバージョンはHTTPリクエストのAcceptヘッダーで指定できます。v3betaの違いはあまり大きくないみたいなので、現時点では特に指定せずにデフォルトのバージョンを使っても問題なさそうです。

認証方法 としては、Basic認証、OAuth2 Token、OAuth2 Key/Secretの3種類が利用できます。 今回は、Flaskを利用したWebアプリなので、OAuth2 Tokenを利用します。

PythonでOAuth2

oauth2というStarがたくさんついたライブラリがありますが、2年以上メンテナンスされておらず、名前の割にOAuth2には対応していないようです。

フォークされたものもありますが、メンテナンスされておりStarも多いRauthが良さそうだったので利用します。

Rauthは内部でいい感じに Requests を使っており、使いやすいです。

サンプルアプリケーション

以下のようなアプリを作りました。GitHubで公開しています。

# coding: utf-8

import os
import binascii

from flask import Flask, request, session, abort, redirect
from rauth import OAuth2Service

# Read secret keys from env vars
# See: http://12factor.net/config
GITHUB_CLIENT_ID = os.environ['GITHUB_CLIENT_ID']
GITHUB_CLIENT_SECRET = os.environ['GITHUB_CLIENT_SECRET']

app = Flask(__name__)
app.secret_key = os.environ['SESSION_SECRET_KEY']  # necessary for session

# Set up service wrapper for GitHub
# - `client_id` and `client_secret`: available in an application page of GitHub
# - `name`: name for human
# - `authorize_url` and `access_token_url`: available in the GitHub's developer guide
#    http://developer.github.com/v3/oauth/#web-application-flow
# - `base_url`: base url for calling API
github = OAuth2Service(
    client_id=GITHUB_CLIENT_ID,
    client_secret=GITHUB_CLIENT_SECRET,
    name='github',
    authorize_url='https://github.com/login/oauth/authorize',
    access_token_url='https://github.com/login/oauth/access_token',
    base_url='https://api.github.com/')


@app.route('/')
def top():
    """
    Top page
    """

    # For authorized users, show welcome message and links
    if 'username' in session:
        return 'Welcome @{0}! <a href="/repos">Repos</a> <a href="/logout">Logout</a>'.format(
            session['username'])

    # Generte and store a state in session before calling authorize_url
    if 'oauth_state' not in session:
        session['oauth_state'] = binascii.hexlify(os.urandom(24))

    # For unauthorized users, show link to sign in
    authorize_url = github.get_authorize_url(scope='', state=session['oauth_state'])
    return '<a href="{0}">Sign in with GitHub</a>'.format(authorize_url)


@app.route('/callback/github')
def callback():
    """
    OAuth callback from GitHub
    """

    code = request.args['code']
    state = request.args['state'].encode('utf-8')

    # Validate state param to prevent CSRF
    if state != session['oauth_state']:
        abort(400)

    # Request access token
    auth_session = github.get_auth_session(data={'code': code})
    session['access_token'] = auth_session.access_token

    # Call API to retrieve username.
    # `auth_session` is a wrapper object of requests with oauth access token
    r = auth_session.get('/user')
    session['username'] = r.json()['login']

    return redirect('/')


@app.route('/logout')
def logout():
    """
    Logout
    """

    # Delete session data
    session.pop('username')
    session.pop('access_token')

    return redirect('/')


@app.route('/repos')
def repos():
    """
    List recently updated repositories
    """

    # Restore auth_session from the access_token stored in session
    auth_session = github.get_session(session['access_token'])
    r = auth_session.get('/user/repos', params={'sort': 'updated'})
    repos = r.json()

    list_items = []
    for repo in repos:
        list_items.append('<li>{0}</li>'.format(repo['full_name']))

    return '<ul>{0}</ul>'.format('\n'.join(list_items))


if __name__ == '__main__':
    app.run(debug=True)

前提

Foremanですが、Rubyの環境を作るのが面倒な場合は、バイナリ1つで実行できるforegoがお勧めです。Go言語++

動かすまでの手順

1. GitHubでアプリケーションを登録

https://github.com/settings/applications/new でアプリケーションを新規登録します。

Authorization callback URLhttp://localhost:5000/callback/github とします。 リダイレクトされるだけなので、ローカルアドレスで大丈夫です。

2. クローンして依存関係をインストール

$ git clone https://github.com/orangain/example-github-oauth-flask.git
$ cd example-github-oauth-flask
$ virtualenv --python=python3 venv  # virtualenvを使う場合
$ . venv/bin/activate  # virtualenvを使う場合
(venv)$ pip install -r requirements.txt

執筆時点で最新のRauth 0.6.2が依存するRequests 1.3.2では、インストール時にSyntax Errorが表示されますが、特に問題なく使えます。

3. 設定ファイルを配置

example-github-oauth-flask/.env を以下の内容で作成します。リポジトリに入れるとマズイ設定項目は環境変数から読み込むのが 12factor のやり方です。

GITHUB_CLIENT_ID=(1で作成したアプリのClient ID)
GITHUB_CLIENT_SECRET=(1で作成したアプリのClient ID Secret)
SESSION_SECRET_KEY=(ランダムな文字列)

4. サーバーを起動

(venv)$ foreman run python github_oauth.py

これで、 http://localhost:5000/ にアクセスすれば以下のようなページが表示され、GitHubAPIを利用できます。

f:id:mi_kattun:20140226230508p:plain

参考サイト

unattended-upgradesはインストールしただけでは動かない

2018-06-17追記: パッケージの自動削除について追記しました。検証環境はUbuntu 16.04 LTS x86_64です。

ということを知らなかったので、Debian/Ubuntuでの自動アップデートについて改めて調べてみました。

動作を確認した環境は、Ubuntu Server 12.04 LTS x86_64です。

自動アップデートを有効にする

結論から言うと、以下のようにインストールすれば有効になります。既にインストールされている環境では3行目だけを実行すればよいです。

$ sudo apt-get update
$ sudo apt-get install unattended-upgrades
$ sudo dpkg-reconfigure -plow unattended-upgrades  # Choose "Yes"

3行目のコマンドは、以下のファイルを生成してくれます。*1

$ cat /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

不要パッケージの自動削除

自動アップデートを有効にしているとカーネル関連のファイルが溜まってディスクフルになることがあります。これを防ぐために、不要なパッケージを自動削除します。

$ sudo vim /etc/apt/apt.conf.d/50unattended-upgrades

以下の設定をtrueにします。

Unattended-Upgrade::Remove-Unused-Dependencies "true";

ログ

自動アップデートのログは、 /var/log/unattended-upgrades/ 以下に出力されます。 デフォルトでは毎日1回実行されます。

ドキュメント

自動アップデートを有効にする方法は、 /usr/share/doc/unattended-upgrades/README の冒頭に書かれています。

Unattended upgrades
-------------------

This script can upgrade packages automatically and unattended.
However, it is not enabled by default.  Most users enable it via the
Software Sources program (available in System/Administration).

If you would prefer to enable it from the command line, run
"sudo dpkg-reconfigure -plow unattended-upgrades".

ちなみに、 Ubuntu Server Guide には /etc/apt/apt.conf.d/10periodic を以下のように書き換えるという方法が載っています。これでも設定内容は同じことなので動くと思いますが、古い方法らしいです。 *2

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";

参考サイト

*1:-plowは優先度がlow以上の質問を表示するオプションですが、付けなくても変わらなかったので必要なのかよくわからないです。

*2:参考サイトに挙げたServer Faultの質問におけるHendy Irawan氏によれば。

iTerm2でSolarizedを使うと一部の色が灰色になってしまう問題の対処法

iTerm2Solarizedの配色を使う場合、以下の記事が参考になります。

問題

しかし、単純にダウンロードしたColor Schemeを設定しただけど、それまで色がついていたはずの文字列が灰色になってしまうことがあります。

特にSSHで他のサーバーにログインした時に色がなくなって困ることがありました。

f:id:mi_kattun:20140219235622p:plain こんな感じで色あせたように見えます。

解決策

以下のページに書いてありました。iTerm2の設定で、Profiles → Text → "Draw bold text in bright colors" のチェックを外すと、綺麗に表示されるようになります。

iterm text color highlighting · Issue #71 · altercation/solarized

f:id:mi_kattun:20140219235635p:plain 以下のように世界が色を取り戻します。 f:id:mi_kattun:20140219235627p:plain

原因

Solrizedのカラーパレットをよく見ると、Brightの列は一部の色が灰色になっています。 "Draw bold text in bright colors"のチェックが付いていると、iTerm2は太字をこの灰色でレンダリングしてしまうのです。

f:id:mi_kattun:20140219235642p:plain

まとめ

Issueを眺めていると、この問題は上で挙げたIssueの他にもいろんな人が2〜3年前から指摘していますが、直る気配はないようです。

とりあえず、iTerm2でSolrizedを使う場合は、"Draw bold text in bright colors"のチェックを外すか、色を自分でカスタマイズすると綺麗に表示できます。

Ruby製アプリじゃなくてもTurnipで自動受け入れテストがしたい

Python製のWebアプリケーションの自動受け入れテストをしたくて、調べてみました。

Ruby界隈だと、最近ではCucumberに代わってTurnipというツールが流行っているみたいなので試してみました。


(c) .foto project

自動受け入れテスト用のライブラリ

Rubyで自動受け入れテストをしようとすると、以下の3つの選択肢があるようです。

Cucumberは以前にも使ったことがあるので、今回はTurnipを選択してみました。詳しい説明は以下のページに譲ります。

Webページを自動操作するライブラリ

Webページの自動操作にはCapybaraを使います。デフォルトではRackTestというドライバが使われますが、これはHTTP通信を行わないドライバでありRackアプリケーションでしか使えません。

このためHTTP通信を行う他のドライバを使います。ヘッドレスでJavaScriptを利用できるドライバとして、以下の2種類があるようです。

最近ではPoltergeistのほうがインストールが簡単かつ高速で人気らしいので、今回はPoltergeistを使いました。

前提

前提として、Ruby 2.1*1 およびBundlerがインストールされているものとします。

PoltergeistのインストールにはPhantomJSが必要なので、 PoltergeistのREADMEに従ってインストールします。

Macならば brew install phantomjs で簡単にインストール出来ました。

準備

適当なフォルダを作ります。

$ mkdir bus-spec
$ cd bus-spec

以下のコマンドでGemfileを作ります。

$ bundle init

Gemfileを以下のように編集します。

source "https://rubygems.org"

gem 'turnip'
gem 'capybara'
gem 'poltergeist'

以下のコマンドでGemをインストールします。

$ bundle install --path=vendor/bundle

TurnipのREADMEを参考に、テストに必要なファイル・フォルダを作成します。

$ mkdir -p spec/{features,steps}

.rspecを以下の内容で作成します。

-r turnip/rspec

spec/spec_helper.rbを以下の内容で作成します。

Dir.glob('spec/steps/*steps.rb') { |f| load f, true }

require 'capybara/rspec'
require 'capybara/poltergeist'

Capybara.default_driver = :poltergeist

# 以下の関数はGeolocation APIをシミュレートするヘルパー関数です。
def simulate_location(latitude, longitude, accuracy=0)
  Capybara.current_session.execute_script <<-EOS
    window.navigator.geolocation = {
      getCurrentPosition: function(success) {
        var position = {coords: {latitude: #{latitude}, longitude: #{longitude}, accuracy: #{accuracy}}};
        success(position);
      }
    };
  EOS
end

rspecを実行して以下のように表示されれば準備完了です。

$ bundle exec rspec
No examples found.


Finished in 0.00005 seconds
0 examples, 0 failures

Turnipによる受け入れテスト

それではTurnipによる自動受け入れテストを書いていきます。

spec/features/search.featureには、プログラムに詳しくない人が読んでもわかる書式(Gherkin形式)で機能を記述します。

# encoding: utf-8
# language: ja

機能:PCまたはスマートフォンでバス停を検索
     シナリオ:トップページにアクセスして検索する
          前提ポケドスにアクセスする
          前提"京都市役所"に居る
          もしトップページを表示する
          ならば画面に"近くのバス停を探す"ボタンが表示されていること
          もし"近くのバス停を探す"ボタンをクリックする
          ならば画面に"この近くのバス停"と表示されていること
          かつ画面に"京都市役所前: 91m"と表示されていること

プレースホルダーに日本語を使う場合は、""で囲うなど少し工夫が必要です。参考:Ruby - Turnip で placeholder のマッチングを柔軟にする - Qiita

spec/steps/bus_steps.rbには、上で書いたGherkin形式のステップ(「前提」「もし」「ならば」など)に対応するプログラムを書きます。

# encoding: utf-8

step 'ポケドスにアクセスする' do
  Capybara.app_host = 'http://bus.capybala.com/'
end

step ':placeに居る' do |place|
  case place
  when '京都市役所'
    @place = {lat: 35.01129870069897, lng: 135.76807713296512}
  else
    raise "Unknown place"
  end
end

step 'トップページを表示する' do
  visit '/'
  simulate_location(@place[:lat], @place[:lng]) if @place
end

step '画面に:labelボタンが表示されていること' do |label|
  expect(page).to have_link(label)
end

step ':labelボタンをクリックする' do |label|
  click_link label
end 

step '画面に:textと表示されていること' do |text|
  expect(page).to have_content(text)
end

再度rspecを実行して以下のように表示されれば成功です。

$ bundle exec rspec
.

Finished in 2.92 seconds
1 example, 0 failures

まとめ

Turnipはこのように、テストする機能を自然文で書けるテストライブラリであり、Capybaraと組み合わせることでWebサービスの自動受け入れテストを行えます。

2012年に書いた記事と比べると、Cucumber → Turnip、capybara-mechanize/capybara-webkit → Poltergeistと変化しました。

正直Rubyをゴリゴリ書いていない身としては、TurnipのCucumberに対する利点として挙げられる、RSpecが使える点とプレースホルダーが簡単に書ける点が、それほど嬉しいことなのかよくわかりませんでした。

また、CucumberやTurnipのGherkin書式は、普通のテストを書くのに比べてコストがかかる割に、どの程度受け入れられるの?という考えもあります。 参考:RSpecで、Cucumberのような結合試験のspecを記述する - 高尾宏治日記 on はてな

うまく使えば強力なツールですが、使いドコロをよく考えて使う必要があるでしょう。

ここで説明したものをもう少し複雑にしたコードをGitHubで公開しています。

参考サイト・書籍

*1:今回は2.1を使いましたが、1.9.3以上ぐらいで動くと思います。