Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Rails RESTful API開発における TDDのTIPS

Rails RESTful API開発における TDDのTIPS

Ryuya Ishibashi

January 21, 2023
Tweet

More Decks by Ryuya Ishibashi

Other Decks in Technology

Transcript

  1. Confidential 2 自己紹介 Ryuya Ishibashi SES企業にて大小様々なプロジェクトに参画 • Ruby on Rails

    / Vue.js / PHP / C# / .NET / Drupal / AWS / Azureなどの技術に触れてきまし た ↓ ENECHANE株式会社←いまここ • Railsエンジニアとして、電気料金シミュレーションの API開発を中心にお仕事しています (@RubyKaigi2022) Twitter / GitHub
  2. Confidential 3 そもそもRESTful APIとは? • RESTful API・・・「REST」原則を満たすAPIのこと • REST ・・REpresentational

    State Transfer      (具体的に状態を定義した情報のやり取り) 引用元:https://www.codecademy.com/article/what-is-rest
  3. Confidential 4 そもそもRESTful APIとは? • RESTの4原則 ◦ 統一インターフェース ▪ HTTPメソッド

    / JSON形式でやりとりするよ ◦ アドレス可読性 ▪ 一意なURIを持っているよ (https://enechange.jp/api/xxx) ◦ 接続性 ▪ 情報の内部に別の情報のリンクを含むよ ◦ ステートレス性 ▪ やりとりが1回ごとに完結するよ
  4. Confidential 5 RailsにおけるRESTful API • Ruby on RailsではRESTful APIが簡単に作れます! •

    Rails5からAPIモードに対応🎉 • どのくらい簡単かというと・・・ • APIモードで新しいアプリを作って rails new rails-api-tdd-sample --api • controller, model, routeを作って rails g scaffold user name:string • DBを作成 rails db:create db:migrate • たったこれだけで5つのエンドポイントが作成できます! ◦ CRUD (Create, Read, Update, Delete) + INDEX
  5. Confidential 6 RailsにおけるRESTful API • ・・・ということで、RailsでRESTful APIをどんどん作りましょう♪ • Rails RESTful

    APIの作成をTDDで行っていき、いくつかのTIPsを紹介し ていきます! • ゴール • 記事 (Article)のCreate機能をRESTful APIで提供する • TDDで上記を実装する • テストフレームワークはRSpecを利用します
  6. Confidential 9 TIPS 1 設計はテストに先立つ • 今回は以下のようにModelを定義します ◦ モデル:Article カラム名

    属性 必須 一意性 その他制約 説明 id BigInt ◯ ◯ Rails側で自動生成 title String ◯ ◯ 50文字以内 記事のタイトル content Text ◯ 記事の本文
  7. Confidential 11 TIPS 2 Modelのテストは網羅的に • ModelはUT的な側面が強いので、網羅性が大事です “Modelのテストは網羅的に” • まずはModelを作成しましょう

    rails g model article title:string content:text rails db:migrate • 上記の操作で以下も作成されます ◦ spec/models/article_spec.rb(Specファイル:テストコードを書く) ◦ spec/factories/articles.rb( Factoryファイル:テストデータを書く)
  8. Confidential 12 TIPS 2 Modelのテストは網羅的に • TDDなので当然テストから書いていきましょう! • まずは正常系でModelインスタンスが正しく作成できることを検証します ◦

    spec/models/article_spec.rb • Factoryで初期値があるため、上記は成功します require 'rails_helper' RSpec.describe Article, type: :model do let(:article) { build(:article) } describe "#validations" do it '正常なデータが作成できること ' do expect(article).to be_valid end end end
  9. Confidential 14 TIPS 2 Modelのテストは網羅的に • 必須チェック ◦ titleが空であれば不正なデータになる ◦

    titleが空ではダメな旨、エラー情報が追加される ▪ spec/models/article_spec.rb context "title" do it 'titleが空の場合、不正データとなる ' do article.title = '' expect(article).not_to be_valid expect(article.errors[:title]).to include("can't be blank") end end
  10. Confidential 16 TIPS 2 Modelのテストは網羅的に • Modelにバリデーションの記述を追加すると、 ◦ app/models/article.rb •

    テストに成功します🎉 class Article < ApplicationRecord validates :title, presence: true end
  11. Confidential 17 TIPS 2 Modelのテストは網羅的に • 同様に、その他のバリデーションのテストも追加していきます • 一意性 ◦

    titleが重複するデータは不正となる ◦ titleを一意なものに更新したら、正常データとなる ▪ spec/models/article_spec.rb it 'titleが一意でない場合、不正データとなる ' do article_1 = create(:article) expect(article_1).to be_valid article_2 = build(:article, title: article_1.title) expect(article_2).not_to be_valid expect(article_2.errors[:title]).to include("has already been taken") article_2.title = 'new title' expect(article_2).to be_valid end
  12. Confidential 19 TIPS 2 Modelのテストは網羅的に • Modelにバリデーションの記述を追加すると ◦ app/models/article.rb •

    (再び)テストに成功します🎉 class Article < ApplicationRecord validates :title, presence:true, uniqueness: true end
  13. Confidential 20 TIPS 2 Modelのテストは網羅的に • 同じ要領でModelの各項目の全ての制約に対して、 ◦ テストコード →

    テスト失敗 → 開発コード → テスト成功 を繰り返していきましょう ※ 今回はテーブル側の制約は割愛します • 最終的なソースコードはリポジトリをご覧ください! https://github.com/RyuyaIshibashi/rails-api-tdd-sample
  14. Confidential 22 TIPS 3 APIのIF設計は大事な約束 • Modelができたので、次はAPIのIF設計を行います • 特に自社開発ではAPIのIF設計はおざなりになりがちですが、 きちんとドキュメント化し、メンテしていきましょう

    “APIのIF設計は大事な約束” →そうすると、フロントエンドを専任のエンジニアが担当したり、 外部委託する際にスムーズにやりとりができるようになります • OpenAPIドキュメントなどを利用するといいでしょう https://www.openapis.org/ • RSpecからOpenAPIドキュメントを作成するgemもあります https://github.com/exoego/rspec-openapi https://github.com/rswag/rswag
  15. Confidential 23 TIPS 3 APIのIF設計は大事な約束 • Createのリクエストを以下のように定義します ◦ HTTP Request

    ◦ RequestBody 項目名 属性 必須 一意性 その他制約 説明 title String ◯ ◯ 50文字以内 記事のタイトル content Text ◯ 記事の本文 項目名 内容 URL #{domain}/articles Method Post RequestBody JSON
  16. Confidential 24 TIPS 3 APIのIF設計は大事な約束 • Createのレスポンスを以下のように定義します ◦ 成功時 ▪

    HTTP Response ▪ ResponseBody 項目名 属性 必須 説明 message String ◯ “SUCCESS” data Object ◯ 作成された記事情報 項目名 内容 Status 200 Success ResponseBody Json
  17. Confidential 25 TIPS 3 APIのIF設計は大事な約束 ◦ 失敗時 ▪ HTTP Response

    ※本来は401 Unauthorizedなど、多様なステータスコードが想定されます ▪ ResponseBody 項目名1 項目名2 属性 必須 説明 message String ◯ “FAILURE” detail Object ◯ エラー詳細 項目名 内容 Status 400 Bad Request ResponseBody Json
  18. Confidential 28 TIPS 4 コントローラのテストはステップバイステップに • ではまず、Controllerを作成しましょう rails g controller

    articles • 上記の操作で以下も作成されます ◦ spec/requests/articles_spec.rb(Specファイル:テストコードを書く) • 次に、routingの記述も行っておきます ◦ config/routes.rb Rails.application.routes.draw do resources :users resources :articles, only: [:create] end
  19. Confidential 29 TIPS 4 コントローラのテストはステップバイステップに • では早速、コントローラのテストを書いていきます ◦ 成功時 ▪

    まずはstatus code200が返ることをテストします • spec/requests/articles_spec.rb describe "POST /articles" do let (:params) do { title: 'text', content: 'content' } end subject do post "/articles", headers: { "Content-Type" => "application/json" }, params: params.to_json end context "正しいリクエストがあった場合 " do it "status code 200を返す" do subject expect(response.status).to eq(200) end end end
  20. Confidential 31 TIPS 4 コントローラのテストはステップバイステップに • 実装を書いて、 ◦ app/controllers/articles_controller.rb •

    テストを成功させます class ArticlesController < ApplicationController def create render status: 200, json: {} end end
  21. Confidential 32 TIPS 4 コントローラのテストはステップバイステップに • 次に正しくデータが作成されることをテストします ◦ spec/requests/articles_spec.rb •

    実装は以下 ▪ spec/requests/articles_spec.rb it "データが作成されること " do expect { subject }.to change{ Article.count }.by(1) created_article = Article.last expect(created_article.title).to eq params[:title] expect(created_article.content).to eq params[:content] end class ArticlesController < ApplicationController def create article = Article.new(article_params) article.save render status: 200, json: {} end private def article_params params.permit(:title, :content) end end
  22. Confidential 33 TIPS 4 コントローラのテストはステップバイステップに • 最後にレスポンスが期待通りになることをテストします ▪ spec/requests/articles_spec.rb •

    実装は以下 ▪ spec/requests/articles_spec.rb it "正しいレスポンスが返ること " do subject response_body = JSON.load(response.body) created_article = Article.last expect(response_body['message']).to eq "SUCCESS" expect(response_body['data']['id']).to eq created_article.id expect(response_body['data']['title']).to eq params[:title] expect(response_body['data']['content']).to eq params[:content] end def create article = Article.new(article_params) article.save render status: 200, json: { message: 'SUCCESS', data: article } end
  23. Confidential 35 TIPS 5 コントローラのテストは分岐を意識しよう • 次は異常系のテストを書いていきます • RequestBodyに様々な値を入れて、細かく400エラーを検証することは不要で す

    • なぜでしょうか? → Modelのバリデーションで既に網羅的にテストしているからです • そのため、コントローラのテストでは分岐の部分だけを意識すると、 重複が少ないスマートなテストが書けます
  24. Confidential 37 TIPS 5 コントローラのテストは分岐を意識しよう • では早速、テストコードを追加します ◦ spec/requests/articles_spec.rb context

    "誤ったリクエストがあった場合 " do let (:params) { { title: '', content: 'content' } } it "status code 400を返す" do subject expect(response.status).to eq(400) end it "正しいレスポンスが返ること " do subject response_body = JSON.load(response.body) expect(response_body['message']).to eq "FAILURE" expect(response_body['detail']).to eq ({ "title" => ["can't be blank"] }) end it "データが増減しないこと " do expect { subject }.to change{ Article.count }.by(0) end end
  25. Confidential 39 TIPS 5 コントローラのテストは分岐を意識しよう • 実装コードを追加し、 ◦ spec/requests/articles_spec.rb ◦

    • テストを成功させましょう(これが最後です) def create article = Article.new(article_params) if article.save render status: 200, json: { message: 'SUCCESS', data: article } else render status: 400, json: { message: 'FAILURE', detail: article.errors.messages } end
  26. Confidential 41 まとめ • 今日のTIPS TIPS 1 “設計はテストに先立つ” TIPS 2

    “Modelのテストは網羅的に” TIPS 3 “APIのIF設計は大事な約束” TIPS 4 “コントローラのテストはステップバイステップに” TIPS 5 “コントローラのテストは分岐を意識しよう”

  27. Copyright © ENECHANGE Ltd. All Rights Reserved. | 44 CHANGING

    ENERGY FOR A BETTER WORLD 私たちENECHANGEは、「エネルギー革命」を技術革新により推進し、 より良い世界を創出することをミッションとしています。 世界はいま、脱炭素社会の実現を求めています。 そのためには、「エネルギーの4D革命」による技術革新が必要不可欠です。 ENECHANGE、エネルギー(ENERGY)を変革する(CHANGE) そんな社名を名付けられたこの会社は、 エネルギー問題に人生をかけて取り組んでいきたい、 と思う多くの人々の想いが集結してつくられた会社です。 エネルギーの未来をつくる ミッション
  28. Copyright © ENECHANGE Ltd. All Rights Reserved. | 45 •

    EV充電エネチェンジ 駐車場オーナー向けEV充電インフラサービス エネルギー業界に特化したSaaS事業 事業領域 プラットフォーム事業 データ事業 EV充電サービス事業 Deregulation 自由化 Digitalization デジタル化 Decarbonization 脱炭素化 Decentralization 分散化 • 家庭向け電力・ガス切り替えプラットフォーム • 法人向け電力・ガス切り替え支援 • EMAP(デジタルマーケティング支援SaaS) • SMAP(スマートメーターデータ活用SaaS) エネチェンジクラウド
  29. Copyright © ENECHANGE Ltd. All Rights Reserved. | 46 技術スタック

    Frontend Mobile Backend Infura Analytics Other 開発スピードを大事に技術スタック選定をしています。 メジャーな技術スタックを採用しつつ、随時より新しい技術へのアップデートも進めています。