行動駆動開発の3つは,テスト駆動開発から
5634 ワード
TDDの由来
テストドライバ開発(Test Driven Development,TDD)の考え方は限界プログラミング(Extreme Programming,XP)から来ている.XPは1999年に始まり、テストを先取りすることをコンセプトとしています.ツールのサポートが不足しているため、XPは最初は暖かくなく、Junitが登場するまで、XPは広く普及した.噂によると、JunitはKent BeckとEric Gammaの2人の大牛が飛行機の中で退屈になって遊びを書いて作ったという.03年になると、XPのテストはTDDに優先的に進化しました.
ruby言語でのTDDの例:
x,yで座標を初期化し、異常値をエラーする点を実現したい.テスト:
class TC_Point < Test::Unit::TestCase
@@valid_points = [[1, 2], [0, 0]]
@@invalid_points = [[nil, 3], [3, nil], [1, -2], [-1, 2], [1.5, 2], [35, 5.66778]]
def test_valid_point
@@valid_points.each do |point|
p = Point.new(point[0], point[1])
assert(p.row == point[0])
assert(p.column == point[1])
end
end
def test_invalid_point
@@invalid_points.each do |point|
assert_raise RuntimeError do
p = Point.new(point[0], point[1])
end
end
end
end
このコードは実行後、Pointというクラスの書き込みを実現することができます.
class Point
attr_reader :row, :column
def initialize(row, column)
if !row.is_a?(Integer) or !column.is_a?(Integer)
raise "row #{row} and column #{column} must be integer"
end
if row<0 or column<0
raise "row #{row} and column #{column} must be >= 0"
end
@row = row
@column = column
end
end
テストを実行します.テストに合格する.その後、Pointというコードを再構築します.再構成,すなわち,既存のコード挙動を一定に保ち,コードの可読性,独立性などを改善する.改善されたPointクラスがテストに合格できる限り、すなわち、再構築は従来の機能に影響を及ぼさない.
TDDの進化
JUnitの後、続々と、様々な言語にも独自のUnit Test Frameworkがあり、一時TDDツールは雨後のタケノコのように、地面を抜いた.しかし、風の多くの人たちと道具を熟練している間に、より敏捷な先輩たちを求めて前進し続けた.TDDの次のステップはどこですか?より良い分離コード(Working Effectively with legacy code)、より良いテストフレームワーク(Xunit Test Patterns)、これらはすべて花を添えています.しかし、雪の中で炭を送る道はどこにあるのだろうか.先輩たちは頭を絞って、二つの道を考え出した.
BDDの由来
行動駆動開発(Behavior Driven development,BDD)は,第2のアイデアから進化したものである.上記の例を続けて、私のテストコードをもっと分かりやすくしたいと思っています.私はこのような方法を使用することができます.def test_valid_point ->
def test_point_should_support_set_integer_to_x_and_y
def test_invalid_point ->
def test_point_should_raise_error_when_set_none_integer_to_x_and_y
RubyのBDDのツールRspecを使用する場合、テストコードは自然言語で次のように記述できます.describe "A new point" do
it "should raise exception when set none integer as x or y" do
none_integers = [[nil, 3], [3, nil], [1, -2], [-2, 1], [1.5, 2], [2, 1.5]]
none_integers.each do |ni|
Point.new(ni[0], ni[1]).should raise_error()
end
end
it "should accept when set integer as x and y" do
integers = [[1, 2], [0, 0]]
integers.each do |i|
p = Point.new(i[0], i[1])
p.x.should == i[0]
p.y.should == i[1]
end
end
end
Rspec後、他のプログラミング言語も次々と真似され、一連のSpecツールは雨後のタケノコのようだ.しかし、より敏捷な先輩たちを追うには、足を止めなかった.Rspecは依然としてユニットテスト層、すなわちdescribe/it/doの中にとどまっているからだ.コードだ.スペックに自然言語を導入するため、先輩たちはその上でCucumberを開発した.
CucumberはもともとRspecの新しい表現として開発されたが,その文法Given/When/thenの強さにより,独創的である.必要性、システム設計、モジュール設計など、すべての動作を記述できます.すなわち、自動検収性テスト、自動化システムテスト、自動化統合テストのスクリプトとして使用できます.これでJunitがXP/TDDに春をもたらしたようにCucumberが来て、BDDの春も来ました.Cucumberを使用すると、上記の例は、Scenario: valid pairs
Given a pair of integers "<x>" and "<y>"
When I initialize a Point with it
Then a point should be generated
Examples:
|x | y |
|3 | 1|
|0 | 0|
Scenario: invalid pairs
Given a pair of none integers "<x>" and "<y>"
When I initialize a Point with it
Then a point should raise exception
Examples:
|x |y |
|nil |3 |
|3 |nil |
|0.1|1 |
|1 |0.1|
Cucumberができてから、BDDの定義はやっと矢を放つことができます.
def test_valid_point ->
def test_point_should_support_set_integer_to_x_and_y
def test_invalid_point ->
def test_point_should_raise_error_when_set_none_integer_to_x_and_y
describe "A new point" do
it "should raise exception when set none integer as x or y" do
none_integers = [[nil, 3], [3, nil], [1, -2], [-2, 1], [1.5, 2], [2, 1.5]]
none_integers.each do |ni|
Point.new(ni[0], ni[1]).should raise_error()
end
end
it "should accept when set integer as x and y" do
integers = [[1, 2], [0, 0]]
integers.each do |i|
p = Point.new(i[0], i[1])
p.x.should == i[0]
p.y.should == i[1]
end
end
end
Scenario: valid pairs
Given a pair of integers "<x>" and "<y>"
When I initialize a Point with it
Then a point should be generated
Examples:
|x | y |
|3 | 1|
|0 | 0|
Scenario: invalid pairs
Given a pair of none integers "<x>" and "<y>"
When I initialize a Point with it
Then a point should raise exception
Examples:
|x |y |
|nil |3 |
|3 |nil |
|0.1|1 |
|1 |0.1|