のえら

技術備忘とかメモとか.間違いはつっこんでいただきたい所存.

Rspecの呼び出し回数チェックテストで全件通し実行するとコケていた件

rspecでrake task系のユニットテストで、onceなどのメソッド呼び出し回数チェックを使用していた。
それらのユニットテストをそれ単体で実行すると成功するが、テストを通して全件実行すると失敗する。
検証したところ、spec/lib/tasks/xxx_spec.rb で以下のようにrake taskを呼び出すためにロードしているが、 Rake::Task[task_name].invoke がこのコードを読み込んだ回数分、指定のタスクを実行していた。

before(:all) do
  Rails.application.load_tasks
end

このため、同様のrake task系テストをまとめているディレクトリ配下のユニットテストを通しで実行すると、回数を検証するonceなどを使用しているitブロックで複数回rake叩いた挙動になってしまい、テストに失敗する。

例)
spec/aaa_task_spec.rb

before(:all) do
  Rails.application.load_tasks
end

context 'HogeClass#foo' do
  it { expect(HogeClass).to receive(:foo).once.with(kind_of(RuntimeError)) }
end

spec/bbb_task_spec.rb

before(:all) do
  Rails.application.load_tasks
end

context 'FugaClass#foo' do
  it { expect(FugaClass).to receive(:foo).once.with(kind_of(RuntimeError)) }
end

spec/ccc_task_spec.rb

before(:all) do
  Rails.application.load_tasks
end

context 'HogeClass#foo' do
  it { expect(HogeClass).to receive(:foo).once.with(kind_of(RuntimeError)) }
end

rspec spec/

  • > 1) HogeClass#foo

Failure/Error: expect(HogeClass).to receive(:foo).once.with(kind_of(RuntimeError))

(HogeClass (class)).foo(kind of RuntimeError)
expected: 1 time with arguments: (kind of RuntimeError)
received: 2 times
...

※load_tasksを呼び出す回数ずつ増える

解決策の例

1)各ファイルでのタスク読み込みをやめてspec_helperなどで一回だけ呼ぶようにする
2)タスク読み込みをまとめて行うのではなく、都度Rake.application.rake_requireする

rails_helperにて一回だけ呼ぶように変更

IntelliJ の git 操作で Could not execute editor

IntelliJ で git 操作するのに Terminal からコマンド打ってぽちぽちしようとしたらエディタを実行できないというエラーメッセージが表示されてコマンドが実行できない。

起きたこと

IntelliJ, VSC は git を使用。
Terminal で git 操作をしていて、PR のコミットを整理しようと以下のコマンドを実行。

git rebase -i HEAD~4

実行するとテキストエディタが起動して対象のコミットを選択する表示になるはず。
が、以下のメッセージが。

Could not execute editor

git がテキストエディタを起動できないらしい。。
ターミナルで git 操作していた時は vi が起動していたので IntelliJ 経由でも起動してくれると思っていた。

解決した方法

git が起動するエディタを設定する。
ターミナルを起動して以下実行。

# 使用しているvimのパスを確認
$ which vim
-> /usr/bin/vim

# git が起動するエディタを、上記で確認した vim のパスに設定
$ git config --global core.editor "/usr/bin/vim"

再度 IntelliJ の Termina から git rebase コマンド打ってみるとエディタが起動、解決。

git のデフォルトエディタの設定について

https://git-scm.com/book/ja/v1/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%81%AE%E8%A8%AD%E5%AE%9A

core.editor
コミットやタグのメッセージを編集するときに使うエディタは、ユーザーがデフォルトエディタとして設定したものとなります。デフォルトエディタが設定されていない場合は Vi エディタを使います。このデフォルト設定を別のものに変更するには core.editor を設定します。
$ git config --global core.editor emacs

Groovy の式展開(埋め込み文字列)の型は GStringImpl

ruby の "hello #{str}" のような感じで Groovy でも式展開が使える。

"hello ${str}"
  • 文字列をダブルクォートでくくる
  • 展開したい変数を ${} でくくる

で、この展開された文字列の型は String ではなく org.codehaus.groovy.runtime.GStringImpl クラスとなるため、List<String>のように String で定義しているものに突っ込もうとするとこける。

String として扱うなら #toString() で変換する。

Spockをちょっと触ったので覚え書き

初めてGroovySpockを触ったので色々と覚え書き。

動作確認は Spock Web Console を使用。
https://meetspock.appspot.com/

基本的な検証の例と出力結果

  • フィーチャーメソッドの名称は自由
  • フィーチャーブロックにはコメントも書ける(出力はされない)
  • setup(given) -> expect or when, then [-> expect or when, then] -> cleanup (whereは後述)
  • when と then はセットで使用する、一つのフィーチャーメソッド内に複数書ける
サンプルコード
import spock.lang.*

class SampleSpec extends Specification {
    def 'given, when, then'() {
        given:
        def amount = 1000
        when: '合計値に0.5をかけた場合'
        def actual = amount * 0.5
        then: '1000が返ること'
        actual == 1000
    }

    def 'only expect'() {
        expect:
        Math.max(10, 3) == 3
    }

    def 'setup, cleanup'() {
        setup:
        def numbers = [1, 3, 4, 0]

        expect:
        numbers.isEmpty()

        cleanup:
        numbers.clear()
    }
}
実行結果

- maximum of two numbers, 0 or 0
- given, when, then FAILED

Condition not satisfied:

actual == 1000
| |
500.0 false

at SampleSpec.given, when, then(Script1.groovy:22)

- only expect FAILED

Condition not satisfied:

Math.max(10, 3) == 3
| |
10 false

at SampleSpec.only expect(Script1.groovy:27)

- setup, cleanup

whereブロックの話

同じ内容のテストをテストする値を変更して実行したい場合に使える。

  • Unrollアノテーションはwhereブロックの実行結果を行ごとで出してくれる(whereブロックの変数が使用できる)
  • フィーチャーメソッドの引数は任意、それぞれの引数の型を明示的に指定することもできる
  • whereブロックはフィーチャーメソッドの中身をまるっと実行する
サンプルコード
import spock.lang.*

class SampleSpec extends Specification {
    @Unroll
    def "maximum of two numbers, #first or #second"(int first, int second, int expected) {
        expect:
        Math.max(first, second) == expected

        where:
        first | second || expected
        1 | 3 || 3
        7 | 4 || 4  // ここは失敗する
        0 | 0 || 0
    }
}
実行結果

SampleSpec
- maximum of two numbers, 1 or 3
- maximum of two numbers, 7 or 4 FAILED

Condition not satisfied:

Math.max(first, second) == expected
| | | | |
7 7 4 | 4
false

at SampleSpec.maximum of two numbers, #first or #second(Script1.groovy:7)

- maximum of two numbers, 0 or 0

whereブロックの話その2

  • 何度目かは #iterationCount で取得できる(フィーチャーメソッド名の中だけで使用可能?)
  • テーブル形式とパイプ形式がある、後者はイテレーションが使えるオブジェクトならなんでも呼び出せる、外部リソースを使うようなテストで使用する?
サンプルコード
import spock.lang.*

class SampleSpec extends Specification {
    @Unroll
    def "like a table(#a, #b)[#iterationCount]"() {
        expect:
        Math.max(a, b) == b
        where:
        a | b
        1 | 7
        2 | 8
        3 | 9
    }

    @Unroll
    def "pipe(#a, #b)[#iterationCount]"() {
        expect:
        Math.max(a, b) == b
        where:
        a << [123]
        b << [789]
    }
}
実行結果

SampleSpec
- like a table(1, 7)[0]
- like a table(2, 8)[1]
- like a table(3, 9)[2]
- pipe(1, 7)[0]
- pipe(2, 8)[1]
- pipe(3, 9)[2]

フィクスチャーメソッド

テスト全体とフィーチャーメソッドの前後に実行される。
RSpec でいう before(:each), before(:all), after(:each), after(:all) のようなもの。

サンプルコード
import spock.lang.*
 
class MyFirstSpec extends Specification {
	def setupSpec() {
		println('call setupSpec')
	}
 
	def setup() {
		println('call setup')
	}

	def cleanup() {
     		println('call cleanup')
   	}
 
   	def cleanupSpec() {
     		println('call cleanupSpec')
   	}
 
   	def 'test1'() {
     		println('call test1')
     		expect:
     		assert true
   	}

   	def 'test2'() {
   		println('call test2')
   		expect:
     		assert true
   	}

	def 'test3'() {
        		println('call test3')
        		expect:
        		assert true
        		where:
        		a | b
        		1 | 2
        		3 | 4
    	}
}
実行結果(外部出力)

call setupSpec
call setup
call test1
call cleanup
call setup
call test2
call cleanup
call setup
call test3
call cleanup
call setup
call test3
call cleanup
call cleanupSpec

rubocop で ruby の数字の桁区切りをアンダースコアでできることを知った話

Use underscores(_) as decimal mark and separate every 3 digits with them.

プロジェクトに導入している rubocop で上記のメッセージが出た。
ruby では10000のような大きな数字を使うとき、人の目で見てパッと何桁かを理解できるよう、任意の箇所に _ (アンダースコア) で区切ることができる、とのこと。
rubocop 的には3桁ごとにアンダースコアを入れなさいよ、と警告しているので 100_00 も 10_000 も許容する。
※桁区切り(カンマ区切り)なので後者の方が見やすいと思われる(よく日本語にはなじまないとか言われてるけど。。)

このアンダースコアはプログラム上無視される。
以下 rails console で検証。

[1] pry(main)> number = 10_000
=> 10000
[2] pry(main)> number == 10_000
=> true
[3] pry(main)> number == 10000
=> true
[4] pry(main)> 10_000 == 10000
=> true
[5] pry(main)> 10_000.class
=> Integer
[6] pry(main)> number.class
=> Integer

rubocop のデフォルトでは5桁以上で適用
https://github.com/bbatsov/rubocop/blob/master/config/default.yml#L1076-L1078

定義は Style/NumericLiterals
https://github.com/bbatsov/rubocop/blob/master/config/enabled.yml#L784-L789

ruby style guide では以下のように記述されている。
https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics

Add underscores to large numeric literals to improve their readability.


# bad - how many 0s are there?
num = 1000000

# good - much easier to parse for the human brain
num = 1_000_000


WebベースのDBクライアント「JackDB」を触ってみたのでメモ

JackDB TOPページ

f:id:noterr0001:20170716014406p:plain


Log in

ログイン画面。
Googleアカウント or githubアカウントでのログインも可能。
アカウントがない場合は「Create a new account.」からアカウント作成画面に遷移して作成する。

f:id:noterr0001:20170716014811p:plain


Sign up

アカウントの作成画面。

  • Email address
  • Full name
  • Company or organization
  • Choose a username
  • Choose a password

全て必須。
企業または組織単位でのアカウント(以下、組織アカウント)作成となり、ユーザ単位でアカウントを作ることはできない。
※この組織アカウントの中にユーザが存在するような感じ

組織アカウントでの無料試用は「14日間」で、ログイン後、組織アカウントを使っていると、画面の左上に「Trial Ends In XX Days」と残りの無料試用可能日数が表示される。
後述の個人アカウントに切り替えることで期限なしで使える。
なお、組織アカウントで14日超えてもプランの切り替え画面に遷移せず、継続できる場合がある(理由は不明)

f:id:noterr0001:20170716014838p:plain


データソース管理画面(組織アカウント)

ログインすると、データソースの管理画面に遷移する。ここがログイン後のトップページ。

組織アカウントでは「JackDB Demo」から、JackDBのデモが操作できる。後述。
DBはPostgreSQLで、サンプルデータに対してSELECTのみ可能。

データソースの追加や切り替えはこの画面から行う。
「Add a data source」をクリックするとデータソースの追加画面に遷移する。

f:id:noterr0001:20170716015057p:plain


データソース追加画面(組織アカウント)

データベースの種類を選んでデータソースを追加する。
組織アカウントでは以下から選択可能。

Herokuの場合は、Herokuにログインしていればデータソースの一覧が出るのでそこから選択。
その他のDBも、接続名称(わかりやすい適当な名前)・ホスト名・ポート・DB名・DBユーザ名・DBパスワードを入力して、登録。
※DBによって若干異なるので適宜変更

追加が完了すると、接続してDBに対してクエリ発行できるようになる。
画面操作については後述のデモ画面参照。

f:id:noterr0001:20170716015116p:plain


JackDBデモ初回(組織アカウント)

データソース管理画面からデモに遷移した画面。
初回のみ、ちょっとした説明が表示される。

f:id:noterr0001:20170716015134p:plain


JackDBデモ(組織アカウント)

サンプルクエリが実行された状態。
ざっと機能概要。

  • 画面左の「OPEN EDITORS」でクエリの管理ができる(最大8つ)
  • 新規エディタを追加するときは「NEW TAB」を押下。
  • タブごとに名前が付けられる(Executeボタン左のテキストボックス押下)
  • Executeボタン押下でクエリ実行、下に結果表示(カラムをクリップボードへコピーする、などの操作は不可)
  • 実行結果はExportボタン押下でファイル保存可能
  • 右上の雲の中に上矢印のボタンでインポートも可能(フォーマットはcsv, psv, tsvから選択)

f:id:noterr0001:20170716015150p:plain


データソース管理画面(個人アカウント)

組織アカウントでログインした後、画面右上の「地球アイコン+組織名」を押下すると、アカウントの切り替えポップアップが表示される。
Personalを選択すると、個人アカウントに切り替わる。
(不満っぽい顔してる)
切り替わると左上の残り試用日数の表示がなくなる。
Demoも使用不可。
組織アカウントとデータソースは共有しない。

f:id:noterr0001:20170716015204p:plain


データソース追加画面

選択できるデータソースが制限されている。

f:id:noterr0001:20170716015220p:plain

Google Cloud Bigquery で非同期処理

require 'google/cloud/bigquery'

bigquery_project = Google::Cloud::Bigquery.new(
  # Bigqueryの接続情報を指定
  project: 'test_project_id',
  keyfile: './service_account.json'
)

sample_query = <<-QUERY
  SELECT
    repository.*
  FROM
    `bigquery-public-data.samples.github_nested`
  ORDER BY repository.forks desc
  LIMIT 1000
QUERY

puts '###QUERY###'
puts sample_query

puts 'Running query'
query_job = bigquery_project.query_job(sample_query) # (1)

puts 'Waiting for query to complete'
query_job.wait_until_done! # (2)

puts 'Query results'
if !query_job.failed? # (3)
  puts query_job.query_results.first
else
  puts query_job.error
end


(1) 指定の project に対してクエリを投げてジョブを作成する

  • クエリを実行するジョブを Bigquery で作ってその情報を返す

ここで戻るのは Google::Cloud::Bigquery::QueryJob クラス

  • Bigquery 側では、ジョブの作成と実行・ジョブ処理に使用する一時テーブル(約24時間後に消える)の作成が行われる
  • レスポンスにクエリの問い合わせ結果は含まれない

(2) Bigquery 内で実行しているジョブに対してリクエストを投げて、状態を確認する

  • 2秒待機→リクエストを、クエリ実行ステータスが「完了(停止)」になるまで繰り返す(5回)
  • 終わるまで後続の処理は実行されない

(3) ジョブの実行結果にエラーが含まれるかチェックする

  • #done? は対象のジョブが停止していたら true を返すだけなので、成功/失敗は判断できない

エラーチェックしたい場合は #failed? を使う

  • エラーがある場合ジョブの error にエラー理由とメッセージが入っている


実行結果


$ bundle exec ruby async_query_sample.rb

###QUERY###
SELECT
repository.*
FROM
`bigquery-public-data.samples.github_nested`
ORDER BY repository.forks desc
LIMIT 1000
Running query
Waiting for query to complete
Query results:
{:url=>"https://github.com/octocat/Spoon-Knife", :has_downloads=>true, :created_at=>"2011/01/27 11:30:43 -0800", :has_issues=>true, :description=>"This repo is for demonstration purposes only. Comments and issues may or may not be responded to.", :forks=>6718, :fork=>false, :has_wiki=>false, :homepage=>"", :integrate_branch=>nil, :master_branch=>nil, :size=>284, :private=>false, :name=>"Spoon-Knife", :organization=>nil, :owner=>"octocat", :open_issues=>139, :watchers=>8025, :pushed_at=>"2011/10/31 07:34:52 -0700", :language=>nil}


(1)でクエリをぶん投げたあとは Bigquery 側で処理を進めているので、重いクエリを実行するときや集計をしている間にプログラム側で何かしたいとき、複数のクエリを投げて並列で集計させたいときなどに使える。