はじめに
株式会社Fusic所属のエンジニアです。記事執筆は人生で2本目。1本目は去年のアドベントカレンダーです()
お手柔らかによろしくお願いします。
前提技術
- Ruby on Rails
- テストフレームワークはRspec
- factory_bot
ざっくりTDDの定義
- テスト(itの文章)から先に書く
- 「一旦テスト落とす → テスト通るまで実装する → 次のテスト行く前にリファクタする」で一つずつテストを通していく
ぐらいのざっくりルールで自分はやってます。
こんな雑にTDDを名乗ると怒られそう。
ざっくりでもTDDを行うメリット
結論から言いますと、実装する前にきちんと仕様を整理してコードを書く習慣が自然とできるからです。
まだ実践し始めて4ヶ月ほどですが、筆者の持ってた課題感が解消されている実感があります。
筆者の持ってた課題
自分は実装の詳細を詰める途中で「まあ、とりあえず書くか」が手癖になっていて、行き当たりばったりな実装が多かったんですね。
会社の先輩方には
- やり直しって意外に時間がかかるよ〜
- 先に考えをまとめてから実装した方が結果早いよ〜
とアドバイスを受けてましたが、なかなか悪癖が治りませんでした。
気づいたら実装を進めてしまい、プッシュ前にテストコードを書き忘れたことに気づき、タブ連打(Copilot)でテストコードを自動生成し、案の定コード修正の依頼を受ける日々。辛いですね。
同じ課題感を持ってる方がいれば読んでください。
ざっくりTDDの始め方
これも結論から書くとシンプルで、実装で実現したいことをテストの説明文という形で箇条書きするだけです。
メモ帳やMarkdown、手書きのノートに箇条書きするのをそのままテストファイルに書くだけ。簡単そう。
ここからは具体例がないとしんどいので、実装したい機能例を出します。
具体例
例えばCSVデータを顧客テーブルに登録する処理が必要だとします。
クラス名はひとまずRegisterCustomersFromCsv
としておいて、まず始めに_spec.rb
ファイルを作りましょう。一旦/services
にファイルは置くとします。
(ここはクラスの役割によってどこに置くかは変わります)
1 | require 'rails_helper'
2 | require 'csv'
3 |
4 | RSpec.describe RegisterCustomersFromCsv do
5 |
6 | end
実装コードのファイルも作っときます
1 | class RegisterCustomersFromCsv
2 | def initialize
3 | end
4 |
5 | def call
6 | end
7 | end
ファイルだけ先にできたので、ここで仕様を整理します。
仕様の整理
(例
- 受け取ったCSVデータを元に、顧客テーブルにデータが保存される
- CSVのデータが1件もない時、何も保存されずエラーメッセージを返す
これに加えて、以下のような仕様があったとします
- CSVのヘッダーが、一つでも顧客テーブルのカラムと異なる場合、例外
- 顧客が有効な郵便番号でない場合、例外
- 顧客の登録情報にメールアドレスが重複する場合、例外
これをそのままテストケースとしてregister_customers_from_csv_spec.rbに書いていきます
自分は直接itの説明に書き込みながら整理してます。
1 | require 'rails_helper'
2 | require 'csv'
3 |
4 | RSpec.describe RegisterCustomersFromCsv do
5 | subject(csv_data) { described_class.new.call(csv_data:) }
6 |
7 | def generate_csv_data
8 | # CSVデータを生成する処理
9 | end
10 |
11 | it '受け取ったCSVデータを元に、顧客テーブルにデータが保存される' do
12 | expect { subject(generate_csv_data) }.to change(Customer, :count).by(1)
13 | end
14 |
15 | it 'CSVデータが1件もない時、何も保存されずエラーメッセージを返す' do
16 | expect { subject(generate_csv_data) }.to raise_error(RegisterCustomersFromCsv::NoCsvDataError)
17 | end
18 |
19 | it 'CSVのヘッダーが、一つでも顧客テーブルのカラムと異なる場合、例外を発生させる' do
20 | expect { subject(generate_csv_data) }.to raise_error(RegisterCustomersFromCsv::InvalidCsvHeaderError)
21 | end
22 |
23 | it '顧客が有効な郵便番号でない場合、例外を発生させる' do
24 | expect { subject(generate_csv_data) }.to raise_error(RegisterCustomersFromCsv::InvalidPostalCodeError)
25 | end
26 |
27 | it '顧客の登録情報にメールアドレスが重複する場合、例外を発生させる' do
28 | expect { subject(generate_csv_data) }.to raise_error(RegisterCustomersFromCsv::DuplicatedEmailError)
29 | end
30 | end
とりあえずで手を動かしてるのは変わってないんですが、自然と先に仕様の整理が終わっていてスムーズに実装に入れるんですよね〜
で、ここまでできたらあとは行数指定してテストを実行→実装しながら、一個ずつテストを通してくだけです。
$ bundle exec rspec ./spec/services/register_customers_from_csv_spec.rb:11
ここまでのテストを通す実装の途中で「あ、これも考慮する必要があるな」みたいな仕様が生えてきたら、テストから追加しましょう(自戒)
まとめ
実装が早くなる、とか設計が上手くなる、とかTDDはメリットが色々あるらしいですが、プログラミングが前より楽しくなったというのが自分の中で大きいです。
自分が考えたクエストを上からクリアしていくみたいで楽しいので、ぜひやってみてください。
誤字脱字や間違いなどあればコメントをお願いします。