Rails 4.1 MiniTest 導入

 今までRailsのテストには Rspec を使っていたのですが、今回はMiniTestを使うことになりました。
 上司の鶴の一声的なものだったので、頑張ればひっくり返せたかもしれませんが、せっかくの機会だったのでMiniTestの使い方を習得してみようと実装を始めました。

 が……

 日本語の資料がとにかく少ない……。
 びっくりするぐらい少ないので、基本的なところをさら~~っとまとめておこうと思います。

前提

 プロジェクトの途中でMiniTestを導入した体で進めていきます。
 アプリケーションを作る時からMiniTestの導入が決まっている場合は下記ページのほうが役に立つと思います。
 Railsテスティングガイド

インストール

 Gem (minitest と minitest-rails)をインストールしたら、まずは rails generate minitest:install コマンドを実行します。
 するとアプリケーションルートに test ディレクトリが生成され、その中に test_helper.rb ファイルが作られます。

テストファイルの作成

 すでにプロジェクトの製作が進んでおり、ひと通りモデルなどが設計されている状態からモデルのテストファイルを作るには、rails generate minitest コマンドを使います。

rails generate minitest:model Sample

 上記具体例であれば /test/models/sample_test.rb ファイルと /test/fixtures/samples.yml ファイルが作成されます。
 作成されたファイルの内容は以下のとおりです。

require "test_helper"

class SampleTest < ActiveSupport::TestCase

  def sample
    @sample ||= Sample.new
  end

  def test_valid
    assert sample.valid?
  end

end
# Read about fixtures at
# http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html

# This model initially had no columns defined.  If you add columns to the
# model remove the '{}' from the fixture names and add the columns immediately
# below each fixture, per the syntax in the comments below
#
one: {}
# column: value
#
two: {}
#  column: value

fixture を編集

 次に fixture を編集します。これはテスト時に使用するDBの値になります。
 sample テーブルに int 型の test があるなら、下のように書きます。

sample_1:
  test: 1
  created_at: <%= Time.now %>
  updated_at: <%= Time.now %>

 created_at と updated_at はある前提で書きました。
 view と同じように ruby のコードを埋め込むことも可能です。

テスト作成

 /test/models 配下のファイルを編集して、テストを書いていきます。
 記述方法は非常にシンプルで、中でクラスを初期化して該当メソッドを実行するだけ。命名規約も、メソッド名に test_ を接頭語としてつけるだけ。前準備は setup メソッドに、後処理は teardown メソッドに記述します。
 具体例を書きます。

require "test_helper"

class SampleTest < ActiveSupport::TestCase

  def setup
    # 前処理
    @sample = samples(:sample_1)
  end

  def teardown
    # 後処理
  end

  def test_sample
    assert_equal @sample.test, 1
  end

end

 4行目の samples に渡しているシンボル「:sample_1」は samples.yml の1行目に書いたやつです。
 内容は単純に前処理で生成した @sample の test を 1 と比較し、assert_equal メソッドを使って同じ値かを確認しています。
 MiniTest では assert メソッドでテスト結果を評価します。

assert BOOLEAN                # 実行結果が真なのかチェック
assert_equal VALUE_a, VALUE_b # 2つの値が同じかチェック
assert_empty ARRAY            # 中身が空かチェック
refute BOOLEAN                # 実行結果が偽なのかチェック
refute_equal VALUE_a, VALUE_b # 2つの値が異なっているかチェック

テスト実行

 rake test コマンドを実行してあげればOKです。
 テスト結果を見ながらテストを追加したり、直してあげたりしましょう。
 

備考

 テストファイルを作る際、名前空間がある場合はモデル作る時と同じで :: を使えば指定できる。

rails generate minitest:model NameSpace::Sample

 これで /test/models/name_space/sample_test.rb と /test/fixtures/name_space/samples.yml が生成されます。

 ……色々書いてみましたが、だいたいのことは GitHub に書いてあったりするんですよね。

  • BobbuBrowne

    Hello! Cool post, amazing!!!

Rails の CookieStore をデコード

いやー、意外と苦労しました。

第一稿は、オブジェクトが初期化出来なかったので破棄し、
第二稿は、初期化に必要な情報が直書きだったので書き直し、
これが第三稿です。

secret = ActiveSupport::CachingKeyGenerator.new Rails.application.key_generator
encryptor = ActiveSupport::MessageEncryptor.new secret.generate_key(Rails.application.config.action_dispatch.encrypted_cookie_salt),
                                                secret.generate_key(Rails.application.config.action_dispatch.encrypted_signed_cookie_salt),
                                                serializer: ActionDispatch::Cookies::NullSerializer
encryptor.decrypt_and_verify CGI.unescape(cookie_str)

 業務でクッキーストアの情報を知る必要が出てきたので、デコード方法を探したのですが…
 どれもうまくいきませんでした。

 情報が古かったのか、何なのか、原因は不明ですが、とにかく急ぎで対応する必要があったのでgem内を色々漁ってなんとかしてみました。

 ちなみに業務での Rails のバージョンは 4.1.8 です。

 クッキーストアの値はCookieのRails.application.config.session_options[:key]で取得できるキーにエンコードされて格納されており、こいつをデコードする必要があります。
 デコードは ActiveSupport::MessageEncryptor オブジェクトの decrypt_and_verify メソッドでできるのですが、初期化するためにはシリアライザークラスと、encrypted_cookie_salt と encrypted_signed_cookie_salt を元に作られた key が必要になるので、まずはキーを作りたいのですがそれには ActiveSupport::CachingKeyGenerator オブジェクトが必要で、これの初期化には ActiveSupport::KeyGenerator が必要です。
 さらに ActiveSupport::KeyGenerator の初期化には secret キーと iterations の値が必要ということがわかりました。

 整理すると、下記クラスの初期化が必要です。

  1. ActiveSupport::KeyGenerator
  2. ActiveSupport::CachingKeyGenerator
  3. ActiveSupport::MessageEncryptor

 初期化のためには下記の情報が必要です。

  1. secret キー
  2. iterations
  3. encrypted_cookie_salt
  4. encrypted_signed_cookie_salt
  5. シリアライザークラス

 情報が多くて面倒ですが、一つ一つ順を追って確認していきます

secret キー

 Rails.application.secrets[:secret_key_base] で取得可能であることが判明。
 (設定されている値は /config/secrets.yml の該当する環境の値)

iterations

 iterations の値がどうやっても見つからない。
 設定しないで ActiveSupport::KeyGenerator を初期化することも可能だったが、それだと iterations の値が 65536 に指定されてしまう。
 ”何故か” Rails デフォルトだと、iterations の値は1000になっているので、なんとかしてこの設定値を探しだそうと躍起になった結果…

 Rails.application.key_generator に初期化済みのオブジェクトを見つけましたっ!
 この初期化済みのオブジェクトを作成するときに 1000 の値を使っていて、クッキーストアの値をエンコードする時にはこのオブジェクトを使っているようです。

ActiveSupport::KeyGenerator オブジェクト

 上記記載の通り Rails.application.key_generator に初期化済みのオブジェクトが有るためこれをそのまま使います。
 …何のために secret キーとか調べたのか。

encrypted_cookie_salt

 デフォルト値だと encrypted cookie になっているようですが、これの設定はどこに有るんだろうと探したところ、…/lib/action_dispatch/railtie.rb (ver.4.1.8) に記述が有りました。
 ここには config.action_dispatch.encrypted_cookie_salt = ‘encrypted cookie’ とあったので Rails.application.config.action_dispatch.encrypted_cookie_salt で取れるみたいです。

encrypted_signed_cookie_salt

 encrypted_cookie_salt と同じ所に有りました。
 ちなみにデフォルト値は signed encrypted cookie です

シリアライザークラス

 これの指定はクッキーストアに値を格納するために使われている ActionDispatch::Cookies::EncryptedCookieJar クラスのコンストラクタに記載がありました。
 具体的には …/lib/action_dispatch/middleware/cookies.rb (ver.4.1.8) の 511 行目にあります。

        secret = key_generator.generate_key(@options[:encrypted_cookie_salt])
        sign_secret = key_generator.generate_key(@options[:encrypted_signed_cookie_salt])
        @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: NullSerializer)
      end

      def [](name)

 ココで ActionDispatch::Cookies::NullSerializer を直値で渡しているため、もうこれは直書きです。

ActiveSupport::MessageEncryptor

 ActiveSupport::MessageEncryptor に encrypted_cookie_salt と encrypted_signed_cookie_salt と シリアライザークラス を渡して初期化すればOKです。

最後に

 クッキーストアの値は HTML エンコードされているので CGI.unescape メソッド使ってデコードするのを忘れないようにしてください。
 僕は忘れて2時間ぐらいハマってました…

デバッグの際に便利な特異メソッド

ruby(と言うかrails)でコマンドラインつかってデバッグしている時、特定のオブジェクト変数の中身を書き換えたい時がある。
アクセサがあれば良いが、ない場合が殆どで困るケースが儘ある。

そこで、特異メソッドです。

class Hoge
  def initialize
    @piyo = "piyo"
  end
end

hoge = Hoge.new # hoge@piyo = "piyo"

def hoge.piyo=(new_piyo)
  @piyo = new_piyo
end

hoge.piyo = "foo" # hoge@piyo = "foo"

なんか、JavaScript のプロトタイプによるメソッドの宣言みたいだ、となんとなく思ったりもしましたがそういった物ではなく、特定のオブジェクトに対してメソッドを追加できるものです。
…やっぱりプロトタイプによるメソッドの宣言みたい。

コレを使えば一時的にオブジェクト変数を書き換えたくなってもコードを直接触ること無く書き換えられます。

便利、なのですが、コレをデバッグ以外で使えると考えるとすごく怖い。

デバッグ以外での使用は控えましょう。混乱の元です。
(10分程度考えましたが、これを使う正当な理由が見つけられませんでした)

最近絵を描こうとするたびに筆が止まる

最近、手癖以外で書くと、すぐに筆が止まってしまう。
怠惰すぎですわ20150303

[ruby] データから zip を直接作る

 先日仕事でエクスポートしたデータを zip ファイルにしてAmazon S3にアップロードするタスクがふられた。
 エクスポートと言ってもファイルに書き出すわけでは無いので、ファイルとして実態のないデータを zip ファイルにする必要があった。

 よく見かけるサンプルとしては

require 'rubygems'
require 'zip'

Zip::File.open("zipfile_name.zip", Zip::File::CREATE) do |zip_file|
  zip_file.add(File.basename("hoge/data.dat"), "hoge/data.dat")
end

 こんな感じで実ファイルをzipファイルに追加するコードですね

 今回は実ファイルが無いのでこの方法が取れません。
 一度データをファイルに書き出してやってもいいんですが、読み/書き時のトラブルなどが増え、実装コストが上がるので避けます。

 ちょいと調べてみたらgithubにget_output_streamメソッドを呼べばいいみたいなサンプルが書いてあったので実装してみた。

require 'rubygems'
require 'zip'

io = Zip::File.open("zipfile_name.zip", Zip::File::CREATE)
io.get_output_stream("hoge/data.dat") do |file|
  file.print "body"
end
io.close

こんなコードでうまくいきました。

EC サイト、Spree で単体テスト時に No route matches

先日、Rails で作られたECサイトフレームワークの Spree を拡張していたところ
単体テストで ActionController::UrlGenerationError: No route matches と出てしまった。

routing 自体はちゃんと設定されているんですが、どうにもうまく行かない。
色々調べて見たところ

writing Rails engine rspec controller tests

こんな記事を見つけたので

require 'rails_helper'
require 'spec_helper'

describe Spree::OrdersController do
  routes { Spree::Core::Engine.routes }
...
end

こんな感じにしてみたら動いた。
rspec からは、spree_core のルーティングが見えてなかったみたいだ。
なぜ見えてないのか等、追調査したいところです。

何とか描きあがった

C88 サークルカット
眠いのでねゆ

RSS / feedly
  • follow us in feedly
  • follow us in feedly
ソーシャル
広告