# 全ての要素が整数の配列を引数にとり、その要素の合計を要素数で割った平均を整数で返すdefnaive_average(array)
array.sum / array.length
end
これに対応するプロパティベーステストは、
require'rspec'RSpec.describe "#naive_average"doG = PropCheck::Generators
it "returns an integer for any input"doPropCheck.forall(G.array(G.integer)) do |numbers|
result = naive_average(numbers)
expect(result).to be_a(Integer)
endendend
$ bundle exec rspec naive_average_spec.rb
#naive_average
returns an integer for any input (FAILED - 1)
Failures:
1) #naive_average returns an integer for any input
Failure/Error: result = naive_average(numbers)
ZeroDivisionError:
(after 71 successful property test runs)
Failed on:
`[]`
Exception message:
---
divided by 0
---
(shrinking impossible)
# ./naive_average.rb:2:in `/'
# ./naive_average.rb:2:in `naive_average'
# ./naive_average_spec.rb:10:in `block (3 levels) in <top (required)>'
# ./naive_average_spec.rb:9:in `block (2 levels) in <top (required)>'
Finished in 0.01036 seconds (files took 0.10241 seconds to load)
1 example, 1 failure
Failed examples:
rspec ./naive_average_spec.rb:8 # #naive_average returns an integer for any input
71 回プロパティベーステストが成功した ( after 71 successful property test runs ) あとに、 [] を渡したとき ( Failed on [] ) に ZeroDivisionError で失敗したようですね。ひとまず 70 回テストするのに 0.01 秒で実施できるなら、100 回以上のテスト生成でも実用に耐えそうです。
$ bundle exec irb -I .
irb(main):001> require 'naive_average'
=> true
irb(main):002> naive_average([])
/Users/niku/src/property_based_testing_with_ruby_sample/naive_average.rb:2:in `/': divided by 0 (ZeroDivisionError)
from /Users/niku/src/property_based_testing_with_ruby_sample/naive_average.rb:2:in `naive_average'
from (irb):2:in `<main>'
from <internal:kernel>:187:in `loop'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/lib/ruby/gems/3.3.0+0/gems/irb-1.9.0/exe/irb:9:in `<top (required)>'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/bin/irb:25:in `load'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/bin/irb:25:in `<top (required)>'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/lib/ruby/3.3.0+0/bundler/cli/exec.rb:58:in `load'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/lib/ruby/3.3.0+0/bundler/cli/exec.rb:58:in `kernel_load'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/lib/ruby/3.3.0+0/bundler/cli/exec.rb:23:in `run'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/lib/ruby/3.3.0+0/bundler/cli.rb:492:in `exec'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/lib/ruby/3.3.0+0/bundler/vendor/thor/lib/thor/command.rb:28:in `run'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/lib/ruby/3.3.0+0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/lib/ruby/3.3.0+0/bundler/vendor/thor/lib/thor.rb:527:in `dispatch'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/lib/ruby/3.3.0+0/bundler/cli.rb:34:in `dispatch'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/lib/ruby/3.3.0+0/bundler/vendor/thor/lib/thor/base.rb:584:in `start'
from /Users/niku/.asdf/installs/ruby/3.3.0-preview3/lib/ruby/3.3.0+0/bundler/cli.rb:28:in `start'
... 5 levels...
require'naive_average'require'prop_check'require'rspec'RSpec.describe "#naive_average"doG = PropCheck::Generators
it "returns an integer for any input except empty array"doPropCheck.forall(G.array(G.integer, empty: false)) do |numbers|
result = naive_average(numbers)
expect(result).to be_a(Integer)
endendend
実行しましょう。
$ bundle exec rspec naive_average_spec.rb
#naive_average
returns an integer for any input except empty array
Finished in 0.017 seconds (files took 0.10255 seconds to load)
1 example, 0 failures
The unit test has limitations, namely that each input must be added to the test by the developer. One benefit of fuzzing is that it comes up with inputs for your code, and may identify edge cases that the test cases you came up with didn’t reach.
# see: https://github.com/danmayer/coverband/blob/v5.2.5/lib/coverband/configuration.rb#L116-L129# The adjustments here either protect the redis or service from being overloaded# the tradeoff being the delay in when reporting data is available# if running your own redis increasing this number reduces load on the redis CPUdefbackground_reporting_sleep_seconds@background_reporting_sleep_seconds ||= if service?
# default to 10m for serviceCoverband.configuration.coverband_env == "production" ? 600 : 60elsif store.is_a?(Coverband::Adapters::HashRedisStore)
# Default to 5 minutes if using the hash redis store300else60endend
毎年稲を刈る度に新しいライブラリが現れるフロントエンド界隈ですが、最近の DIGGLE のフロントエンドでは従来 ContextAPI で行なっていた React の Global State 管理を見直し、移行先として有力な Recoil と Jotai を比較した上で Jotai を導入することに決定しました。
導入決定の経緯と導入する際の工夫について今回はお話しさせていただきます。
React の Global State 管理に関して
React ではアプリケーションの規模が大きくなるとしばしば Global State を導入することになると思います。
導入理由は、コンポーネントを細分化していくにあたってstate/props のバケツリレーの階層が深くなり可読性が落ちていくためなど様々だと思われますが、
DIGGLE でも例に漏れず可読性向上を目的として Global State を導入して管理を行ってきました。
また、Jotai では大きなオブジェクトを扱う際には focusAtom や splitAtom で分割するなどできます。
Recoil は atom の中に atom が入らないため、オブジェクトを細かく分割した atom を用意していたのですが、Jotai ではそれらをまとめて必要な単位で分割して切り出す形に変更しました。
DIGGLEでは Jotai / ContextAPI の組み合わせを利用することに決定しました。
Jotai はシンプルでわかりやすく可読性とパフォーマンスを両立しながら今後の開発を行なっていけそうです。
基本的には Jotai を用いて Global State 管理を行い、state を使わずに変数のスコープを切って再利用したいものに関しては ContextAPI を利用していこうと思います。
今回は現時点での DIGGLE の環境において最善と思われる Global State 管理を検討したものになっており、結論は時期/環境によって大きく左右されると思います。
そのため Jotai は素晴らしいライブラリとは思うものの今回の決定に囚われることなく、その時々の状況に応じて適宜方針を検討し直していきたいと考えています。
今回は DIGGLE において Global State 管理を Jotai に決めた経緯を紹介させていただきました。
本記事が皆様の検討の際の一助になれば幸いです。
usestd::time;
usestd::sync::Arc;
usedatafusion::prelude::*;
usedatafusion::arrow::record_batch::RecordBatch;
#[tokio::main]
async fnmain() ->datafusion::error::Result<()> {
let now =time::Instant::now();
let ctx =SessionContext::new();
let path ="test.parquet";
// 集計用クエリの定義: SQL形式で記述可能let opts =ParquetReadOptions::default();
ctx.register_parquet("facts", &path, opts).await?;
let df = ctx.sql("SELECT date, account_id, unit_id, sum(value) FROM facts GROUP BY date, account_id, unit_id").await?;
// collect() の実行で実際にクエリがexecuteされるlet results: Vec<RecordBatch>= df.clone().collect().await?;
let time = now.elapsed();
println!("time: {:?}, result size: {}", time, df.count().await?);
Ok(())
}
usepolars::prelude::*;
usestd::time;
#[tokio::main]
async fnmain() {
let now =time::Instant::now();
let df =LazyFrame::scan_parquet("test.parquet", Default::default()).unwrap();
let df = df.groupby(["date", "account_id", "unit_id"])
.agg([col("value").sum()])
.collect()
.unwrap();
let time = now.elapsed();
println!("time: {:?}, result size: {}", time, df);
}