単項マイナスと構文解析
単項マイナスとは 単項マイナスと括弧 括弧なし単項マイナスを許容する場合のBNF calcの場合
minitestについて連続して2回書いてきました。 「minitestはドキュメントが少ない」という人がいますが、私も同感です。 例えば、モックとスタブの説明も少ないです。 そこで、今回はmock.rbのソースコードを参考に、モックの私的ドキュメントを書いてみました。 あくまで私個人の考えであり、minitest作成者の意図とは何の関係もありませんので、あらかじめご了解ください。
デリゲータ(delegator)は「委任者、委任する人」ということなので「モックに処理を委任するオブジェクト」という意味ではないかと思います。
デリゲータはモックを生成するときに、new
コマンドの引数として与えます。
require 'minitest/mock'
# delegator
m = Minitest::Mock.new("Hello world")
# m is a mock
p m #=> <Minitest::Mock:0x00007f809dedab50 @delegator="Hello world", @expected_calls={}, @actual_calls={}>
# Because m (mock) uses the delegator's method, m.display is the same as "Hello.world".display
m.display #=> Hello world
print "\n"
print m+"\n" #=> Hello world\n
# Because m has its own to_s method, m.to_s is NOT "Hello world".to_s
print m, "\n" #=> <Minitest::Mock:0x00007f8130db2c08>
m.expect(:size, 1000)
print m.size, "\n" #=> 1000, the size method is defined by m.expect.
print m.length, "\n" #=> 11, which is the real length of "Hello world"
p m #=> <Minitest::Mock:0x00007f264bd96e30 @delegator="Hello world",
# @expected_calls={:size=>[{:retval=>1000, :args=>[], :kwargs=>{}}]},
# @actual_calls={:size=>[{:retval=>1000, :args=>[], :kwargs=>{}}]}>
p m.verify #=> true
# m.size #=> Error: No more expects available for :size
minitest/mock
を取り込んでおきます。
mには文字列オブジェクト”Hello world”をデリゲータとするモックを代入します。
p m
でモックのインスタンス変数@delegatorに”Hello world”がセットされていることがわかるm.display
は"Hello world".display
を実行し、標準出力にHello worldが出力される+
メソッドも持っていないので、m+"\n"
はデリゲータの+メソッド("Hello world"+"\n"
)を実行するto_s
メソッドを自身のメソッドとして持っているので、m.to_s
はデリゲータを使わず、自身を文字列化するここまでで、要するにモックはデリゲータのほとんどのメソッドを引き継いでいることが分かると思います。
モックがexpectメソッドで「みせかけのメソッド」を定義するとき、そのメソッドがデリゲータのメソッドと同一名であれば、expectの定義を優先します。 後半を見ていきましょう
m.size
は1000を返す。
mはデリゲータのsizeメソッド(こちらは文字数の11になる)は使わず、expectの定義を優先したm.length
ではモック自身はlengthメソッドを持たないので、デリゲータの”Hello world”.lengthを実行し、11を返すp m
でモックの内容を表示すると、@expected_callsと@actual_callsの配列要素に、expectでの定義とm.size
の実行それぞれの返り値と引数が記録されているm.verify
でモックにおけるexpectされたメソッドが実行されたのでtrueが返された以上の機能からするとデリゲータとモックはどういう関係なのでしょうか?
モックはデリゲータをラップする。 デリゲータのメソッドのうち、テストで用いたいメソッドだけexpectでセットし、それ以外はそのまま実行させる
つまり、モックに置き換えたい元のオブジェクトがデリゲータだと考えられます。
expectでは3番めの引数が、定義するメソッドのパラメータです。
モック.expect(メソッド名, 返り値, パラメータの配列)
expectで定義されたメソッドはそのメソッドの呼び出し時にチェックされます。 チェックするのは
===
または==
が成り立つかどうか)。
例えばexpectでStringのパラメータを定義し、呼び出し時に”abc”が引数であれば、String==="abc"
はtrueになる。
===
はClassクラスで定義されていて、引数がそのクラスのインスタンスまたはサブクラスのインスタンスならばtrueになる。
StringクラスはClassクラスのインスタンスなので===
が定義されている。
なお、"abc"===String
はfalseになる。
文字列クラスのインスタンスメソッドとして===
が再定義されているためで、文字列インスタンスの===
と==
は同じ# arguments
m.expect(:concat, "Hello, folks.", [String, Integer])
print m.concat("Foo", 100), "\n" #=> Hello, folks.
m.expect(:concat, "Hello, there.", [Integer])
print m.concat("abc"), "\n" #=> Error :concat called with unexpected arguments
m.expect(:concat, "Hello, there.", [String, String])
print m.concat("abc"), "\n" #=> Error :concat expects 2 arguments
m.concat("Foo", 100)は引数の数、タイプとも定義に合っているので、返り値
“Hello, folks.”`が返されるm.concat("abc")
は引数の数は1つで良いが、タイプがIntegerではないのでエラーになるm.concat("abc")
は引数の数が2つで定義と異なるのでエラーになる以上のように、呼び出し時の引数が定義と異なるとエラーになります。
一般にメソッド呼び出しの最後の引数のハッシュは{}
を省略できることになっています。
expectでも同様に最後のパラメータにハッシュをつけ足すことができます。
モック.expect(メソッド名, 返り値, パラメータの配列, ハッシュ)
呼び出し時にハッシュの部分が同一でなければエラーになります。
m.expect(:concat, "Hello, folks.", [String], a:10,b:20,c:30)
print m.concat("abc", a:10, b:20, c:30), "\n" #=> Hello, folks.
m.expect(:concat, "Hello, folks.", ["efg"], a:10,b:20,c:30)
print m.concat("efg", a:10, b:20, c:30), "\n" #=> Hello, folks.
{a:10,b:20,c:30}
が続くよう定義m.concat("abc", a:10, b:20, c:30)
では定義通り文字列と(定義と同一の)ハッシュを引数としているので実行され、”Hello, folks.”が返される{a:10,b:20,c:30}
が続くよう定義m.concat("efg", a:10, b:20, c:30)
では定義と同一の文字列、ハッシュを引数としているので実行され、”Hello, folks.”が返されるモックは、予定された引数でメソッドが呼ばれるかどうかのチェックが結構厳しいです。 テストですから当然ですが。
expectメソッドにブロックを付けることができます。 そのときは第3、4引数(引数とハッシュ)はつけません。
モック.expect(メソッド名, 返り値){|x,y,...| x=10 && y,is_a?(String) && ....}
ブロックのパラメータにはメソッド呼び出し時の引数が代入されます。 ブロックでそのメソッドチェックをします。 メソッド呼び出し時のブロックのチェックもできます。
m.expect(:concat,"Hello, there.") {|x,y| x.is_a?(String) && y.is_a?(Integer)}
print m.concat("a", 1), "\n" #=> Hello, there.
m.expect(:concat,"Hello, there.") {|x,y,&z| x.is_a?(String) && y.is_a?(Integer) && z.call(10)==100}
print m.concat("a", 1){|x| x*x}, "\n" #=> Hello, there.
p m.verify #=> true
m.concat("a", 1){|x| x*x}
では、文字列、整数の引数、ブロックはパラメータを2乗(したがって10を100にして返す)なので定義の条件が満たされ”Hello, there.”が返されるテストで確認したいことは、対象のプログラムが期待通りにメソッドを呼び出しているかどうかです。 上記の例は極めて簡単なので、引数のタイプの確認の重要性があまり感じられません。 しかし、実際のプログラムでは、引数がいくつかの計算を経て得られることも考えられ、期待通りのオブジェクトかのチェックが重要になるかもしれません。
モックのverifyメソッドは、expectで設定されたメソッドがきちんと呼び出されたかを見ます。
以上、モックのソースコードを見て、モックの働きの詳細を紹介しました。
残念ながらminitestの詳しい解説がなかなか見つかりません。 結局ソースコードを読むしかないのか、とちょっと残念な気持ちになります。
ところで、ここまで解説してきましたが、モックがどれくらいテスト上で重要なのでしょうか? そしてどれくらい有効に使えるのでしょうか? プログラムの下位のパーツの代わりを期待されるモックとスタブですが、テスト用のパーツを書くほうが分かりやすいような気もします。 その2つは、やろうとしていることは同じで方法が違うだけです。 こんな考えが浮かぶのは、まだまだテストということの勉強が足りないのでしょうか。
最後に他のテストツールで有名なRspecについてひとこと触れたいと思います。 RSpecは使ったことがあり、本も呼んだことがあります。 RSpecは対象のプログラムの振る舞いを記述することにかなりの重点を置いているように思います。 テストだけではなく、そのプログラムの仕様を記述する感じです。 それがspec(specification 仕様)が名前になっている理由かもしれません。
実はminitestでもspec風の書き方ができるのです。 minitestのドキュメントサイトに少しだけですが、説明があります。 また、RSpecの書き方については書籍などを参考にしてください。
単項マイナスとは 単項マイナスと括弧 括弧なし単項マイナスを許容する場合のBNF calcの場合
パーサ・ジェネレータとは 少し複雑な文法 四則(加減乗除)計算のBNF Racc で実装 クラス定義、BNFの記述部分 ヘッダー、インナー、フッター コンパイルと実行 演算子の優先順位と結合における左右の優先順位 まとめ
StrScanライブラリのドキュメント 字句解析とは StrScanライブラリ StrScanライブラリを使った字句解析 実例
lbtというgemを作って公開してみた lbtはどんなgemか ファイルの配置 lbt.gemspec Rakefile gemのビルド RubyGems.orgへのアップロード 補足・・rake/gempackagetaskサブライブラリについて
文字列のエンコーディングに頭を悩ませることはほとんどなくなりました。 なぜなら、どのアプリ、システムもUTF-8を使うようになったからです。 Rubyでもエンコーディングの問題が起こることはまず無いでしょう。 ですが、今回はエンコーディングの考え方を整理してみたいと思います。
Fiberを書いたときから、次はスレッドを書こうと思っていましたが、時間がかかってしまいました。 その理由は、期待したとおりのスレッドの効果がなかったためです。 今回はそのことを書きますが、これはRubyのスレッドの抱えている問題なのか、自分のやり方が悪いのかははっきりしていません。
Fiberは「ノンプリエンプティブな軽量スレッド」とRubyのマニュアルに記載されています。
今回はRubyプログラムから自動的にドキュメントを作成するRDocについて書きたいと思います。 私はこのことについて、エキスパートではありません。 この記事も、初心者の体験談だと考えてください。
Ruby/Gtkの記事を先日書いたときに、「これはかなり使える」という手応えを感じたので、WordBook(Railsで作った単語帳プログラム)のGTK 4版を作りました。 プログラムは「徒然なるままにRuby」のGitHubレポジトリに置いてあります。 レポジトリをダウンロードし、ディレクトリ_example/...
今回はGTK 3とGTK 4をRubyで使うライブラリについて書きたいと思います。
今回もRubyとGUIのトピックです。 Glimmerを取り上げます。
Rubyはグラフィックについて弱い印象があります。 しかし、グラフィックはデバイスに関することなので、言語そのものには直接の関係はないはずで、あるとすればライブラリです。 今後グラフィック関係のgemが開発されることに期待しましょう。
Rails7におけるシステムテストについて書きます。
前回作ったWordbook(リソースフル)のテストを書いてみます。 RailsのテストはminitestをRails用に拡張したものです。
今回はRailsの慣例に沿った形でWordbookを作り直します。
今回はWordBookの検索と削除についてです。
今回はRailsにおけるデータの作成と保存、そして変更について説明します。 そのベースになるモデルとデータベースの話から始め、appendとchangeの動作について詳しく説明します。
一般に、HTMLは文書の構造を表し、CSSはその体裁(見栄え)を表します。 Railsは最終的にCSSを含むHTML文書を出力するので、この2つについての理解が必須です。 この記事ではとくにCSSの人気ライブラリであるBootstrapを紹介します。 BootstrapはJavascriptも含んでいます。
Rubyの最も人気のあるアプリケーションであるRuby on Railsを取り上げようと思い、書き始めました。 予想してはいましたが、相当な分量になってしまいました。 そのため、何回かに分けて記事にすることにします。 また、対象となる読者のレベルをどうしようかと考えましたが、「徒然Ruby」が基礎的な内容から始ま...
Rubyのライブラリ管理システムのRubygemsとコマンドgemおよびbundlerについて説明します。
minitestについて連続して2回書いてきました。 「minitestはドキュメントが少ない」という人がいますが、私も同感です。 例えば、モックとスタブの説明も少ないです。 そこで、今回はmock.rbのソースコードを参考に、モックの私的ドキュメントを書いてみました。 あくまで私個人の考えであり、minites...
今回もminitestの話です。 mockとstubに焦点をあて説明します。
アプリ作成の記事でminitestを使いました。 今回はminitestについて、また一般にテストについて、私の考えを書こうと思います。
今回はメソッドの呼び出し制限ついて説明します。 呼び出し制限にはpublic、private、protectedの3つがあります。
今回は特異メソッド、特異クラス定義、名前空間、モジュール関数について説明します。
2023/10/29 追記:この記事は新しく書き直しました。 古い記事で使っていたGitHubのCalcが大幅にアップデートされたためです。 そこで、この記事に合うようなプログラムsimple_calcを新たに作りました。 このプログラムは本レポジトリの_example/simple_calcにあります。
if〜elsif〜・・・〜else〜endは皆さん良く使うでしょうか? これは場合分けで良く使われる方法です。 これと同様の制御構造にcase文があります。 Cのswitch文に似ていますが、より強力な機能を持っています。 if-else-endよりも高い能力があるといえます。
Procオブジェクトを生成するメソッドlambdaについて説明します。
今回はブロックを一般化したオブジェクトProcを説明します。
ブロック付きメソッドの作り方を説明します。
モジュールには名前空間とミックスイン(Mix-in)の2つの機能があります。 ここではミックスインについて説明します。
クラスの親子関係
Rubyの演算子とその再定義について書きます。
今回からクラスとインスタンスを定義、生成する方法を説明します
Kernelモジュールのメソッドはどこでも使うことができます。 そのメソッドの中には便利で有用なものが多いです。
ここでは私が便利だと思ったメソッドを紹介します。
実数
今回はシンボルとハッシュについて説明します。
文字列は最も使うオブジェクトのひとつです。 特にウェブ・アプリケーションでは、コンテンツだけでなくHTMLのタグやCSSを含めすべてが文字列です。 Rubyは文字列オブジェクトのメソッドが充実しており、またパターンマッチのための正規表現も充実しています。
配列は、どのプログラミング言語にもあると思います。 複数の要素を一括して扱うことができるのが配列です。 Rubyの配列はメソッドが充実しているので、プログラムを効率的、機能的に書くのに役立ちます。
今回の目標はインスタンスです。 インスタンスを説明するために、ローカル変数と文字列オブジェクトを事前に扱います。
今回はメソッド定義です。 メソッド定義はRubyの核心ですが、今回はトップレベルに限って説明します。 この限定によって、内容はかなり易しくなっています。
ブロックはRubyの特長です。 ブロックのおかげで記述が非常にすっきりと分かりやすくなります。 今回はブロックをイテレータの本体として使う方法を説明します。
ここではRubyの最も基本的なオブジェクトである整数について説明します。
「徒然なるままに」をネットで調べてみると、「することもなく、手持無沙汰なのにまかせてという意味」とありました。 まさに、自分の現状を言い当てた言葉。 しかも、ブログに書くネタもなかなか思いつかない日々。