前にrspecというBDDのフレームワークについて解説しましたが
今回はBDDやTDDの進め方について実践していきます。
TDDは求められるテストをまず記述する。
BDDは求めらる振る舞いをまず記述する。
二つは主体とすべきものが異なりますが手順的にはかなり似ています。
では、実際のソースコードを交えながら解説。
今回は勤怠システムで使うような出勤時間が休憩時間中だったら
休憩時間後に調整するadjustStartTimeという関数を作ります。
1. まずは失敗するコードを書く
初めての人はこの工程に疑問を持つかもしれませんが、理由は次に。
[テストコード]
describe 'adjustStartTime Test' do
context 'exist breaktime 22:00-22:30' do
subject {adjustStartTime('22:05:00')}
it {should eq '22:30:00'}
end
end
22:00~22:30が休憩時間の場合に22:05が出勤時間ならば22:30に
なるはずというテストコードです。
ここに対して失敗する実コードを書く。
[実コード]
def adjustStartTime(starttime)
return ''
end
どんな引数を渡されても空文字を返す。
この状態でテストコードを動かし、エラーを確認したら
次のステップに移ります。
2. 正常となる仮の実装を行う。(仮実装)
def adjustStartTime(starttime)
return '22:30:00'
end
求められる値を固定で返しているので正常となります。
これも当たり前と思いますよね。
この1,2の確認により、テストコード自体が正常かどうかが分かります。
TDD(BDD)のフレームワークが正常に動いているか。
テスト対象の関数を呼べているか。等
3. テストコードの追加(三角測量)
他のテストケースも追加していきます。
[テストコード]
describe 'adjustStartTime Test' do
context 'exist breaktime 22:00-22:30' do
subject {adjustStartTime('22:05:00')}
it {should eq '22:30:00'}
end
context 'out of breaktime 22:00-22:30' do
subject {adjustStartTime('21:00:00')}
it {should eq '21:00:00'}
end
end
休憩時間22:00~22:30に対して21:00に出勤したケースです。
この場合は休憩時間外なので出勤時間の調整は行なわず
そのままの値が返るべきです。
しかし、今の実コードでは固定値で22:30と返しているだけなので
エラーとなります。
4.関数を動作するよう作っていく(明白な実装)
ここからは関数を仕様通りに作っていきます。
[実コード]
def adjustStartTime(starttime)
breaktime = BREAKTIME.where('start_time <= ? AND ? <= end_time', starttime, starttime).first
if breaktime then
return breaktime[:end_time]
else
return starttime
end
end
DBから出勤時間が含まれる休憩時間を取得してあった場合には
休憩時間の終わりを返す、無ければ出勤時間をそのまま返すようにしました。
テストコードを動かした所、エラーとなりました。
原因を探ると、戻ってくる値としては文字列を期待しているのに
DBから取得した時間は型がtime型になっていた為です。
そこで以下のように修正。
[実コード]
def adjustStartTime(starttime)
breaktime = BREAKTIME.where('start_time <= ? AND ? <= end_time', starttime, starttime).first
if breaktime then
return breaktime[:end_time].strftime("%H:%M:%S")
else
return starttime
end
end
無事にテストコードもを動かしてもエラーが出なくなりました。
5. リファクタリング
ここまでで正常に動作するものは作れたのですが
可読性、保守性を高める為にリファクタリングをします。
ここではDB検索部分のプレースホルダを?とするより
名前付きプレースホルダを使い分かりやすくしました。
[実コード]
def adjustStartTime(starttime)
breaktime = BREAKTIME.where('start_time <= :starttime AND :starttime <= end_time', {starttime: starttime}).first
if breaktime then
return breaktime[:end_time].strftime("%H:%M:%S")
else
return starttime
end
end
最終的にこんな形になりました。
テストコードを動かす事でリファクタリング時のミスもすぐさま見つかります。
TDD/BDDまとめ
TDD/BDDは期待するテスト(振る舞い)を最初に書き、実装していく手法です。
面倒に感じるかもしれませんが、この行為を行っていくことで
実装したコードが正常に動作するものか着実に確認していくことができます。
TDD/BDD以外の方法として似たものにデバッガやアラートを使うものが
ありますが、一つのテストケースごとしかできない、挙動が変わってしまう
等の問題があります。
テストの理想形は1行変更したら、すぐテストする事です。
正常に動作していたものが1行変更してエラーが出れば
すぐさまその行が問題と分かります。
もし、数十行、数百行変更した後にテストするならば問題がある行を
特定するだけで多くの時間がかかるでしょう。
TDD/BDDにより上記の理想に近いテストが行えます。
また、最初からテストコードを書くことで、テストしやすいコードを書けます。
テストがしやすいコードというのは再利用しやすく、分かりやすいコードです。
是非ともマスターしてみて下さい。