1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FusicAdvent Calendar 2024

Day 13

【初心者】ひとまずここからざっくりTDD

Last updated at Posted at 2024-12-12

はじめに

株式会社Fusic所属のエンジニアです。記事執筆は人生で2本目。1本目は去年のアドベントカレンダーです()
お手柔らかによろしくお願いします。

前提技術

  • Ruby on Rails
  • テストフレームワークはRspec
    • factory_bot

ざっくりTDDの定義

  • テスト(itの文章)から先に書く
  • 「一旦テスト落とす → テスト通るまで実装する → 次のテスト行く前にリファクタする」で一つずつテストを通していく

ぐらいのざっくりルールで自分はやってます。
こんな雑にTDDを名乗ると怒られそう。

ざっくりでもTDDを行うメリット

結論から言いますと、実装する前にきちんと仕様を整理してコードを書く習慣が自然とできるからです。
まだ実践し始めて4ヶ月ほどですが、筆者の持ってた課題感が解消されている実感があります。

筆者の持ってた課題

自分は実装の詳細を詰める途中で「まあ、とりあえず書くか」が手癖になっていて、行き当たりばったりな実装が多かったんですね。

会社の先輩方には

  • やり直しって意外に時間がかかるよ〜
  • 先に考えをまとめてから実装した方が結果早いよ〜

とアドバイスを受けてましたが、なかなか悪癖が治りませんでした。

気づいたら実装を進めてしまい、プッシュ前にテストコードを書き忘れたことに気づき、タブ連打(Copilot)でテストコードを自動生成し、案の定コード修正の依頼を受ける日々。辛いですね。

同じ課題感を持ってる方がいれば読んでください。

ざっくりTDDの始め方

これも結論から書くとシンプルで、実装で実現したいことをテストの説明文という形で箇条書きするだけです。

メモ帳やMarkdown、手書きのノートに箇条書きするのをそのままテストファイルに書くだけ。簡単そう。

ここからは具体例がないとしんどいので、実装したい機能例を出します。

具体例

例えばCSVデータを顧客テーブルに登録する処理が必要だとします。

クラス名はひとまずRegisterCustomersFromCsvとしておいて、まず始めに_spec.rbファイルを作りましょう。一旦/servicesにファイルは置くとします。
(ここはクラスの役割によってどこに置くかは変わります)

spec/services/register_customers_from_csv_spec.rb
1 | require 'rails_helper'
2 | require 'csv'
3 |
4 | RSpec.describe RegisterCustomersFromCsv do
5 |
6 | end

実装コードのファイルも作っときます

app/services/register_customers_from_csv_spec.rb
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の説明に書き込みながら整理してます。

spec/services/register_customers_from_csv_spec.rb
 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はメリットが色々あるらしいですが、プログラミングが前より楽しくなったというのが自分の中で大きいです。

自分が考えたクエストを上からクリアしていくみたいで楽しいので、ぜひやってみてください。
誤字脱字や間違いなどあればコメントをお願いします。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?