ためすう

Rails5 で DatabaseCleaner を使う

2019-06-02

やったこと

Rails5 の Rspec で、DatabaseCleaner を導入してみます。

テスト用のデータベースとして MySQL を利用しているのですが、ロールバックしても auto_increment のカラムは数字が増え続けてしまうので、その対応をするため、DatabaseCleaner を使ってみます。

確認環境

$ ruby --version
ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep rspec-rails
rspec-rails (3.8.2)

調査

Gemfile に記載

Gemfile

group :development, :test do
  ...
  gem 'database_cleaner'
end
$ bundle install

設定

spec/rails_helper.rb

require 'database_cleaner'

RSpec.configure do |config|
...
  config.before(:suite) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
end

rspec で実行する (例)

spec/models/task_spec.rb

require 'rails_helper'

RSpec.describe Task, type: :model do
  context 'validate task' do
    scenario 'is valid with task' do
      user = create(:user)
      p user.id
      task = build(
        :task,
        user_id: user.id
      )
      expect(task).to be_valid
    end
  end
end

下記テストを何回実行しても id が 1 であることを確認できます。

$ bundle exec rspec spec/models/task_spec.rb -fd

Task
  validate task
1
    is valid with task

Finished in 0.18589 seconds (files took 1.26 seconds to load)
1 example, 0 failures

test.log を見てみる

$ less -qR log/test.log
...
   (0.8ms)  SET FOREIGN_KEY_CHECKS = 0
   (1.5ms)  select table_name from information_schema.views where table_schema = 'xxxx_test'
   (18.6ms)  TRUNCATE TABLE `ar_internal_metadata`;
   (26.6ms)  TRUNCATE TABLE `tasks`;
   (13.0ms)  TRUNCATE TABLE `users`;
   (0.8ms)  SET FOREIGN_KEY_CHECKS = 1

truncate コマンドが実行されていることが確認できます。

※ 最初、DatabaseRewinder で試したのですが、うまく行かなかったので諦めました。

参考

Rails5 で struct を json に変換する

2019-06-02

やったこと

struct クラスを json に変換する方法を調べました。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

調査

Rails の to_json は、ActiveSupport で上書きされてます。

速度面の要件次第では別の方法を考えた方が良いかもしれません。

app/controllers/tasks_controller.rb

class TasksController < ApplicationController
  def show
    point = Struct.new(:x, :y)
    tmp = point.new(111, 222)

    render :json => tmp.to_json
  end
end

出力結果

$ curl http://localhost:3000/tasks/1
{"x":111,"y":222}

参考

Rails5 で factory_bot をインストールする

2019-06-02

やったこと

Rails5 で factory_bot をインストールします。

rspec はインストール済みです。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep rspec-rails
rspec-rails (3.8.2)

調査

Gem のインストール

Gemfile

group :development, :test do
  ...
  gem 'factory_bot_rails'
end
$ bundle install

Factory Bot の設定

spec/rails_helper.rb

RSpec.configure do |config|
  ...
  config.include FactoryBot::Syntax::Methods
end

データ定義

spec/factories/tasks.rb

FactoryBot.define do
  factory :task do
    sequence(:name) { |n| "NAME#{n}" }
  end
end

テスト記述

spec/models/task3_spec.rb

require 'rails_helper'

RSpec.describe Task, type: :model do
  before do
    @task = build(:task)
  end

  it 'テスト1' do
    p @task.name
  end

  it 'テスト2' do
    p @task.name
  end

end

テスト実行

$ bundle exec rspec spec/models/task3_spec.rb -f d

Task
"NAME1"
  テスト1
"NAME2"
  テスト2

Finished in 0.00557 seconds (files took 0.86627 seconds to load)
2 examples, 0 failures

参考

Rails5 で rspec の expect を使ってみる

2019-06-02

やったこと

Rails5 で rspec の expect を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep rspec-rails
rspec-rails (3.8.2)

調査

spec/models/task2_spec.rb

require 'rails_helper'

RSpec.describe Task, type: :model do
  it '計算1 should' do
    (1 + 2).should eq 3
  end

  it '計算2 expect' do
    expect(3 + 4).to eq 7
  end
end

出力結果

$ bundle exec rspec spec/models/task_spec.rb -f d

Task
  計算1 should
  計算2 expect

Deprecation Warnings:

Using `should` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax is deprecated. Use the new `:expect` syntax or explicitly enable `:should` with `config.expect_with(:rspec) { |c| c.syntax = :should }` instead. Called from /Users/xxxx/sample2/spec/models/task2_spec.rb:5:in `block (2 levels) in <top (required)>'.


If you need more of the backtrace for any of these deprecations to
identify where to make the necessary changes, you can configure
`config.raise_errors_for_deprecations!`, and it will turn the
deprecation warnings into errors, giving you the full backtrace.

1 deprecation warning total

Finished in 0.00259 seconds (files took 0.84059 seconds to load)
2 examples, 0 failures

should は非推奨になったので、警告が出ています。

should で発生する理由はこちらにあるようです(まだ理解していない)

参考

How to Win a Data Science Competition (Week2-3 part4)

2019-05-29

Validation

学習目標

  • Describe validation process and its purpose
  • Compare validation strategies
  • Identify train/test split in a competition
  • Identify and analyze validation problems

Problems occurring during validation

バリデーション段階の問題

  • データがあまりにも少ないとき
  • データが多様すぎて、不整合があるとき
  • KFold は大抵多くても5で充分

課題の提出段階の問題

  • リーダーボードのスコアがバリデーションスコアより一貫して、高い or 低い
  • リーダーボードのスコアがバリデーションスコアと相関がない

発生する原因

  • Kfold でかなり異なるスコアかもしれない
  • 公開リーダーボードのデータが少なすぎる場合
  • 訓練データとテストデータの分布が異なっているかもしれない

まとめ

  • 公開テストデータが少な過ぎる場合、バリデーションを信頼する(オーバフィットしてないことも確認)
  • 訓練データとテストデータの分け方
  • 訓練データとテストデータの分布

参考

How to Win a Data Science Competition (Week2-4 part1)

2019-05-28

Data Leakages

学習目標

  • Embrace the concept of data leakage
  • Find and exploit typical data leakages
  • Probe public leaderboard

Basic data leaks

リークとは

非現実的に良い予測をする予期しないデータ内の情報

リークが発生するのは、エラー、事故の結果である

データリークの種類

  • 時系列データ

    • 訓練データ、公開テストデータ、非公開テストデータで、時間順になってないものがあるなら、リークを見つけたことになる
  • 期待していない情報

    • メタ情報、ファイル作成日、画像の解像度 などから見つける (はるかに困難)
    • ID の情報
    • 行番号

参考

How to Win a Data Science Competition (Week2-3 part3)

2019-05-28

Validation

学習目標

  • Describe validation process and its purpose
  • Compare validation strategies
  • Identify train/test split in a competition
  • Identify and analyze validation problems

Data splitting strategies

バリデーションの種類

バリデーションデータを作成するのに下記3通りがある。

  • ランダムに分ける
  • 時系列で分ける
  • idごとに分ける
  • 組み合わせ

あるモデルで、有効な特徴も他のモデルでは役に立たないことがある

異なる分割戦略

  • 生成された特徴では
  • モデルはその生成された特徴に依存する
  • ある種のリークがある

バリデーションの時に、コンペの主催者によって、作成された訓練データ、テストデータを再現するのが大切

参考

How to Win a Data Science Competition (Week2-3 part2)

2019-05-28

Validation

学習目標

  • Describe validation process and its purpose
  • Compare validation strategies
  • Identify train/test split in a competition
  • Identify and analyze validation problems

Data splitting strategies

バリデーションの種類

  • holdout
  • K-fold
  • Leave-one-out

holdout

ngroups = 1

sklearn.model_selection.ShuffleSplit

データを下記に分ける

  • 訓練データ
  • テストデータ

  • 十分なデータがあり、異なる分割に対して、同様のスコア、最適なモデルのパラメータを取得する時に使う

K-fold

ngroups = k

sklearn.model_selection.Kfold

データを k 回、訓練データ、テストデータに分ける

そして、平均値を出す

  • 十分なデータがあり、異なる分割に対して、スコア、最適なモデルのパラメータが異なる時に使う

Leave-one-out

ngroups = len(train)

sklearn.model_selection.LeaveOneOut
  • K-Fold の K とサンプル数が一致する場合である
  • データが少ない場合に使う

階層化

  • シャッフルデータには、holdout、K-Fold を使う
  • データが少ない場合、ランダムに分割する
  • 偏りのあるデータに役立つ
  • 他クラス分類に役立つ

参考

Rails5 で rspec の before, after, around を使ってみる

2019-05-27

やったこと

Rails5 で rspec の before, after, around を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep rspec-rails
rspec-rails (3.8.2)

調査

spec/models/task_spec.rb

require 'rails_helper'

RSpec.describe Task, type: :model do

  around(:example) do |example|
    puts 'around example before'
    example.run
    puts 'around example after'
  end

  before(:context) do
    puts 'before context'
  end

  after(:context) do
    puts 'after context'
  end

  it 'gets return' do
    puts 'in the example'
  end
end

出力結果

$ bundle exec rspec spec/models/task_spec.rb -f d

Task
before context
around example before
in the example
around example after
  gets return
after context

Finished in 0.00188 seconds (files took 0.85554 seconds to load)
1 example, 0 failures

参考

Rails5 で時間に関わる rspec のテストを書く

2019-05-27

やったこと

Rails5 で時間に関わるテストを rspec で書いてみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

$ gem list | grep rspec-rails
rspec-rails (3.8.2)

調査

$ rails g rspec:model Task

spec/rails_helper.rb

RSpec.configure do |config|
  ...
  config.include ActiveSupport::Testing::TimeHelpers
  ...
end

spec/models/task_spec.rb

require 'rails_helper'

RSpec.describe Task, type: :model do
  it 'is whether now time' do
    travel_to('2019-05-26 23:00'.to_time) do
      expect(Time.zone.now).to eq(Time.new(2019, 5, 26, 23, 0, 0))
    end
  end
end

参考