Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

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

Avatar for Ryuya Ishibashi

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 開発スピードを大事に技術スタック選定をしています。 メジャーな技術スタックを採用しつつ、随時より新しい技術へのアップデートも進めています。