はじめに
Kent Beck氏の名著『テスト駆動開発(Test-Driven Development: By Example)』を年末年始休暇を使って読みました
非常に興味深い内容でしたが使用されている言語がJavaで少し馴染みが薄いものでした…
例示されていたコードを日々使用しているRubyで復習も兼ねて写経してみます
環境
Railsは使わず、素のRubyとRSpecだけで進めています。
- ruby 3.4.7(3以降なら大丈夫なはず…)
- RSpec
- RuboCop(Linter)
- Ruby LSP(VScode拡張)
ソースはGitHub のリポジトリとして随時上げていく予定です
ディレクトリ構成
以下のような構成で進めていきます
テスト対象のコードはlib以下に配置し、.rspec に -Ilib を追記し、require 時にパスを気にしなくて良いように設定しました。
.
├── lib/
│ └── dollar.rb
├── spec/
│ ├── dollar_spec.rb
│ └── spec_helper.rb
├── .rspec
├── .rubocop.yml
├── .vscode
│ └──settings.json
├── Gemfile
└── Gemfile.lock
TDDのサイクル
この書籍では、以下の 「Red・Green・Refactor」 と呼ばれるサイクルを極めて短いスパンで繰り返すことで開発を進めます。
-
Red(失敗):
実装したい機能のテストを一つ書く
実行して、期待通りに失敗することを確認する
目的:これから何を作るか(インターフェース)を決定する -
Green(成功):
テストを通すための最小限のコードを書く
ここでは「罪悪感を覚えるような汚いコード(コピー&ペーストや固定値の返却)」でも構わない
目的:現在の機能が動いている状態を最速で作る -
Refactor(リファクタリング):
テストが通っている状態を維持したまま、コードの重複を除去し、設計をきれいにする
目的:コードを保守可能な状態にする
第1部 多国籍通貨
作成するコードについて
今回の題材は、異なる通貨(ドルやスイスフランなど)を扱う株式レポート機能です。
例えば、以下のような計算を可能にすることを最終ゴールとしています。
株価($5)× 株の数(2)= \$10
株価($5)+ 為替差益(10 CHF)= \$10(レートが 2:1 の場合)
この要件を満たすため、書籍にならって「ToDoリスト」を作成し、一つずつ潰していくスタイルで進めます。
【現在のToDoリスト】
- \$5 + 10 CHF = $10 (レートが2:1の場合)
- \$5 * 2 = \$10 ← 今からここに着手
- amountをprivateにする
- Dollarの副作用どうする?
- Moneyの丸め処理どうする?
まずは、最もシンプルそうな「ドルの掛け算($5 * 2 = $10)」から着手します。
第1章 仮実装
最初の課題は、通貨の掛け算です。
本章では$5 * 2 = $10 を実行できるように仮実装します
1. 失敗するテストを書く (Red)
以下のようにテストを書きます。
この時点ではlib/dollar.rb のファイルがないため失敗するはずです。
# frozen_string_literal: true
# spec/dollar_spec.rb
require 'dollar'
RSpec.describe 'Dollar' do
it 'multiplication' do
five = Dollar.new(5)
five.times(2)
expect(five.amount).to eq 10
end
end
2. 最短で通す (Green)
「仮実装」として、まずはテストを通すことだけに集中します。
- Dollarクラスを定義
- コンストラクタ定義を追加
- timesメソッドを定義
# lib/dollar.rb
# frozen_string_literal: true
class Dollar
attr_reader :amount
def initialize(amount)
@amount = amount
end
def times(multiplier)
# テストを通すためだけに、一旦「5 × 2 = 10」を返す(罪悪感を覚える実装)
@amount = 5 * 2
end
end
これで bundle exec rspec を実行し、無事に Green になりました。
しかし、今はただ期待値をそのまま返すだけなのでtimesメソッドを修正します
3. times メソッドの修正(Refactor)
ハードコーディングしていた部分(5 * 2)を、一般化した計算式に置き換えます。
# frozen_string_literal: true
class Dollar
attr_reader :amount
def initialize(amount)
@amount = amount
end
def times(multiplier)
@amount *= multiplier
end
end
これでToDoリストの1つは解決しました!
【現在のToDoリスト】
- \$5 + 10 CHF = $10 (レートが2:1の場合)
- \$5 * 2 = \$10
- amountをprivateにする
- Dollarの副作用どうする?
- Moneyの丸め処理どうする?
次回記事