blog

紫陽花

Reactでテストを行う まとめ その1

Reactでテストを行う まとめ

Reactのテスト環境を構築する際に色々と調べた結果。

もうツールとか色々ありすぎてわけがわからん。

となったので。

もし自分と同じ境遇にいる人がいたら少しでも手助けになれば良いなと思い、自分が調べたことをまとめておく。

技術スタックは以下となった。

  • ES6
  • Babel
  • Browserify / Webpack
  • React
  • enzyme
  • Mocha
  • sinon
  • power-assert

ぱっと見、開発を支えてるもので8つもある。 テストでは4つのツールを上手く組み合わせて行う。

enzyme

enzyme 呼び方: エンザイム 意味: 酵素

2016年2月現在で、GithubStarが8000越え

Airbnbが作ったテスティングツール

Reactのドキュメントには 「Airbnbが作ったやつがあってこっちの方が良さげだよ」 的なことが書いてあって本家顔負けな感じ

Test Utilities

Github LINK

Document LINK

Reactのコンポーネントテストを良い感じにしてくれる。react-addon-test-utilsなどを使わずにテストできる

  • Shallow Rendering
  • Full Rendering
  • Static Rendering

Mocha

Mocha 呼び方: モカ

Mocha is a feature-rich JavaScript test framework running on Node.js and in the browser, making asynchronous testing simple and fun.

スティングフレームワーク

テスト実行を担う describe/it などのテストのベースとなる機能を提供してくれる

Github LINK Document LINK

RSpecとかに似てる気がする。 describeでテストの大枠を決め、it内でテストの具体的な内容を定義する。

describe('Test Title', () => {
  it('Equal', () => {
    const two = 3;
    assert(two === 3);
  });
}

mochaはアサーションライブラリを自由に選択できる。 chaiかpower-assertで悩んだがpower-assertを採用した 理由は、なるべくシンプルに使えて学習コストが高くないものにしたかった。 power-assertとchaiを比べてみるとchaiの方が文法が豊富いろいろな書き方でテストを書くことができ、柔軟性が高いと感じた。 power-assertはシンプルにassert()だけでいけるのと。テストが失敗した時の出力結果がわかりやすかった。

追記: どうやらpower-assertはNode.jsのAssert APIを拡張したものらしい。

power-assert

power-assert 呼び方: パワーアサート

アサーションライブラリ twadaさんが開発を行っている。

Power Assert in JavaScript. Provides descriptive assertion messages through standard assert interface. No API is the best API.

Github LINK

Chai

Chai 呼び方: チャイ

アサーションライブラリ power-assertと対照的に豊富なAPIがありテストを書くことができる。

Chai is a BDD / TDD assertion library for node and the browser that can be delightfully paired with any javascript testing framework.

Github LINK Document LINK

Rails Validationについて

データ管理はしっかりやりたい

DBにデータが保存される時に、そのデータが正しいかどうかを検証する仕組みをバリデーションという。

バリデーションのトリガー

バリデーションが走るメソッド群

  • create
  • create!
  • save
  • save!
  • update
  • update!

上記のメソッドはオプションでバリデーションをスキップすることも可能

save(validate: false)

バリデーションがスキップされるメソッド群

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • touch
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters

Validate

通常のバリデーションです。 modelに定義して利用します。

class Recipe < ActiveRecord::Base # これでtitleがnullだとデータのInsertが行われず、弾かれます validates :title, presence: true end

valid?invalid?が用意されておりboolean型の値が返ってきます。

Recipe.new(title: "レシピタイトル").valid?
#=> true

Recipe.new(title: null).valid?
#=> false

Recipe.new(title: "レシピタイトル").invalid?
#=> false

Recipe.new(title: null).invalid?
#=> true

カスタムValidate

単体のmodelで独自のvalidationを定義し、使いたい場合に利用する。

validate link_url_cannot_be_load

def link_url_cannot_be_load
  if @obj.url.include?(BASIC_DOMAIN)
    errors[:link_url] << "でURLは利用することができません。可能ドメインか確認してください"
  end
end 

カスタムValidator

複数のmodelで独自のvalidationを定義し、使いまわしたい場合に利用する。

もしくは単体のmodelでも複数の独自validationを定義し使いたい場合に利用する。

開発者にもよるが、自分は上記の理由と定義を決めている。ここは個人の好みにもよるのかもしれない・・・

そしてRailsのapp/以下にvalidators ディレクトリを作ってその中にカスタムvalidatorをまとめておく

カスタムバリデータはActiveModel::Validatorを拡張したクラス。

クラスにはvalidateメソッドが実装されている必要がある。

class Recipe << ActiveRecord::Base
  include ActiveModel::Validations
  validates_with ContentValidator
end

class ContentValidator < ActiveModel::Validator
  # 必ずvalidateメソッドを実装する
  # 引数にはレコードを1つ取り、それに対してのバリデーションを実行する
  def validate(record)
    if record.description.blank?
      record.errors[:description] << "が空です"
    end
  end
end
  • Validates
  • カスタムValidate
  • カスタムValidator

3つのバリデーションを使いこない堅牢なシステムを構築していきたい。

データまわりの定義は厳密に決めていくことが大切だと自分は考えています。

理由としては一度データが入ったデータベースは変更が難しくなってしまうからです。

アプリケーションレイヤーのvalidation制御はもちろん。

DB側での外部キー制約、ユニークキー制約、複数の一意制約、null制約などルールをしっかり決めておくことが大切。

新規開発においては、使用がコロコロ変わるのはしょっちゅうあるのである程度変更があることを前提に開発していくと良い。

ただ、リリース段階まで行く時にはきっちりと制約周りは定義することが守るべきルールとしてある。

Rails, Carrierwave, MiniMagick, Piet, 画像アップロード, 画像圧縮

Railsで画像をアップロードし、かつ画像圧縮も行いたい時の実装をメモで残す。

環境

Macbook

OSX 10.11.6

Rails 5.0.0

Ruby 2.3

Carrierwave

Piet

MiniMagick

Gem

実装に必要なGemをインストール

gem "carrierwave"
gem "piet"
gem "mini_magick"

bundle install --path vendor/bundle

Piet

Carrierwaveのextensionとして画像圧縮を行えるようにするGem

圧縮を行う類似のGemがいくつか存在するがPietを採用

トータルダウンロード数 比較

piet : 242,713
carrierwave-imageoptimizer : 97,947

jpegoptim

.jpeg画像の圧縮で使用するパッケージ

brew install jpegiotim

pngquant

.png画像の圧縮で使用するパッケージ

画像圧縮を行うPietではpngquantを用いて圧縮を行えるように他のGemを利用している。

png_quantizator

Pietのドキュメントに導入方法が記載

pngquantが圧縮効率が良いのでこちらを採用する。

圧縮効率の比較資料

brew install pngquant

アップローダーの作成

bundle exec rails g uploader image
#=> app/uploaders/image_uploader.rb

上記のコマンドを実行しアップローダーを作成します。

class ImageUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  include Piet::CarrierWaveExtension
  
  process quality: 80
  
  # Large size
  process resize_to_limit: [640, 640]
  process :custom_optimize
  
  # Midium size
  version :midium do
    process resize_to_limit: [300, 300]
    process :custom_optimize
  end
  
  # Small size
  version :small do
    process resize_to_limit: [200, 200]
    process :custom_optimize
  end
  
 
  
  def extension_white_list
    %w(jpg jpeg gif png)
  end
  
  def mimetype
    IO.popen(["file", "--brief", "--mime-type", path], in: :close, err: :close) { |io| io.read.chomp.sub(/image\//, "") }
  end
  
  
  def custom_optimize
    case mimetype
    when "png" then pngquant
    when "jpeg", "gif" then optimize(quality: 90)
  end
end

custom_optimize メソッド独自で定義し、画像の拡張子によって実行する圧縮を分岐させるような形に。

initialize以下のcarrierwave.rbにも定義

CarrierWave.configure do |config|
  # hogehoge
end
module CarrierWave
  module MiniMagick
    def quality(percentage)
      manipulate! do |img|
        img.quality(percentage.to_s)
        img = yield(img) if block_given?
        img
      end
    end
  end
end

流れで画像圧縮のGemを作ったので興味があればこちらも。

Rails CarrierWave アップロード画像を良い感じに圧縮するGem作った

リファクタリング:Rubyエディション 読んだ

リファクタリング:Rubyエディション」 読みました。

リファクタリング:Rubyエディション

リファクタリング:Rubyエディション

最近は「コードの質を高め、生産性の高いコードを書く」ことを1つの目標にしていて、綺麗なコードの書き方や設計周りの本を読んでいます。

リファクタリング:Rubyエディション」は前々から読んでみたいと思っていたのですが、1冊4800円もするので買うのに少しビビってました。(学生財布)

しかしいざ、買って読んでみるとなんでもっと早く買わなかったんだと。4800円をケチってた自分を叱りたくなりました。

内容はもう目から鱗で。今まで自分が思っていた、考えていたことをうまく言語化してくれていて本当に素晴らしかったです(特にカタログ周り)

メタプログラミングRubyも素晴らしい本でしたが、リファクタリング:Rubyも負けないぐらいいい本です。 Rubyエンジニアの人たちの必読書になると思います。

感想

内容は主に4つの構成

1.リファクタリング -最初のサンプル-

リファクタリングの重要性を理解するためにも最初に大きくサンプルコードが載ってあり非常にわかりやすかったです。 学び始めのとっかかりとしては最高。

1つ1つのリファクタリングの積み重ねでここまで綺麗な構成に変わるものだと、before afterで見せてくれます。

また、UMLでも構成を表現してくれるので処理のフローが理解しやすいです。 実務でも構成だったり、処理のフローを整理したい場合の模範にできるなと思いました。

2.リファクタリングの原則

リファクタリングとはこういうものだよ。という概念的な部分の説明

これらをわかりやすく説明しています。

リファクタリングを導入すると、強調点が変わる。事前設計をすることに変わりはないが、完璧な解を探そうとはしなくなる。まずまず妥当な解であれば良い。」

3.コードの臭い

コードの臭いは、リファクタリングをするべき箇所を臭いという表現で説明しています。 3があるおかげで4のカタログの内容理解がグッと高まります。(バイキルト的な)

4.リファクタリングのカタログ

リファクタリングのテクニックをカタログ形式で示してくれています。

カタログを読むことでテクニックが言語化されてまとめられてとても良かったです。

全て頭に入っているわけではないので、今後リファクタリングを進める中で全てマスターしていきたい。

一覧は覚えたテクニックリスト

メソッドの構成方法

  • メソッドの抽出
  • メソッドのインライン化
  • 一時変数のインライン化
  • 一時変数から問い合わせメソッドへ
  • 一時変数からチェインへ
  • 説明用変数の導入
  • 一時変数の分割
  • 引数への代入の除去
  • メソッドからメソッドオブジェクトへ
  • アルゴリズム変更
  • ループからコレクションクロージャメソッドへ
  • 名前付き引数の導入
  • 名前付き引数の除去
  • 使われていないデフォルト引数の除去
  • 動的メソッド定義

オブジェクト間でのメンバの移動

  • メソッドの移動
  • フィールドの移動
  • クラスの抽出
  • クラスのインライン化
  • 委譲の隠蔽
  • 横流しブローカーの除去

データの構成

  • 自己カプセル化フィールド
  • データ値からオブジェクトへ
  • 値から参照へ
  • 参照から値へ
  • 配列からオブジェクトへ
  • マジックナンバーからシンボル定数へ
  • タイプコードからポリモーフィズム
  • タイプコードからモジュールのextendへ
  • タイプコードからState/Strategyへ
  • 属性初期化の遅延実行
  • 属性初期化の先行実行

条件式の単純化

  • 条件文の分解
  • 条件分岐の組み替え
  • 条件式の統合
  • 重複する条件分岐の断片の統合
  • 制御フラグの除去
  • 条件分岐のネストからガード節へ
  • 条件分岐からポリモーフィズム

Rails ActiveRecordで値が変更する前後の値を取得する

ActiveRecordで値が変更する前後の値を取得することがあったのでメモ

Rails 2.1あたりからActiveRecordの変更前後の値を取得する機能が備わっている。

method 意味
changed? 変更されているかどうか
changed 変更されているattribute名の配列
changeds 変更されているattribute名と値のハッシュ。値は変更前後の値を配列で。
{attr}_changed? {attr}が変更されているかどうかの判定
{attr}_was {attr}の変更前の値。変更されていない場合は元の値
{attr}_change {attr}の変更前後の値の配列。変更されていなかったらnil
{attr}_will_change! {attr}を変更することを明示。

attrには属性名が入ります。nameの場合 name_wasで変更前の値を取得することができる

使用例

Railsのコールバック処理で複数の配列データを1つに統一したい場合に利用する

コールバック処理

  • before_save
  • before_update
  • after_update

etc...

before_update処理でmerge_tagメソッドを実行する

merge_tagメソッドでは既に登録されていた既存の値(tag)と、

変更が加えられた新規の値tag_wasを組み合わせる(merge)処理が行われ、

self.tagに代入される

before_update :merge_tag

def merge_tag
  # tag_wasには元々のtagの値が入り、
  # mergeの引数のtagは新たなtagの値になる
  self.tag = tag_was.merge(tag)
end

もっと簡単な例で動きを説明すると

tag = Tag.find(1)
#=> #<Tag:0x007fc35ee6fc98 id: 1, name: "ドラえもん", created_at: Wed, 11 Apr 2016 20:21:02 JST +09:00>
tag.name
#=> "ドラえもん"
tag.changed?
#=> false
tag.name = "のび太"
#=> "のび太"
tag.changed?
#=> true
tag.name_was
#=> "ドラえもん"

のように、tag.name_wasで変更前の値を取得することができています。

ActiveRecordのちょっとした機能ですが、応用的な処理を実装する時には便利に使えるかなと思います。

Singletonパターン Ruby

シングルトンパターンとは

  • 作成したクラスのインスタンスが唯一であることが保証される
  • 唯一であるがゆえに、生成した1つだけのインスタンスにアクセスすることができる。

実装

Rubyにはsingletonと呼ばれるモジュールが定義されているためこれをrequireし、 クラスにincludeすることでシングルトンクラスを定義することができる。

require 'singleton'

#  シングルトン
class SingletonObject# instanceメソッドが定義される
  include Singleton
  attr_accessor :counter
  
  def initialize
    @counter = 0
  end
end

obj1 = SingletonObject.instance
obj1.counter += 1
puts(obj1.counter)
# 1

obj2 = SingletonObject.instance
obj2.counter += 1
puts(obj2.counter)
# 2
# 前回の+1が引き継がれている

obj3 = SingletonObject.new
# private method `new` called for SingletonObject:Class
# newメソッドはプライベートメソッドとして定義される