http://webos-goodies.jp/archives/51111790.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=EUC-JP" />

<title>Ruby スクリプトのユニットテスト・チュートリアル - WebOS Goodies</title>

<link rel="stylesheet" href="http://webos-goodies.jp/template/site.css" type="text/css" />
<link rel="stylesheet" href="http://webos-goodies.jp/template/articles.css" type="text/css" />
<link rel="alternate" type="application/rss+xml" title="RSS" href="http://webos-goodies.jp/index.rdf" />
<link rel="alternate" type="application/atom+xml" title="Atom" href="http://webos-goodies.jp/atom.xml" />
<link rel="shortcut icon" href="http://webos-goodies.jp/favicon.ico" type="image/x-icon" />




<!-- **** Opera Widgets auto detection **** -->

<link rel="alternate"
      type="application/x-opera-widgets"
      title="WebOS Goodies"
      href="http://widgets.opera.com/widgetize/Feed%20Reader/Advanced/?serve&amp;skin=skin2&amp;widgetname=WebOS%20Goodies&amp;url=http%3A%2F%2Fwebos-goodies.jp%2Fatom.xml&amp;rel=myopera&amp;ref="/>

<!-- **** Firefox search plugin auto detection **** -->

<link rel="search"
      type="application/opensearchdescription+xml"
      title="WebOS Goodies"
      href="http://webos-goodies.googlecode.com/svn/trunk/products/firefox-search/webos-goodies-jp/webos-goodies-jp.xml"/>

<!-- **** Yahoo! UI **** -->

<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.2.0/build/container/assets/container.css">

<script type="text/javascript" src="http://yui.yahooapis.com/2.2.0/build/yahoo-dom-event/yahoo-dom-event.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.2.0/build/animation/animation-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.2.0/build/container/container-min.js"></script>

<!-- **** Page specific scripts **** -->

<script type="text/javascript" src="http://parts.blog.livedoor.jp/js/emoji.js"></script>
<script type="text/javascript" src="http://parts.blog.livedoor.jp/js/misc.js"></script>
<script type="text/javascript" src="http://webos-goodies.jp/template/common.js"></script>

</head>

<body>

<!-- **** Script **** -->

<script type="text/javascript"> <!--
SWBodyTop();
SWAuthAdmin();
//-->
</script>

<noscript>
<a href="http://w1.ax.xrea.com/c.f?id=100527431" target="_blank">
<img src="http://w1.ax.xrea.com/l.f?id=100527431&url=X"  border="0" />
</a>
</noscript>





<a name="top"></a>

<div id="wrap">

<!-- **** blog タイトル **** -->

<div id="top">

<div class="rights"></div>
<div id="search">
<form class="search" id="search_engine" method="GET" action="http://google.com/cse">
<p>
<input class="text" name="q" type="text" id="keywords" value="Google Custom Search" onfocus="document.forms['search_engine'].keywords.value='';" onblur="if (document.forms['search_engine'].keywords.value == '') document.forms['search_engine'].keywords.value='Google Custom Search';" />
<input class="searchbutton" name="submit" type="submit" value="Search" /></p>
<input type="hidden" name="cx" value="011600946954025377643:3gub1bav68w" />
<input type="hidden" name="cof" value="FORID:0" />
<input type="hidden" name="ie" value="EUC-JP" />
</form>
</div><!-- search -->

<div class="lefts">
<!-- <h1<a href="http://webos-goodies.jp/">WebOS Goodies</a></h1> -->
<h1>WebOS Goodies</h1>
<h2>Gentoo Linux で個人サーバーから名称変更しました。今後ともよろしくお願いします！</h2>
</div><!-- lefts -->
</div><!-- top -->

<!-- **** メニュー **** -->

<div id="topmenu">
<div class="rights"></div>
<div class="lefts"><ul>
<li><a class="current" href="http://webos-goodies.jp/" title="ホームページへ移動">Home</a></li>
<li><a href="http://wiki.livedoor.jp/sourcewalker/d/" title="Wiki へ移動">Wiki</a></li>
</ul></div><!-- lefts -->
</div><!-- topmenu -->

<!-- **** サイトの趣旨 **** -->

<div id="subheader">
<div class="text">
<strong>WebOS Goodies へようこそ！</strong>
WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
</div>
<div style="clear:both;"></div>
</div>

<!-- **** 購読リンク **** -->

<div id="subscribe">Subscribe&nbsp;
<a href="http://webos-goodies.jp/atom.xml" title="Atom feed"><img alt="[Atom feed]" width="14" height="14" src="http://webos-goodies.jp/template/imgs/feed/feed.png"/></a>&nbsp;
<a href="http://fusion.google.com/add?feedurl=http://webos-goodies.jp/atom.xml" title="Google Gadget/Google Readerで購読"><img alt="[Google]" width="68" height="17" src="http://webos-goodies.jp/template/imgs/feed/google.gif" />&nbsp;
<a href="http://www.bloglines.com/sub/http://webos-goodies.jp/" title="Bloglinesで購読"><img src="http://webos-goodies.jp/template/imgs/feed/bloglines.gif" alt="[Bloglines]" width="76" height="17"/></a>&nbsp;
<a href="http://reader.livedoor.com/subscribe/http://webos-goodies.jp/" title="livedoor Readerで購読"><img width="91" height="17" src="http://webos-goodies.jp/template/imgs/feed/livedoorreader.gif" alt="[livedoor Reader]" /></a>&nbsp;
<a href="http://r.hatena.ne.jp/append/http://webos-goodies.jp/" title="はてなRSSで購読"><img src="http://webos-goodies.jp/template/imgs/feed/hatenarss.gif" alt="[はてなRSS]" width="80" height="15"/></a>&nbsp;
<a href="http://widgets.opera.com/widgetize/Feed%20Reader/Advanced/?serve&skin=skin2&widgetname=WebOS%20Goodies&url=http%3A%2F%2Fwebos-goodies.jp%2Fatom.xml&rel=myopera&ref=" title="Opera Widgetで購読"><img src="http://webos-goodies.jp/template/imgs/feed/operawidget.png" alt="[Opera Widgetize]" width="80" height="15"></a>
</div>


<div id="main">

<div id="leftside">

<!--
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/"
         xmlns:dc="http://purl.org/dc/elements/1.1/">
<rdf:Description
    rdf:about="http://webos-goodies.jp/archives/51111790.html"
    trackback:ping="http://app.blog.livedoor.jp/sourcewalker/tb.cgi/51111790"
    dc:title="Ruby スクリプトのユニットテスト・チュートリアル"
    dc:identifier="http://webos-goodies.jp/archives/51111790.html"
    dc:subject="web-development,rails"
    dc:description=" 少し前に公開した Ruby 用 JSON クラスに数多くのバグを仕込んでしまい（たいへんご迷惑をおかけしました m(_ _)m）、テストの重要性を改めて痛感している今日この頃です。今後も開発を続けるにあたって、現在の行き当たりばったりなテスト方法ではとてもやっていけ..."
    dc:creator="sourcewalker"
    dc:date="2007-04-10T10:01:45+09:00" />
</rdf:RDF>
-->


<div class="article">

<!-- **** 記事タイトル **** -->

<div class="article_head">
<h1 class="title">Ruby スクリプトのユニットテスト・チュートリアル</h1>
</div>

<!-- **** 記事本文 **** -->

<div class="article_body">
<div class="dokuwiki">
<div class="level1">

<p>
 少し前に公開した <a href="http://webos-goodies.jp/archives/51071565.html" class="iw_lb">Ruby 用 JSON クラス</a>に数多くのバグを仕込んでしまい（たいへんご迷惑をおかけしました m(_ _)m）、テストの重要性を改めて痛感している今日この頃です。今後も開発を続けるにあたって、現在の行き当たりばったりなテスト方法ではとてもやっていけないと危機感を持ちまして、きちんとしたユニットテストの方法を調べてみました。
</p>

<p>
で、実際に試してみたところ、これがとても（゜∀゜）イイ！バグを早期に発見できるのはもちろんですが、実は開発効率という面でも大きなメリットがあることがわかりました。小規模な部品の状態でもきちんと動作確認できるので、パーツごとに各個撃破でプログラムを組み上げていく、完全なボトムアップ型アプローチが可能になります。これにより、最も効率の良い順番でコーディングを進めることができるわけです。従来は早い段階で動作テストをするために無理をして全体を仮組みしていましたが、それに比べて工数面でも精神面でもかなりの負担軽減になります。この手のメリットは言葉ではいろいろ聞いていましたが、実際に体験するとまったく別次元です。どんなことでも、やってみるのが重要ですね。
</p>

<p>
そんなわけで、本日は <a href="http://wiki.livedoor.jp/sourcewalker/d/Ruby" class="iw_lw">Ruby</a> におけるユニットテストの方法をチュートリアル的にまとめてみました。標準の <a href="http://www2.ruby-lang.org/ja/man/?cmd=view;name=Test%3A%3AUnit">Test::Unit</a> によるテストはもちろん、 <a href="http://onestepback.org/software/flexmock/">FlexMock</a> によるモックオブジェクトの作成、 <a href="http://eigenclass.org/hiki/rcov">rcov</a> によるカバレッジテストなんてものにも挑戦しています。今回も詳細は省いて基本的な手順にフォーカスしていますので、これからユニットテストをやってみようという方は、ぜひ参考にしてください。目指せテストファースト開発！（笑）
</p>

</div>
</div>

</div><!-- article_body -->

<a name="more"></a>



<div class="article_body">
<div class="dokuwiki">
<h1><a name="testunit_niyoruyunitotesuto" id="testunit_niyoruyunitotesuto">Test::Unit によるユニットテスト</a></h1>
<div class="level1">

<p>
 <a href="http://wiki.livedoor.jp/sourcewalker/d/Ruby" class="iw_lw">Ruby</a> スクリプトのテストを自動化する際の基本的な枠組みが <strong>Test::Unit</strong> です。これは <a href="http://wiki.livedoor.jp/sourcewalker/d/Ruby" class="iw_lw">Ruby</a> に標準で添付されているクラスライブラリですので、すぐに使い始めることができます。今回はチュートリアルということで、以下のような簡単なクラスをテストしてみましょう。
</p>
<pre class="code ruby"><span class="kw1">class</span> NumericInspector
&nbsp;
  <span class="kw1">def</span> is_negative<span class="br0">&#40;</span>v<span class="br0">&#41;</span>
    v &lt;= <span class="nu0">0</span>
  <span class="kw1">end</span>
&nbsp;
  <span class="kw1">def</span> is_integer<span class="br0">&#40;</span>v<span class="br0">&#41;</span>
    v.<span class="me1">is_a</span>?<span class="br0">&#40;</span><span class="kw3">Integer</span><span class="br0">&#41;</span>
  <span class="kw1">end</span>
&nbsp;
<span class="kw1">end</span></pre>
<p>
この <strong>NumericInspector</strong> クラスは数値オブジェクトの調査を行うもので、引数が負の値なら真を返す <strong>is_negative</strong> と、引数が整数なら真を返す <strong>is_integer</strong> の 2 つのメソッドがあります。 <strong>is_negative</strong> には明らかな間違いがありますが、このバグは後のテストで検出することにしましょう。
</p>

<p>
<strong>Test::Unit</strong> によるユニットテストを実行するためには、テスト内容を記述した <a href="http://wiki.livedoor.jp/sourcewalker/d/Ruby" class="iw_lw">Ruby</a> スクリプトを作成しなければなりません。 <strong>NumericInspector</strong> クラスに対するテストスクリプトは以下のような感じになります。
</p>
<pre class="code ruby"><span class="kw3">require</span> '<span class="kw3">test</span>/unit'
<span class="kw3">require</span>'numericinspector'
&nbsp;
<span class="kw1">class</span> TC_NumericInspector &lt; <span class="kw3">Test</span>::Unit::TestCase
&nbsp;
  <span class="kw1">def</span> setup
    @obj = NumericInspector.<span class="me1">new</span>
  <span class="kw1">end</span>
&nbsp;
  <span class="kw1">def</span> test_is_negative
    assert<span class="br0">&#40;</span>!@obj.<span class="me1">is_negative</span><span class="br0">&#40;</span><span class="nu0">1</span><span class="br0">&#41;</span>, <span class="st0">"1 が負の数として判定されました。"</span><span class="br0">&#41;</span>
    assert<span class="br0">&#40;</span>@obj.<span class="me1">is_negative</span><span class="br0">&#40;</span><span class="nu0">-1</span>.<span class="nu0">5</span><span class="br0">&#41;</span>, <span class="st0">"-1.5 が負の数として判定されませんでした。"</span><span class="br0">&#41;</span>
    assert<span class="br0">&#40;</span>!@obj.<span class="me1">is_negative</span><span class="br0">&#40;</span><span class="nu0">0</span><span class="br0">&#41;</span>, <span class="st0">"0 が負の数として判定されました。"</span><span class="br0">&#41;</span>
  <span class="kw1">end</span>
&nbsp;
  <span class="kw1">def</span> test_is_integer
    assert<span class="br0">&#40;</span>@obj.<span class="me1">is_integer</span><span class="br0">&#40;</span><span class="nu0">1</span><span class="br0">&#41;</span>, <span class="st0">"1 が整数として判定されませんでした。"</span><span class="br0">&#41;</span>
    assert<span class="br0">&#40;</span>!@obj.<span class="me1">is_integer</span><span class="br0">&#40;</span><span class="nu0">1</span>.<span class="nu0">5</span><span class="br0">&#41;</span>, <span class="st0">"1.5 が整数として判定されました。"</span><span class="br0">&#41;</span>
  <span class="kw1">end</span>
&nbsp;
<span class="kw1">end</span></pre>
<p>
テスト用に <strong>TC_NumericInspector</strong> クラスを定義しています。クラスの名前はなんでもかまいませんが、必ず <strong>Test::Unit::TestCase</strong> を継承するようにしてください。
</p>

<p>
実際のテスト内容は名前が &quot;test_&quot; で始まるメソッドとして定義します。これらのメソッドは、テストを実行した際にすべて自動的に呼び出されます。メソッドの内部で呼び出している <strong>assert</strong> は <strong>TestCase</strong> クラス（正確には <strong>Test::Unit::Assertions</strong> モジュール）のメソッドで、第一引数が偽であればテスト失敗としてメッセージを表示します、。その他にも多数の判定メソッドがありますので、詳細は<a href="http://www2.ruby-lang.org/ja/man/?cmd=view;name=Test%3A%3AUnit#Test.3a.3aUnit.3a.3aAssertions">リファレンスマニュアル</a>をご覧ください。
</p>

<p>
残りの <strong>setup</strong> メソッドはそれぞれの &quot;test_&quot; メソッドの直前に毎回呼び出されるメソッドで、全テスト共通の初期化処理などを記述するのに便利です。類似のメソッドとして <strong>teardown</strong> もあり、こちらは各 &quot;test_&quot; メソッドの終了直後に呼ばれます。
</p>

<p>
さて、テストスクリプトができたところで、さっそくテストしてみましょう。単純にテストスクリプトを実行すれば OK です。
</p>
<pre class="code">ruby test_numericinspector.rb</pre>

<p>
実際にやってみると・・・
</p>
<pre class="code">$ ruby test_numericinspector.rb
Loaded suite test_numericinspector
Started
.F
Finished in 0.026 seconds.

  1) Failure:
test_is_negative(TC_NumericInspector) [test_numericinspector.rb:13]:
0 が負の数として判定されました。.
&lt;false&gt; is not true.

2 tests, 5 assertions, 1 failures, 0 errors</pre>

<p>
ありゃ、失敗です。 NumericInspector#is_negative(0) が真を返してしまったようです。ソースを見てみると、たしかにメソッドの中身が v &lt;= 0 になっていました。ここは v &lt; 0 でなければいけませんよね。そのように修正して再度テストを実行すると・・・
</p>
<pre class="code">$ ruby test_numericinspector.rb
Loaded suite test_numericinspector
Started
..
Finished in 0.003 seconds.

2 tests, 5 assertions, 0 failures, 0 errors</pre>

<p>
今度はばっちり成功です。
</p>

<p>
以上が <strong>Test::Unit</strong> を利用したユニットテストの基本的な流れです。もし、 <strong>NumericInspector</strong> に新しい機能を追加したときは、対応する &quot;test_&quot; メソッドを <strong>TC_NumericInspector</strong> クラスに追加すれば良いでしょう。さらに詳しい使い方に関しては、<a href="http://www2.ruby-lang.org/ja/man/?cmd=view;name=Test%3A%3AUnit">リファレンスマニュアル</a>にひと通り掲載されているのでご参照ください。
</p>

</div>
<!-- SECTION [2291-7046] -->
<h1><a name="flexmock_demokuobuzyekutowo" id="flexmock_demokuobuzyekutowo">FlexMock でモックオブジェクトを作成</a></h1>
<div class="level1">

<p>
 他のクラスに依存していない単純なクラスなら、前述の <strong>Test::Unit</strong> だけでじゅうぶんです。しかし、内部で他のクラスを呼び出すような複雑なクラス、とくにデータベースやネットワークなどの外部環境にアクセスするようなクラスの場合はどうでしょう。通常、外部環境は場合によって返してくるデータが異なりますから、テスト結果を判定するのはひと苦労です。ましてや書き込みを行うような機能などは、テストのために実際にデータを変更するわけにもいかないので、明らかに無理がありますよね。
</p>

<p>
一般的に、このような状況には「モックオブジェクト」を作るすることで対処します。「モックオブジェクト」はテスト対象のコードが内部で使用するオブジェクトと同じインターフェースを持ちますが、実際の処理は実行せず、引数の検証とテストコードを動かすために必要な最低限の出力のみを行います。 <a href="http://wiki.livedoor.jp/sourcewalker/d/Ruby" class="iw_lw">Ruby</a> で簡単にモックオブジェクトを作れるライブラリはいくつかあるようですが、今回は <strong>FlexMock</strong> というものを使ってみました。
</p>

<p>
<a href="http://onestepback.org/software/flexmock/">http://onestepback.org/software/flexmock/</a>
</p>

<p>
それでは実際に使ってみましょう。 <strong>FlexMock</strong> は標準ライブラリには含まれていないので、まずはインストールからです。 <a href="http://wiki.livedoor.jp/sourcewalker/d/RubyGems" class="iw_lw">RubyGems</a> が使えるなら、以下のコマンドで簡単にインストールできます。
</p>
<pre class="code">gem install --remote flexmock</pre>

<p>
もし <a href="http://wiki.livedoor.jp/sourcewalker/d/RubyGems" class="iw_lw">RubyGems</a> をまだ入れていないという場合は、この際なのでインストールしてしまいましょう。<a href="http://webos-goodies.jp/archives/51106257.html" class="iw_lb">こちらの記事</a>で簡単にご紹介していますので、参考にしてください。
</p>

<p>
テストするコードとして、今回は以下のものを使うことにしましょう。各行に &quot;名前=値&quot; という環境変数定義のような記述をしたファイルを読み込み、その内容をハッシュとして返すという単純な関数です。
</p>
<pre class="code ruby"><span class="kw3">require</span> 'net/http'
&nbsp;
<span class="kw1">def</span> env_read<span class="br0">&#40;</span>path<span class="br0">&#41;</span>
  file = File.<span class="me1">new</span><span class="br0">&#40;</span>path, <span class="st0">"r"</span><span class="br0">&#41;</span>
  <span class="kw1">begin</span>
    result = <span class="br0">&#123;</span><span class="br0">&#125;</span>
    file.<span class="kw3">readlines</span><span class="br0">&#40;</span><span class="br0">&#41;</span>.<span class="me1">each</span> <span class="kw1">do</span> |line|
      <span class="kw1">if</span> /^\s*<span class="br0">&#40;</span>\w+<span class="br0">&#41;</span>\s*\=\s*<span class="br0">&#40;</span>\S*<span class="br0">&#41;</span>/ === line
        result<span class="br0">&#91;</span>$<span class="nu0">1</span><span class="br0">&#93;</span> = $<span class="nu0">2</span>
      <span class="kw1">end</span>
    <span class="kw1">end</span>
    result
  <span class="kw1">ensure</span>
    file.<span class="me1">close</span>
  <span class="kw1">end</span>
<span class="kw1">end</span></pre>
<p>
モックオブジェクトの記述を単純にするため、こちらのコードはいささか冗長な記述になっています。本末転倒な感じですが、サンプルということでご勘弁ください。
</p>

<p>
さて、上記のコードでは外部ファイルを読み込んでいるため、テストのためにはテスト用データを格納したファイルを用意して、それがきちんと Hash に変換されているかを検証しなくてはなりません。これを回避してテストコード内で自由にテストデータを生成できるようにするには、 File クラスをモックオブジェクトに差し替えるのが良さそうです。それを実装したテストコードは以下のようになります。
</p>
<pre class="code ruby"><span class="co1">#! /usr/bin/ruby</span>
&nbsp;
<span class="kw3">require</span> 'rubygems'
<span class="kw3">require</span> '<span class="kw3">test</span>/unit'
<span class="kw3">require</span> 'flexmock'
<span class="kw3">require</span> 'env_read'
&nbsp;
<span class="kw1">class</span> TC_EnvRead &lt; <span class="kw3">Test</span>::Unit::TestCase
  <span class="kw1">include</span> FlexMock::TestCase
&nbsp;
  <span class="kw1">def</span> test_env_read
    source = <span class="br0">&#91;</span><span class="br0">&#93;</span>
    result = <span class="br0">&#123;</span> <span class="st0">"name1"</span> =&gt; <span class="st0">"value1"</span>, <span class="st0">"name2"</span> =&gt; <span class="st0">"value2"</span> <span class="br0">&#125;</span>
    result.<span class="me1">each</span> <span class="kw1">do</span> |key, value|
      source &lt;&lt; key + <span class="st0">"="</span> + value + <span class="st0">"<span class="es0">\n</span>"</span>
    <span class="kw1">end</span>
&nbsp;
    flexstub<span class="br0">&#40;</span>File<span class="br0">&#41;</span> <span class="kw1">do</span> |fileclass|
      fileclass.<span class="me1">should_receive</span><span class="br0">&#40;</span>:new<span class="br0">&#41;</span>.<span class="me1">with</span><span class="br0">&#40;</span>'<span class="kw3">test</span>.<span class="me1">env</span>', 'r'<span class="br0">&#41;</span>.<span class="me1">and_return</span> <span class="kw1">do</span>
        flexmock<span class="br0">&#40;</span>'file'<span class="br0">&#41;</span> <span class="kw1">do</span> |fileobj|
          fileobj.<span class="me1">should_receive</span><span class="br0">&#40;</span>:<span class="kw3">readlines</span><span class="br0">&#41;</span>.<span class="me1">with_no_args</span>.<span class="me1">and_return</span><span class="br0">&#40;</span>source<span class="br0">&#41;</span>
          fileobj.<span class="me1">should_receive</span><span class="br0">&#40;</span>:close<span class="br0">&#41;</span>.<span class="me1">with_no_args</span>.<span class="me1">once</span>
        <span class="kw1">end</span>
      <span class="kw1">end</span>
    <span class="kw1">end</span>
&nbsp;
    assert_equal<span class="br0">&#40;</span>result, env_read<span class="br0">&#40;</span>'<span class="kw3">test</span>.<span class="me1">env</span>'<span class="br0">&#41;</span><span class="br0">&#41;</span>
  <span class="kw1">end</span>
&nbsp;
<span class="kw1">end</span></pre>
<p>
こちらのテストもずいぶんいい加減ですが、ご勘弁を。<strong>FlexMock</strong> を使ったテストの場合も、基本は <strong>Test::Unit</strong> による枠組みを使用しますので、だいたいの処理の流れは既にご理解いただけると思います。以降、 <strong>FlexMock</strong> に関連する部分に的を絞って、軽くご紹介していきましょう。
</p>

<p>
まずは最初に &quot;flexmock&quot; を require しています。当然ながら、これをやらないと <strong>FlexMock</strong> の機能は利用できません。次に、テストケースのクラスに <strong>FlexMock::TestCase</strong> モジュールを include しています。これを行うことで、モックオブジェクトを作成するためのメソッドなどを簡単に呼び出せます。もちろん名前空間をいちいち指定すればこれは必要ありませんが、あまりメリットはないでしょう。ここまでは <strong>FlexMock</strong> を利用する際の一般的な手順なので、しきたりとして覚えてしまってください。
</p>

<p>
さあ、ここからが肝です。今回のコードでは、 <strong>test_env_read</strong> メソッド内でモックオブジェクト作成のすべての処理を行っています。まず、使用しているメソッドの概要をまとめてご紹介してしまいましょう。 </p>
<dl>
<dt><span class='term'> flexstub</span></dt>
<dd> 第一引数に指定した既存のオブジェクトをモックオブジェクト化するメソッドです。主にクラスやグローバルオブジェクトの挙動を変更したい場合に利用します。このメソッドを読んだだけでは既存オブジェクトの挙動はほとんど変化しませんが、後に <strong>should_receive</strong> などを利用してメソッドを再定義することができます。このように、既存のオブジェクトを基にしたモックオブジェクトを「スタブ」と呼びます。</dd>
<dt><span class='term'> flexmock</span></dt>
<dd> 空のモックオブジェクトを作成します。主に <strong>new</strong> メソッドなどでクラスインスタンスを作成する際に利用します。 <strong>flexstub</strong> で作成したオブジェクトと同様に、 <strong>should_receive</strong> でメソッドを定義できます。引数は任意の文字列で、エラーメッセージなどに使われます。</dd>
<dt><span class='term'> should_receive</span></dt>
<dd> モックオブジェクトのメソッドを(再)定義します。引数としてメソッド名のシンボルを渡してください。このメソッドの後ろに <strong>with</strong> や <strong>and_return</strong> などの呼び出しを続けることで、引数や戻り値などの追加情報 (Expectation) を指定することができます。同じメソッドに対して複数 <strong>should_receive</strong> を呼び出して、引数によって場合分けすることも可能です。</dd>
<dt><span class='term'> with</span></dt>
<dd> メソッドに指定される引数を指定します。マッチングは === で行われますので、クラスや正規表現などを指定することも可能です。</dd>
<dt><span class='term'> with_no_args</span></dt>
<dd> メソッドに引数がないことを宣言します。</dd>
<dt><span class='term'> and_return</span></dt>
<dd> 引数に指定したオブジェクトをメソッドの戻り値とします。ブロックを指定した場合は、メソッドが呼ばれた際にそのブロックを評価して、その結果をメソッドの戻り値とします。</dd>
<dt><span class='term'> once</span></dt>
<dd> そのメソッドが 1 回のみ呼ばれなければならないことを宣言します。</dd>
</dl>
<p>これで <strong>test_env_read</strong> がなにをやっているかはだいたい予想がつくのではないでしょうか。念のために簡単にまとめておくと、以下のようになっています。 
</p>
<ol>
<li class="level1"><div class="li"> 最初に、テストデータ（結果として変えるはずの Hash と、そこから逆算したソースファイルのテキスト）を生成しています。</div>
</li>
<li class="level1"><div class="li"> <strong>flexstub</strong> メソッドを使って、 <strong>File</strong> オブジェクトをスタブ化しています。 <strong>flexstub</strong> にブロックを与えると、スタブオブジェクトを引数に渡してそのブロックを評価します。</div>
</li>
<li class="level1"><div class="li"> <strong>should_receive</strong> メソッドを使って、 <strong>File::new</strong> を再定義しています。引数として ('test.env', 'r') を指定していますので、これ以外の引数で呼ばれた場合はテスト失敗となります。</div>
</li>
<li class="level1"><div class="li"> <strong>File::new</strong> の処理中で、 <strong>flexmock</strong> オブジェクトを使って新規のモックオブジェクトを作成し、それに <strong>readlines</strong> と <strong>close</strong> のメソッドを追加しています。 <strong>readlines</strong> の戻り値として (1) で生成したテキストを指定しているので、 <strong>env_read</strong> 内で <strong>readlines</strong> が呼ばれた際にこのテキストが返るわけです。また、 <strong>close</strong> には <strong>once</strong> を呼び出しているので、テスト中で必ず一回 <strong>close</strong> が呼ばれなければテスト失敗となります。</div>
</li>
<li class="level1"><div class="li"> 最後に、テスト対象の <strong>env_read</strong> メソッドを呼び出し、その結果が (1) で生成した Hash と等しいかどうかを <strong>assert_equal</strong> で検証しています。</div>
</li>
</ol>

<p>
 今回使用した Expectation はごく基本的なものだけですので、工夫すればさらに詳細な指定も可能です。利用可能な Expectation は<a href="http://onestepback.org/software/flexmock/">リファレンスマニュアル</a>にリストアップされていますので、そちらをご参照ください。また、今回はテストメソッドの中ですべてのモックオブジェクトを作成していますが、これは必須ではありません。例えば、すべてのテストで共通に利用するモックオブジェクトがあるなら、 <strong>setup</strong> メソッドで作成するのが適当だと思います。
</p>

</div>
<!-- SECTION [7047-16784] -->
<h1><a name="rcov_niyorukabarezitesuto" id="rcov_niyorukabarezitesuto">rcov によるカバレッジテスト</a></h1>
<div class="level1">

<p>
 カバレッジテストという言葉をご存知でしょうか。今回ご紹介したようなテストを実行した際に、プログラムのどれだけの部分が実行されたかという網羅率（カバレッジ）を測定するテストのことです。いわば、テストのテストとも言うべきものですね（笑）。 <a href="http://wiki.livedoor.jp/sourcewalker/d/Ruby" class="iw_lw">Ruby</a> でも簡単なカバレッジテストを行うツールがいくつか公開されています。今回は、その中から <strong>rcov</strong> というツールを試してみましたので、その使い方なども簡単にご紹介しておきます。
</p>

<p>
<a href="http://eigenclass.org/hiki/rcov">http://eigenclass.org/hiki/rcov</a>
</p>

<p>
まずはインストール方法です。 <strong>rcov</strong> は高速化のために C のライブラリを含んでいますので、インストールには C コンパイラが必要です。ただし、 Windows 環境では例外的にコンパイル済みのバイナリが提供されています。いずれにせよ、インストールには <a href="http://wiki.livedoor.jp/sourcewalker/d/RubyGems" class="iw_lw">RubyGems</a> を利用するのが簡単です。
</p>
<pre class="code"> gem install --remote rcov</pre>

<p>
実行すると以下のような選択肢が表示されるます。 Windows で One-Click Installer を使っている場合は (mswin32) を、それ以外の場合は (ruby) を選択してください。 Cygwin の場合も (ruby) で問題なくインストールできました。
</p>
<pre class="code">Select which gem to install for your platform (i386-cygwin)
 1. rcov 0.8.0.2 (mswin32)
 2. rcov 0.8.0.2 (ruby)
 3. rcov 0.8.0.1 (mswin32)
 4. rcov 0.8.0.1 (ruby)
 5. Skip this gem
 6. Cancel installation</pre>

<p>
まだ私も使い始めたばかりなので細かい機能は把握しきれていないのですが、とりあえず以下のように測定したいスクリプトを指定して実行するだけで、 <a href="http://wiki.livedoor.jp/sourcewalker/d/HTML" class="iw_lw">HTML</a> 形式のレポートが生成されます。
</p>
<pre class="code">rcov &lt;スクリプトファイル名&gt;</pre>

<p>
例えば、 <strong>FlexMock</strong> のサンプルコードを <strong>rcov</strong> にかけると、以下のようなレポートが生成されます。
</p>

<p>
<a href="http://www.flickr.com/photos/webos-goodies/450614012/" title="Photo Sharing"><img src="http://farm1.static.flickr.com/186/450614012_87f784f185_m.jpg" width="240" height="104" alt="rcov のレポート画面 1" /></a>
</p>

<p>
さらにテスト対象コードの詳細を表示させたのがこちらです。まあ、ほとんど分岐もないので当然のように 100% で、あまり参考になりませんが・・・（＾＾ゞ
</p>

<p>
<a href="http://www.flickr.com/photos/webos-goodies/450626859/" title="Photo Sharing"><img src="http://farm1.static.flickr.com/212/450626859_1fc5e4da84_m.jpg" width="240" height="187" alt="rcov のレポート画面 2" /></a>
</p>

<p>
もし実行されていない行があれば、赤く表示されるのですぐにわかります。もちろん、これが 100% になれば完璧というわけではありませんし、ソースの記述方法によっては実行されているはずの行が赤く表示されたりもするのですが、だいたいの目安にはなります。テスト漏れがないように、ときどきチェックすると良いでしょう。
</p>

<p>
以上、駆け足になってしまいましたが、本日は <a href="http://wiki.livedoor.jp/sourcewalker/d/Ruby" class="iw_lw">Ruby</a> でユニットテストを行う方法を簡単にご紹介しました。とくに <strong>FlexMock</strong> はかなり使えますね。下位モジュールなどが完成していなくても、取り合えず大枠の動作を確認できるので、開発がかなり楽になります。さらに <strong>rcov</strong> でカバレッジをきちんとチェックしながらテストを作成すれば、ソフトウェアの品質もだいぶ向上しそうです。ま、ツールはあくまでツール、活用しなければ意味がないわけなので、今後は意識的にこれらを使っていこうと思っています。バグが減らなくてお困りの方、ぜひ試してみてください！
</p>

</div>
</div>

</div><!-- article_body -->



</div><!-- article -->

<!-- **** 関連記事 **** -->



<fieldset id="tpl_relatedframe">
<legend>関連記事</legend>
<div id="tpl_related">
</div>
</fieldset>



<!-- **** 各種リンク **** -->

<div class="article_footer">

2007年04月10日 10:01

| <a href="#comments">Comments(0)</a>


| <a href="#trackback">TrackBack(1)</a>

|

<!-- livedoor clip -->
<a href="http://clip.livedoor.com/redirect?link=http://webos-goodies.jp/archives/51111790.html&title=Ruby%20%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8%A4%CE%A5%E6%A5%CB%A5%C3%A5%C8%A5%C6%A5%B9%A5%C8%A1%A6%A5%C1%A5%E5%A1%BC%A5%C8%A5%EA%A5%A2%A5%EB%20-%20WebOS%20Goodies&ie=euc" class="ldclip-redirect" title="livedoor clip! でブックマーク"><img src="http://webos-goodies.jp/template/imgs/bookmark/livedoorclip.gif" width="16" height="16" alt="[livedoor clip!]" style="border: none;vertical-align: middle;" /></a>

<!-- はてなブックマーク -->
<a href="http://b.hatena.ne.jp/entry/http://webos-goodies.jp/archives/51111790.html" title="はてなブックマークでブックマーク"><img src="http://webos-goodies.jp/template/imgs/bookmark/hatena.gif" width="16" height="12" style="border: none; vertical-align:middle;" alt="[はてなブックマーク]" /></a>

<!-- del.icio.us -->
<a href="http://del.icio.us/post" onclick="window.open('http://del.icio.us/post?v=4&noui&jump=close&url='+encodeURIComponent('http://webos-goodies.jp/archives/51111790.html')+'&title='+encodeURIComponent('Ruby スクリプトのユニットテスト・チュートリアル - WebOS Goodies'), 'delicious','toolbar=no,width=700,height=400'); return false;" title="del.icio.us でブックマーク"><img src="http://webos-goodies.jp/template/imgs/bookmark/delicious.gif" width="10" height="10" style="border: none; vertical-align:middle;" alt="[del.icio.us]" /></a>

</div> <!-- article_footer -->

<!-- **** トラックバック **** -->

<div class="trackback">

<h1>トラックバックURL</h1>

<div class="trackback_form">
<input type="text" value="http://app.blog.livedoor.jp/sourcewalker/tb.cgi/51111790" />
<div class="notice">※スパム対策のため、トラックバックはすぐには反映されません。ご了承ください。</div>
</div>

<a name="trackback"></a>



<h1>この記事へのトラックバック</h1>

<div class="trackback_body">


<div class="trackback_item">
<h2>1.&nbsp;<a href="http://m884.jp/2007/04/0411.html">0411巡回</a>&nbsp;&nbsp;
[&nbsp;Takt&nbsp;]&nbsp;&nbsp;&nbsp;2007年04月11日 09:10</h2>
<p>やっと溜まりに溜まったフィードを消化できました。 お久しぶりです。なんか、忙しかったので。 ...</p>
</div><!-- trackback_item -->


</div><!-- trackback_body -->




</div><!-- trackback -->

<!-- **** コメント **** -->

<div class="comment">

<a name="comments"></a>




<h1>この記事にコメントする</h1>

<div></div>

<div id="comment_form">

<form action="http://app.blog.livedoor.jp/sourcewalker/comment.cgi/51111790#errors" method="POST" name="comment_form"
      onSubmit="javacript:setCommentAuthor(this); return submitOnce(this.button);">

<input type="hidden" id="email" name="email" />
<input type="hidden" id="rank" name="rank" value="" />
<input type="hidden" id="rating_icon" name="rating_icon" value="" />

<div class="with_label">
<label for="author">名前</label><input id="author" name="author" />
</div>

<div class="with_label">
<label for="url">URL</label><input id="url" name="url" />
</div>

<div class="no_label">

<div>
<div class="right"><input id="bake" type="checkbox" name="cookie" />情報を記憶</div>
<a href="javascript:void(0);" id="emojiLink1" onClick="showEmojiPanel(this.id, 'text')">
<img id="tool" width="18" height="18" border="0" src="http://parts.blog.livedoor.jp/img/usr/cmn/tool.gif" /></a>
</div>

<div>
<textarea id="text" name="body" rows="10" cols="50"></textarea>
<div id="emoji" style="position:absolute; background-color:#FFFFFF; left: 30px; border:1 solid #000000; margin:0 0 0 0; padding:0 0 0 0; display:none;"></div>
</div>

<div class="submit">
<input type="submit" value=" 投稿する " class="button" name="button" />
</div>

</div> <!-- no_label -->

</div> <!-- comment_form -->

</div> <!-- comment -->

</div><!-- leftside -->

<!-- **** サイドバー **** -->

<div id="rightside">





<!-- ssi -->

<script language="JavaScript" type="text/javascript">
<!--
var calendar_url = "http://blog.livedoor.jp/sourcewalker/calendar.js?blog_id=1427319";
if(location.href.match('/([12][0-9]+)-([0-9]+)\.html$')) {
    calendar_url += "&y=" + RegExp.$1;
    calendar_url += "&m=" + RegExp.$2;
}
document.writeln('<scr' + 'ipt type="text/javascript"' + 'src="' + calendar_url + '">' + '</scr' + 'ipt>');
-->
</script>


<div class="sidetitlebody">
<div class="sidetitle">Archives</div>
</div>
<div class="sidetop"></div>
<div class="side">
<div align="center"><form style="margin:0px;padding:0px;">
<select name="u" onChange="JavaScript:location.href=this.value;return false;">
<option value="http://webos-goodies.jp/archives/2007-04.html">2007年04月</option>

<option value="http://webos-goodies.jp/archives/2007-03.html">2007年03月</option>

<option value="http://webos-goodies.jp/archives/2007-02.html">2007年02月</option>

<option value="http://webos-goodies.jp/archives/2007-01.html">2007年01月</option>

<option value="http://webos-goodies.jp/archives/2006-12.html">2006年12月</option>

<option value="http://webos-goodies.jp/archives/2006-11.html">2006年11月</option>

<option value="http://webos-goodies.jp/archives/2006-10.html">2006年10月</option>

<option value="http://webos-goodies.jp/archives/2006-09.html">2006年09月</option>

<option value="http://webos-goodies.jp/archives/2006-08.html">2006年08月</option>

<option value="http://webos-goodies.jp/archives/2006-07.html">2006年07月</option>

<option value="http://webos-goodies.jp/archives/2006-06.html">2006年06月</option>

<option value="http://webos-goodies.jp/archives/2006-05.html">2006年05月</option>

<option value="http://webos-goodies.jp/archives/2006-04.html">2006年04月</option>

<option value="http://webos-goodies.jp/archives/2006-03.html">2006年03月</option>

<option value="http://webos-goodies.jp/archives/2006-02.html">2006年02月</option>

<option value="http://webos-goodies.jp/archives/2006-01.html">2006年01月</option>

<option value="http://webos-goodies.jp/archives/2005-12.html">2005年12月</option>
</select>
<input type="BUTTON" value="GO" onClick="JavaScript: location.href=this.form.u.value; return false;">
</form></div>

</div>
<div class="sidebottom"></div>
<div class="sidetitlebody">
<div class="sidetitle">Categories</div>
</div>
<div class="sidetop"></div>
<div class="side">
<div class="sidebody"><a href="http://webos-goodies.jp/categories/web-apps.html">一歩進んだ Web アプリケーション活用術</a></div>
<div class="sidebody"><a href="http://webos-goodies.jp/categories/opera-firefox.html">Opera &amp; Firefox をディープに使いこなす</a></div>
<div class="sidebody"><a href="http://webos-goodies.jp/categories/development.html">WebOS 時代のプログラミング・ノウハウ</a></div>
<div class="sidebody"><a href="http://webos-goodies.jp/categories/server.html">Gentoo Linux と VMware による個人サーバー構築記</a></div>
<div class="sidebody"><a href="http://webos-goodies.jp/categories/opensource-apps.html">オープンソース・アプリケーションで快適 Web ライフ</a></div>
<div class="sidebody"><a href="http://webos-goodies.jp/categories/others.html">その他の記事</a></div>

</div>
<div class="sidebottom"></div>
<div class="sidetitlebody">
<div class="sidetitle">Recent Articles</div>
</div>
<div class="sidetop"></div>
<div class="side">
<div class="sidebody"><a href="http://webos-goodies.jp/archives/51116401.html">今週の話題 : Opera 9.2 正式版リリースなど</a></div>

<div class="sidebody"><a href="http://webos-goodies.jp/archives/51114754.html">どうやらリストラされるようです （´Д｀；</a></div>

<div class="sidebody"><a href="http://webos-goodies.jp/archives/51111790.html">Ruby スクリプトのユニットテスト・チュートリアル</a></div>

<div class="sidebody"><a href="http://webos-goodies.jp/archives/51110241.html">今週の話題 : Grub で使えるスプラッシュイメージなど</a></div>

<div class="sidebody"><a href="http://webos-goodies.jp/archives/51107860.html">Google Personalized Homepage のカラム構成を変更する</a></div>

<div class="sidebody"><a href="http://webos-goodies.jp/archives/51106257.html">RubyGems の使い方</a></div>

<div class="sidebody"><a href="http://webos-goodies.jp/archives/51104497.html">今週の話題 : Google のオープンソースツールなど</a></div>

<div class="sidebody"><a href="http://webos-goodies.jp/archives/51103006.html">独自ドメインのメール送信を SPF に対応させる方法</a></div>
</div>
<div class="sidebottom"></div>
<div class="sidetitlebody">
<div class="sidetitle">Recent Comments</div>
</div>
<div class="sidetop"></div>
<div class="side">

<div class="sidebody"><a href="http://webos-goodies.jp/archives/51005401.html#comments">Office Live を無料のドメインホルダーとして活用する</a>&nbsp;(プレミアムのベータ)</div>


<div class="sidebody"><a href="http://webos-goodies.jp/archives/51114754.html#comments">どうやらリストラされるようです （´Д｀；</a>&nbsp;(歩行者)</div>


<div class="sidebody"><a href="http://webos-goodies.jp/archives/51114754.html#comments">どうやらリストラされるようです （´Д｀；</a>&nbsp;(さかな)</div>


<div class="sidebody"><a href="http://webos-goodies.jp/archives/51114754.html#comments">どうやらリストラされるようです （´Д｀；</a>&nbsp;(歩行者)</div>


<div class="sidebody"><a href="http://webos-goodies.jp/archives/51114754.html#comments">どうやらリストラされるようです （´Д｀；</a>&nbsp;(とおりすがりの失業者)</div>


<div class="sidebody"><a href="http://webos-goodies.jp/archives/51114754.html#comments">どうやらリストラされるようです （´Д｀；</a>&nbsp;(vine_user)</div>


<div class="sidebody"><a href="http://webos-goodies.jp/archives/50777061.html#comments">JavaScript で画像処理！ canvas を使ってみよう</a>&nbsp;(歩行者)</div>


<div class="sidebody"><a href="http://webos-goodies.jp/archives/50777061.html#comments">JavaScript で画像処理！ canvas を使ってみよう</a>&nbsp;(描画速度について)</div>


<div class="sidebody"><a href="http://webos-goodies.jp/archives/51104497.html#comments">今週の話題 : Google のオープンソースツールなど</a>&nbsp;(歩行者)</div>


<div class="sidebody"><a href="http://webos-goodies.jp/archives/51104497.html#comments">今週の話題 : Google のオープンソースツールなど</a>&nbsp;(さかな)</div>
</div>
<div class="sidebottom"></div>
<div class="sidetitlebody">
<div class="sidetitle">Recent TrackBacks</div>
</div>
<div class="sidetop"></div>
<div class="side"><div class="sidebody"><a href="http://m884.jp/2007/04/0411.html" target="_blank">0411巡回</a>&nbsp;(Takt)
<br /><a href="http://webos-goodies.jp/archives/51111790.html">Ruby スクリプトのユニットテスト・チュートリアル</a></div><div class="sidebody"><a href="http://m884.jp/2007/04/0411.html" target="_blank">0411巡回</a>&nbsp;(Takt)
<br /><a href="http://webos-goodies.jp/archives/51106257.html">RubyGems の使い方</a></div><div class="sidebody"><a href="http://usednote.seesaa.net/article/37530894.html" target="_blank">VineLinuxをVMWareで動かそう【その３】</a>&nbsp;(中古ノートパソコンを賢く買おう)
<br /><a href="http://webos-goodies.jp/archives/50179807.html">VMware で Linux を動かした際の時刻のずれを解消する（続き）</a></div><div class="sidebody"><a href="http://mapz.exblog.jp/5060791" target="_blank">ブラウザノポータル&quot;pota&quot;</a>&nbsp;(チミンモラスイ？)
<br /><a href="http://webos-goodies.jp/archives/50980281.html">Google Apps for Your Domain を使ってみました！ : Start page など</a></div><div class="sidebody"><a href="http://d.hatena.ne.jp/odaken/20070329/1175169095" target="_blank">[Windows] ランチャー</a>&nbsp;(odakenの日記)
<br /><a href="http://webos-goodies.jp/archives/50752356.html">スタートメニューを検索してプログラムを起動！ &quot;Launchy&quot;</a></div><div class="sidebody"><a href="http://koka.sakura.ne.jp/hiyokko/2007/03/igoogle_sidebar.html" target="_blank">IGoogle Sidebarがパワーアップしてる</a>&nbsp;(ひよっこエンジニアリング)
<br /><a href="http://webos-goodies.jp/archives/51048265.html">ブラウザのサイドバーで Google ガジェットを活用する</a></div><div class="sidebody"><a href="http://ekimore.blog.shinobi.jp/Entry/45/" target="_blank">動画検索エンジン Fooooo が凄いらしいので Firefox 検索プラグイン化</a>&nbsp;(忍が通る獣道)
<br /><a href="http://webos-goodies.jp/archives/50384842.html">Firefox 検索プラグインの作成方法</a></div><div class="sidebody"><a href="http://qdobu.or.tp/2007/03/post_26.html" target="_blank">サーバーの時間がすごくずれる</a>&nbsp;(qdoblog　弓道部)
<br /><a href="http://webos-goodies.jp/archives/50179807.html">VMware で Linux を動かした際の時刻のずれを解消する（続き）</a></div><div class="sidebody"><a href="http://forthclover.blog56.fc2.com/blog-entry-171.html" target="_blank">面白そうな技術ねた</a>&nbsp;(日暮らしSEの日々考慮　〜名古屋でオフショア そしてセレブへ？〜)
<br /><a href="http://webos-goodies.jp/archives/51064439.html">モジュールを繋げてマッシュアップ！「Yahoo! Pipes」の使い方</a></div><div class="sidebody"><a href="http://245.pandeiro.jp/index.php?UTF-8" target="_blank">UTF-8</a>&nbsp;(パンデイロ惑星 (PukiWiki/TrackBack 0.3))
<br /><a href="http://webos-goodies.jp/archives/51072404.html">UTF-8 エンコーディングの危険性</a></div>
</div>
<div class="sidebottom"></div>







<!-- ssi -->





<table style="background-color: #fff; padding: 5px; margin: 12px 0px;" cellspacing=0>
  <tr><td>
    <img src="http://groups.google.com/groups/img/3/groups_bar_ja.gif"
         height=26 width=132 alt="Google グループ Beta">
  </td></tr>
  <tr><td style="padding-left: 5px;font-size: 125%">
    <b>WebOS Goodies</b>
  </td></tr>
  <tr><td style="padding-left: 5px"> <a href="http://groups.google.com/group/webos-goodies">このグループにアクセス</a> </td></tr>
</table>

</div><!-- rightside -->

</div><!-- main -->

<!-- **** フッタ **** -->

<div id="footer">

<div class="rside">&copy; Copyright 2006, <a href="http://webos-goodies.jp/">Hokousya</a> - <a href="http://webos-goodies.jp/atom.xml">RSS Feed</a><br />
Design: <a href="http://www.free-css-templates.com/" title="Design by David Herreman">David Herreman</a>
</div><!-- rside -->

<p>Powered by <a href="http://blog.livedoor.com/" title="livedoor blog">livedoor blog</a><!-- &middot; <a href="http://demo.free-css-templates.com/snews15_1/login/" title="Login">Login</a>--><br />
<!-- <a href="http://validator.w3.org/check?uri=referer" title="Validate">XHTML</a> - <a href="http://jigsaw.w3.org/css-validator/check/referer" title="Validate">CSS</a> -->
</p>

</div><!-- footer -->

</div><!-- wrap -->

<script type="text/javascript" language="javascript">
<!--
loadCommentAuthor(document.comment_form);
//-->
</script>

<script type="text/javascript">
function tplRegistCategory(json)
{
  var f = template.generateRelatedLinks(json, 'http://webos-goodies.jp/');
  document.getElementById('tpl_related').appendChild(f);
}
</script>



<script type="text/javascript" charset="UTF-8" src="http://webos-goodies.jp/categories/jsonp/web-development.js"></script>




<script type="text/javascript" charset="UTF-8" src="http://webos-goodies.jp/categories/jsonp/rails.js"></script>



<!-- Google Analytics -->
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct = "UA-282350-2";
urchinTracker();
</script>

<script type="text/javascript" src="http://parts.blog.livedoor.jp/js/c.js"></script>
<script type="text/javascript">blog_counter('sourcewalker')</script>
<script type="text/javascript" src="http://img.blog.livedoor.com/js/stock_check.js"></script>
<script type="text/javascript">setOopsValue('4','15');</script>
<script type="text/javascript" src="http://blog03.analyzer.livedoor.jp/x.js?pid=47137"></script>
<noscript>
<img src="http://blog03.analyzer.livedoor.jp/img/a.gif?pid=47137">
</noscript>
</body>

</html>
