下級エンジニアの綴

新しく発見したことを綴っていこうと思っています。夢はでっかく上級エンジニアになることです。

Rspecでインスタンスメソッドのテストに苦労したのでメモ

今回はRspecでメソッドのテストをしたかったのですが、メソッド内で定義しているメソッド先の処理のテストは記述したくなかったので、それを解決したメモになります。

rubyのコード

# 今回テストしたかったのはTest1のexecuteメソッドになります
Class Test1
  def self.execute(test_params)
    Test2.execute(test_params)
    Test3.new(test_params).execute(test_params) # ←こいつが一番の曲者でした
  end
end

Class Test2
  def self.execute(test_params)
    p test_params + 'test2'
  end
end

Class Test3
  def initialize(test_params)
    @test = test_params + 'test3'
  end

  def execute(test_params)
    p @test + test_params + 'test3'
  end
end

テストコード

require 'rails_helper'

RSpec.describe Test1 do
  describe 'テスト1'
    it 'interfaceのテスト'
      test = double('Test3:00000000')
      allow(Test2).to receive(:execute).and_return(true)
      allow(Test3).to receive(:new).and_return(test)
      allow(Test3.new("")).to receive(:execute).and_return(true)
      Test1.execute(anything)
    end
  end
end

Test3のテストコードですが、。最初は下記のように記述していました。

allow(Test3).to receive(:new)
allow(Test3.new("")).to receive(:execute)

↑だとexecuteを呼ぶ時にno method errorがでます。

Test3で定義しているexecuteはクラスメソッドではなくインスタンスメソッドなので、インスタンスにメソッドを定義しないとエラーが出てしましました。

なので、

test = double('Test3:00000000')

↑でインスタンスを定義して

allow(Test3).to receive(:new).and_return(test)

↑でnewした時の戻り値にインスタンスを返えすようにします。

allow(Test3.new("")).to receive(:execute)

↑でインスタンスにexecuteメソッドを定義することによって

Class Test1
  def self.execute(test_params)
    Test2.execute(test_params)
    Test3.new(test_params).execute(test_params) # ←こいつのテストが通るようになる
  end
end

Test3のテストが通るようになります。(解決するまでにめっちゃ時間がかかりました。

参考URL

qiita.com

docker-composeでRAILS_ENVを切り替えて実行する方法を調べたのでメモ

今回はrspecを実行したいけど、わざわざdockerのコンテナの中に入って実行するのはイケてないと思い、調べたそのメモになります。

docker-compose exec test bash
RAILS_ENV=test_docker rake db:migrate:reset
RAILS_ENV=test_docker rspec

↑のコマンドを叩きたくないので

docker-compose exec test RAILS_ENV=test_docker rake db:migrate:reset
docker-compose exec test RAILS_ENV=test_docker rspec
## rpc error: code = 2 desc = oci runtime error: exec failed: container_linux.go:262: starting container process caused "exec: \"RAILS_ENV=test_docker\": executable file not found in $PATH"

と叩くと↑のようなエラーが出て上手く動きませんでした。

なので、

docker-compose run -e RAILS_ENV=test_docker test rake db:migrate:reset
docker-compose run -e RAILS_ENV=test_docker test rspec

↑のようにrunする時に-eをつけると、envの指定が出来たので、上手く動きました。

ログインするというより、使い捨てのインスタンスを立てるって感じですね。

参考URL

blog.codeship.com

railsのhash#tryでちょっと躓いたのでメモ

railsでhashにtryをかけているとエラーが出たので、その時に調べた内容をメモ。(一度先輩に教えてもらった気がするのですが完全に忘れてましたorz

本題

この書き方だとhashにkeyが存在する時は問題無いのですが、

{ name: 'yantera' }.try(:fetch, :name)
# => "yantera"

hashにkeyが存在しない時はerrorになります

{}.try(:fetch, :name)
# KeyError: key not found: :name
# from /usr/local/bundle/gems/activesupport-4.1.8/lib/active_support/core_ext/object/try.rb:45:in `fetch'

解決するには以下の2パターンがありました。

  • 自分でdefault値を設定する
    • 同時に好きな値を定義できる
  • []メソッドを設定する
    • メリットはシンプルに書ける
{}.try(:fetch, :name, nil)
{}.try(:fetch, :name, 1)
{}.try(:fetch, :name, 'yantera')
# => nil
# => 1
# => "yantera"

{}.try(:[], :name)
# => nil

参考リンク

qiita.com

小ネタ

  • tryの中身はこのようになってます
    • aにはtryを呼ぶときの引数が配列で入って、bにはブロック文が入ります。
def try(*a, &b)
  if a.empty? && block_given?
    yield self
  else
    public_send(*a, &b) if respond_to?(a.first)
  end
end

要は下のように呼ぶと

{}.try(:fetch, :test, 1) do
  p 'test'
end
# a
# => [:fetch, :test, 1]
# b
# => #<Proc:0x007f5b9bf15f88@(pry):2>

が入ります。

  • if文を見るとaがemptyでかつブロック文が投げられている時、自分で作成したブロックぶんが呼ばれるという内容ですね。
{}.try() do
  p 'test'
end
# => 'test'

というように使えます。(用途はわからないですが)

続いてelseの方ですが

def try(*a, &b)
  if a.empty? && block_given?
    yield self
  else
    public_send(*a, &b) if respond_to?(a.first)
  end
end

self(Hash)にa.firstのメソッドがあれば実行されるので

{}.try(:fetch, :test, 1) do
  p 'test'
end
# a
# => [:fetch, :test, 1]

この場合fetchが呼ばれます。fetchメソッドははhashのkeyをとって実行するので、

{}.fetch(:test, 1)
# => 1
{}.fetch(:test)
# KeyError: key not found: :test

の形になり、hashの中にkeyが存在していると、そのkeyの値が返りますが、デフォルト値を定義していないとKeyErrorになります。

[]はhashからkeyを取るメソッドなので

{}.[] :test
# => nil

という風にかけます。

また、

hoge = {}
hoge.[] :test
# => nil

hoge.default = 1
hoge.[] :test
# => 1

hashにdefault値を設定できるみたいで、指定しなければnilが返りますが、指定すると好きな値に出来るみたいでした。

rubyの特異メソッドのprivateについて気になったから調べたのでメモ

特異メソッドでprivateなメソッドを呼ぶ方法

  • sendを使う
    • privateの特異メソッド、privateのインスタンスメソッドを関係なく呼べる
    • 最終手段な感じがするので、あまり推奨できないと思った
class A
  def self.hoge
    A.new.send(:fuga)
  end

  def self.aaa
    A.send(:bbb)
  end

  def self.bbb
    p 'bbb'
  end

  private

  def fuga
    p 'fuga'
  end

  private_class_method :bbb
end

A.hoge      # "fuga"
A.aaa       # "bbb"
A.new.fuga  # private method `fuga' called for #<A:0x007f3ac14fb2f0> (NoMethodError)
A.bbb       # private method `bbb' called for A:Class (NoMethodError)
  • private_class_methodを使う
    • 特異メソッドが少ない時はこちらを使用する
    • privateにしたいメソッドを定義した後にprivate_class_methodで定義する
class A
  def self.hogo
    fugo
  end

  def self.fugo
    p 'fugo'
  end

  private_class_method :fugo
end

A.hogo  # "fugo"
A.fugo  # private method `fugo' called for A:Class (NoMethodError)
  • class << selfを使う
    • 特異メソッドが多い時に使用する
    • 階層が1段多くなるがメソッドにclass << self~endまでに定義したメソッドが特異メソッドになる。
    • private以下に定義したメソッドは特異クラスのprivateメソッドになる
class A
  class << self
    def hoo
      foo
    end

    private

    def foo
      p 'foo'
    end
  end
end

A.hoo  # "foo"
A.foo  # private method `foo' called for A:Class (NoMethodError)

rubyの多次元hashで指定したkeyを削除する時、ActiveSupportのexceptを使うと便利だったのでメモ

require 'active_support'

h = { a: 1, b: 2, c: 3, d: { e: 4, f: { g: 5 }}}

p h.except(:a)      // {:b=>2, :c=>3, :d=>{:e=>4, :f=>{:g=>5}}
p h                 // {:a=>1, :b=>2, :c=>3, :d=>{:e=>4, :f=>{:g=>5}}}
p h[:d].except!(:f) // {:e=>4}
p h                 // {:a=>1, :b=>2, :c=>3, :d=>{:e=>4}}

!を付けると破壊的メソッドになります。
特定のkey以下のkeyを指定することも可能だったのでかなり汎用性が高いメソッドかなと思いました。

参考URL

qiita.com

go初心者が無名関数のクロージャについて学んだのでメモ

スターティングGo言語という本を読んでいるのですが、これは面白そうだなと思った機能があったのでメモ

package main
import "fmt"
func later() func(string) string { 
    // 1 つ 前 に 与え られ た 文字列 を 保存 する ため の 変数 
    var store string 
    // 引数 に 文字列 を とり 文字列 を 返す 関数 を 返す 
    return func( next string) string { 
        s := store store = next 
        return s 
    } 
} 
func main() {
    f := later() 
    fmt.Println(f("Golang")) // => "" 
    fmt.Println(f("is")) // => "Golang" 
    fmt.Println(f("awesome!")) // => "is"
}
a := 1
b := 2 // この変数だけクロージャから参照される
c := 3
return func() int{
    return b
}
package main

import "fmt"

func integers() func() int { 
    i := 0
    return func() int { 
        i += 1 
        return i 
    } 
} 

func main() { 
    ints := integers() 

    fmt.Println(ints()) // => "1" 
    fmt.Println(ints()) // => "2" 
    fmt.Println(ints()) // => "3" 

    otherInts := integers() 
    fmt.Println(otherInts()) // => "1" 

    fmt.Println(ints()) // => "4" 
    fmt.Println(ints()) // => "5" 

  // 初期化
    ints = integers() 

    fmt.Println(ints()) // => "1" 
    fmt.Println(ints()) // => "2" 
}

上手く使いこなせれると便利そうだなと思いました。

スターティングGo言語

スターティングGo言語

builderscon2017に行って来ました

buildersconとは

buildersconは「知らなかった、を聞く」をテーマとした技術を愛する全てのギーク達のお祭りです。buildersconではトークに関して技術的な制約はありません、特定のプログラミング言語や技術スタックによるくくりも設けません。 必要なのは技術者達に刺激を与えワクワクさせてくれるアイデアのみです。

あなたが実装したクレイジーなハックを見せて下さい。あなたの好きな言語のディープな知識をシェアしてください。あなたの直面した様々な問題と、それをどう解決したかを教えてください。未来技術のような未知の領域について教えてください。

buildersconに参加して、あなたの情熱をシェアしてください!皆様のご参加をお待ちしております!(下記より引用

builderscon.io f:id:yanterakun:20170806182044j:plain

2017/08/03〜2017/08/05で開催されていたので行った感想をまとめたいと思います。
(私が参加したのは4・5日なのでその感想になります。

4日

初めてのMySQLチューニング -データベースは怖くない!

builderscon.io

mysql5.5のチューニングをされたというお話でした。
my.cnfがデフォルトだったり、slaveがない状態で運用を引き継いだ時に学んだ教訓や対応方法を発表されていました。
自分も5.5, 5.6でslaveの作成やmaster昇格などを勉強しましたが、5.6からgtidがあるのでそこまで大変ではないのですが、5.5だとdbをロックしてdumpしてpositionを見てと結構な手数があるので改めてgtidを使える5.6はありがたいと思いました。
他にも、「運用から逃げるのは良くない」など自分にもかなり刺さり、印象深いお話でした。

ランチセッション

ここでは株式会社VOYAGE GROUPとMomentum株式会社の方々の話を聞きながらランチを食べました。
ランチに頂いたお弁当がめっちゃおいしかったです。(写真を撮るのを忘れていましたorz

横山三国志に「うむ」は何コマある?〜マンガ全文検索システムの構築

builderscon.io

趣味で全文検索をされているというお話でしたが、かなり正確な検索をされていたのでとても凄かったです。 最初は横山光輝三国志だけだったそうですが、コナンやドラゴンボールなど漫画のコマが一定の作品以外の作品でも検索できるようになっていました。 思わず自分もクソリプを送るためのコマ面白いコマを抜き出したいなと思いました。 トークもかなり軽快だったのですごく楽しかったです。 ベストトーク賞2位おめでとうございます

Goで実装する軽量マークアップ言語パーサー

builderscon.io

speakerdeck.com

はてな記法のパーサーをgoで実装されたというお話でした。どの言語にも対応できるようにと設計もされていたので凄いなと思いました。

RDBアンチパターン リファクタリング

builderscon.io speakerdeck.com

技術的負債がチーズなのか腐った牛乳なのかと発表されていてなるほど!上手い例えだなと思いました。
負債が自分たちの飯の種になっているというのもあるので、そこから逃げ出さずに返済しましょうというお話でした。
これには自分もかなり納得で且つ耳が痛いお話だなと思いました。
RDBということなので、データベースでのお話でしたが、データだけではなく、アプリケーションでも同じことが言えるので自分もそこから逃げず頑張ろうと思います。
ベストトーク賞1位おめでとうございます!

LT

基本的に皆さんトークを5分以内に終わらせるのすげーって思いました。
たくさんの人の前できっちり発表されていたので本当に上手で、時間を守るだけでなく内容も皆さん濃いので最後まで楽しかったです。
QRコードを16分割したやラズパイでGPSアプリ作ってみたや自分の作ったものを発表していく(デモ付き)等、全体的にめちゃくちゃ面白かったです。

5日

この日はちょっと遅刻しました(5分ほど
見たいセッションは観れたので個人的にはセーフです。

ここまで出来るmruby

builderscon.io

speakerdeck.com

実際にサービスでの実体験を元に発表されていました。なのでかなり勉強になりました。 そしてruby凄いって思いました。mrubyを使うと低レイヤー部分も処理をかけるようになるので、rubyistである自分は触ってみようと思いました。

Ionic 3+ではじめる次世代アプリ開発(HTMLでiPhoneアプリをつくろう!)

builderscon.io

html5と少しのjsの知識があれば簡単にweb/ios/androidで使用できるということだったので、目からウロコでした。

ランチセッション

2日目のお弁当もかなり美味いお弁当だったので腹ペコの自分には至福の時でした。(コーヒーも頂いたのですが、このコーヒーもかなり美味しかったです。
サイボウズ株式会社とSupership株式会社の方たちのセッションを聞きながらいただきました。 f:id:yanterakun:20170806182750j:plain

OSS の引き継ぎ方

builderscon.io

speakerdeck.com

ossへの貢献の仕方っていっぱいあるんだなと思いました。(レビューやプラグインの開発、又は他人からその機能を引き継ぐなど
自分はまだそういったことがしたことがないので。これを機会に動いていきたいと思いました。
残念ながら時間切れということもあって最後まで内容を発表出来ていなかったのですが、続きはasakusa.rbに参加してお話を聞かせていただたいと思いました。

WEB+DB PRESS 100号記念 特別企画

builderscon.io

2部構成で進んでいきました。

1部:WEB+DBの過去を振り返る(1〜100
  • 1〜24巻までの6年間はjavaがメインだった
  • 25〜48でjavaからスクリプト言語に移行していった。
  • 49〜72モバイルからスマホに移行githubもこの期間にでた
  • 73〜99ではネイティブアプリや仮装技術、コンテナ技術が流行しだした
  • 100で人工知能

など技術のトレンドとともに振り返って楽しかったです。

2部:編集部に対しての質問
  • 編集部の技術のキャッチアップ方法

    • ライターから教えてもらう
  • ライターの探し方

    • 別のライターからの紹介
    • SNSからのキャッチする
  • WEB+DBとは

    • webアプリケーション開発に関連するものが中心に存在する
  • 応募方法

    • 編集部に直接メールを送る
    • ライターさんに紹介してもらう

などのWEB+DBに対しての質問時間でした。 WEB+DBについて色々知ることが出来てよかったです。

汎用CMSから新規開発の自社サービスへ移行した事例のご紹介

builderscon.io

実際の業務で行った事例を元に発表されていました。
自分も新旧のサービス移行が発生した場合のアンチパターンを踏み抜かないように気をつけようと思いました。

所感

参加して良かったなと思いました。
開発するモチベーションが上がったという事と、他社さんの事例を多くインプットさせて頂いたのでかなりモチベーションが高くなりました。
次回も参加出来たら良いなと思います。
登壇者さん、運営の皆さんありがとうございました。