2011年05月
2011年05月29日
私は普段は作りたいものを作ってるのだが、今日については溜まっているタスクが多かったので、その作業をはかどらせるために参加した。
成果発表の資料はこちら(PDF 316KB)。ネタプレゼンなのでslideshareには上げてません。

2011年05月21日
前日夜に@ayako119さんに誘われたので参加。
これはTDD(Test-Driven Development、テスト駆動開発)を実習形式で学ぶ会で、札幌での開催はこれが2回目である。私は初参加。
テスト駆動開発というのは簡単に言うと、「実際に必要なプログラムに求められる仕様をコード(単体テスト)として先に書き、それに対応した実装のみを行う、というのを細かく繰り返す」ことで、仕様に沿ったプログラムを書き上げていく、ソフトウェア開発の一手法である。
より詳しい解説は
連載:[動画で解説]和田卓人の“テスト駆動開発”講座|gihyo.jp … 技術評論社
をご覧下さい。
以下では、テスト駆動開発の基本をご存知の方向けに話を進める。
今まで、私は勉強会などでテスト駆動開発について話を聞いており、独学で実践していた。
ただ、単純なロジックに対するテストコードは難なく書けるようになってきたのだが、困っていたこととして、「コードとして書くことが難しい」仕様についてどう対処するか、という実践的な知識が足りてないと感じていたのである。
(ここで「プログラムのコードとして書くことが難しい」仕様として代表的なケースは、その仕様が「CPU利用率」や「現在時刻」など、「動作しているコンピュータの状態によって決まり、かつ自力での制御が難しい」ものに依存している場合である。)
今回のTDD Bootcampで出たこれに対する答えは、「そのテストしにくい部分だけ切り分ける」というものであった。
例として、Ruby向けの単体テストツール「RSpec」を用いて、「MyTimerクラスのインスタンスは、自身が生成された時刻を保持している」という仕様をテストとして書くことを考える。単純に進めれば
# テストコード
require "./mytimer"
describe MyTimer do
it "should contain its own created time" do
mt = MyTimer.new
# 以下が仕様(JUnitの「assertEquals」に相当する部分)。
mt.created_time.should == Time.now
end
end
とテストコードを書き、その後実際のコードを書き進めて最終的に
# 実際のコード
class MyTimer
def initialize # Rubyのコンストラクタは「initialize」というメソッドに記述する
@created_time = Time.now
end
attr_reader :created_time
end
となるであろう。
ただこれは問題があって、インスタンス生成時と実際のテスト時に時刻の秒の値が変わったときだけテストが「失敗」と判断され、それ以外の場合は「成功」と判断されてしまう。
これに対する対処法として、今回は「テストしにくい部分を切り分け、テストの際のみ別のものに差し替える」という方法が述べられていた。まず実際のコードにて、時刻を取得する部分を切り分ける。
# 実際のコード
class MyTimer
def initialize
@created_time = time_now
end
attr_reader :created_time
# 時刻を取得する部分だけ切り分け
def time_now
Time.now
end
end
そしてテストコードを実行する際には、この切り分けた部分だけ差し替えて実行する。例えばRubyなら
# テストコード
require "./mytimer"
describe MyTimer do
it "should contain its own created time" do
$current_time = Time.now # 本当はグローバル変数なんて使いたくないんだけど
# ここで、MyTimerを複製してテスト専用のクラスを作る
testclass_mytimer = MyTimer.dup
# そしてテスト専用のクラスのみ、さっき切り分けた部分を差し替える
testclass_mytimer.class_eval{ def time_now; $current_time; end }
mt = testclass_mytimer.new
mt.created_time.should == $current_time
$current_time = nil # 後始末
end
end
とする。
あと他に興味深かったのは、テストコードを「SetUp」「Exercise」「Verify」「TearDown」の4つに区分する、というものであった。これはテストコードを読みやすくする(=後で読んだり、他の人が読んでも分かりやすくする)ためのものであって、さっきの例でいえば
# テストコード
require "./mytimer"
describe MyTimer do
it "should contain its own created time" do
# ----- setup(下準備をする)
$current_time = Time.now
testclass_mytimer = MyTimer.dup
testclass_mytimer.class_eval{ def time_now; $current_time; end }
# ----- exercise(実際に挙動を確かめたい処理を実行する)
mt = testclass_mytimer.new # 今回は「インスタンスを生成したとき」の挙動を確かめたいので
# ----- verify(結果が期待されていたものであるか確かめる)
mt.created_time.should == $current_time
# ----- teardown(必要ならば、テストに使ったオブジェクト等の後始末をする)
$current_time = nil
end
end
のように切り分けることになる。
今後の自分のためになった一日になったとともに、今後も単体テストを積極的に活用しようという気持ちになれた。企画の@shuji_w6eさん、その他参加者の皆様、ありがとうございました。
【宣伝】
きたる6/11(土)のオープンソースカンファレンス2011 Hokkaidoにて、「札幌C++勉強会」として出展予定です。私はC++における単体テストツール「CppUnit」について記事を書いています。
2011年05月19日
ちょっと理由があって必要になったので。
オブジェクト指向言語においては、一度クラスを定義すると、そのクラスのインスタンスは複数生成できることが多い。
しかし場合によっては、クラスが持つインスタンスを一つに限定したい(「Singletonパターン」と呼ばれる)場合もある。
Pythonでもこれは可能と言えば可能なのだが、多くのサンプルコードは「クラスをSingletonになるように定義する」方法であるため、Singletonなクラスをいくつも利用する場合は不便だし、Singletonにするための定義を何度も書くのはDRYでない。
例えば
class MyClass(Singleton):のように定義するだけでSingletonなクラスが定義できれば便利である。
でぐぐってみると、こんな記事が見つかった。
Pythonでシングルトンを実装するには? - Mixnuts@BetaNews
ただこの方法だと、例えば
class MyClass(Singleton):
def __init__(self):
# 初期化処理
print "初期化してます"
のようにした場合に、
a = MyClass() # こっちで "初期化してます" が表示されるのはよいのだが b = MyClass() # こっちでも "初期化してます" が表示されてしまう!インスタンスは1つなのに!
という問題があった。
一思案してみたのだが、Singletonクラスをどう定義しようとも、MyClassの初期化処理を「__init__」というメソッドにさせている限りは解決しないことが判明し、
class MyClass(Singleton):
def singleton_init(self):
# 初期化処理
print "初期化してます"
というAPIにすることで解決した。コードはこちら↓
