Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Confidential 2 自己紹介 Ryuya Ishibashi SES企業にて大小様々なプロジェクトに参画 ● Ruby on Rails / Vue.js / PHP / C# / .NET / Drupal / AWS / Azureなどの技術に触れてきまし た ↓ ENECHANE株式会社←いまここ ● Railsエンジニアとして、電気料金シミュレーションの API開発を中心にお仕事しています (@RubyKaigi2022) Twitter / GitHub

Slide 3

Slide 3 text

Confidential 3 そもそもRESTful APIとは? ● RESTful API・・・「REST」原則を満たすAPIのこと ● REST ・・REpresentational State Transfer      (具体的に状態を定義した情報のやり取り) 引用元:https://www.codecademy.com/article/what-is-rest

Slide 4

Slide 4 text

Confidential 4 そもそもRESTful APIとは? ● RESTの4原則 ○ 統一インターフェース ■ HTTPメソッド / JSON形式でやりとりするよ ○ アドレス可読性 ■ 一意なURIを持っているよ (https://enechange.jp/api/xxx) ○ 接続性 ■ 情報の内部に別の情報のリンクを含むよ ○ ステートレス性 ■ やりとりが1回ごとに完結するよ

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Confidential 6 RailsにおけるRESTful API ● ・・・ということで、RailsでRESTful APIをどんどん作りましょう♪ ● Rails RESTful APIの作成をTDDで行っていき、いくつかのTIPsを紹介し ていきます! ● ゴール ● 記事 (Article)のCreate機能をRESTful APIで提供する ● TDDで上記を実装する ● テストフレームワークはRSpecを利用します

Slide 7

Slide 7 text

Confidential 7 TIPS 1 設計はテストに先立つ

Slide 8

Slide 8 text

Confidential 8 TIPS 1 設計はテストに先立つ ● 設計がなければテストを書くことはできません “設計はテストに先立つ”
 ● 一般的には、RDBのテーブル=Model設計が最初に行われるはずです

Slide 9

Slide 9 text

Confidential 9 TIPS 1 設計はテストに先立つ ● 今回は以下のようにModelを定義します ○ モデル:Article カラム名 属性 必須 一意性 その他制約 説明 id BigInt ◯ ◯ Rails側で自動生成 title String ◯ ◯ 50文字以内 記事のタイトル content Text ◯ 記事の本文

Slide 10

Slide 10 text

Confidential 10 TIPS 2 Modelのテストは網羅的に

Slide 11

Slide 11 text

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ファイル:テストデータを書く)

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Confidential 13 TIPS 2 Modelのテストは網羅的に ● 続いて、titleカラムの失敗するテストを書いていきましょう! カラム名 属性 必須 一意性 その他制約 メモ title String ◯ ◯ 50文字以内

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Confidential 15 TIPS 2 Modelのテストは網羅的に ● 実装がないので、テストは当然失敗します!

Slide 16

Slide 16 text

Confidential 16 TIPS 2 Modelのテストは網羅的に ● Modelにバリデーションの記述を追加すると、 ○ app/models/article.rb ● テストに成功します🎉 class Article < ApplicationRecord validates :title, presence: true end

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Confidential 18 TIPS 2 Modelのテストは網羅的に ● (再び)実装がないので、テストは当然失敗します!

Slide 19

Slide 19 text

Confidential 19 TIPS 2 Modelのテストは網羅的に ● Modelにバリデーションの記述を追加すると ○ app/models/article.rb ● (再び)テストに成功します🎉 class Article < ApplicationRecord validates :title, presence:true, uniqueness: true end

Slide 20

Slide 20 text

Confidential 20 TIPS 2 Modelのテストは網羅的に ● 同じ要領でModelの各項目の全ての制約に対して、 ○ テストコード → テスト失敗 → 開発コード → テスト成功 を繰り返していきましょう ※ 今回はテーブル側の制約は割愛します ● 最終的なソースコードはリポジトリをご覧ください! https://github.com/RyuyaIshibashi/rails-api-tdd-sample

Slide 21

Slide 21 text

Confidential 21 TIPS 3 APIのIF設計は大事な約束

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Confidential 23 TIPS 3 APIのIF設計は大事な約束 ● Createのリクエストを以下のように定義します ○ HTTP Request ○ RequestBody 項目名 属性 必須 一意性 その他制約 説明 title String ◯ ◯ 50文字以内 記事のタイトル content Text ◯ 記事の本文 項目名 内容 URL #{domain}/articles Method Post RequestBody JSON

Slide 24

Slide 24 text

Confidential 24 TIPS 3 APIのIF設計は大事な約束 ● Createのレスポンスを以下のように定義します ○ 成功時 ■ HTTP Response ■ ResponseBody 項目名 属性 必須 説明 message String ◯ “SUCCESS” data Object ◯ 作成された記事情報 項目名 内容 Status 200 Success ResponseBody Json

Slide 25

Slide 25 text

Confidential 25 TIPS 3 APIのIF設計は大事な約束 ○ 失敗時 ■ HTTP Response ※本来は401 Unauthorizedなど、多様なステータスコードが想定されます ■ ResponseBody 項目名1 項目名2 属性 必須 説明 message String ◯ “FAILURE” detail Object ◯ エラー詳細 項目名 内容 Status 400 Bad Request ResponseBody Json

Slide 26

Slide 26 text

Confidential 26 TIPS 4 コントローラのテストはステップバイステップに

Slide 27

Slide 27 text

Confidential 27 TIPS 4 コントローラのテストはステップバイステップに ● コントローラのテストは、処理の実態を記載する箇所なので、 TDDのプロセスをステップバイステップに進めていくのに向いています ○ テストコード → テスト失敗 → 開発コード → テスト成功 “コントローラのテストはステップバイステップに”


Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Confidential 30 TIPS 4 コントローラのテストはステップバイステップに ● 実装がないのでテストは失敗します

Slide 31

Slide 31 text

Confidential 31 TIPS 4 コントローラのテストはステップバイステップに ● 実装を書いて、 ○ app/controllers/articles_controller.rb ● テストを成功させます class ArticlesController < ApplicationController def create render status: 200, json: {} end end

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Confidential 34 TIPS 5 コントローラのテストは分岐を意識しよう

Slide 35

Slide 35 text

Confidential 35 TIPS 5 コントローラのテストは分岐を意識しよう ● 次は異常系のテストを書いていきます ● RequestBodyに様々な値を入れて、細かく400エラーを検証することは不要で す ● なぜでしょうか? → Modelのバリデーションで既に網羅的にテストしているからです ● そのため、コントローラのテストでは分岐の部分だけを意識すると、 重複が少ないスマートなテストが書けます

Slide 36

Slide 36 text

Confidential 36 TIPS 5 コントローラのテストは分岐を意識しよう ● テスト用語を使うと以下のようになります ○ Model: 条件網羅 ○ Controller: 分岐網羅 “コントローラのテストは分岐を意識しよう”


Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Confidential 38 TIPS 5 コントローラのテストは分岐を意識しよう ● 実装がないのでテストは失敗します (データが増減しないことのテストはsaveに失敗するので通ります)

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Confidential 40 まとめ

Slide 41

Slide 41 text

Confidential 41 まとめ ● 今日のTIPS TIPS 1 “設計はテストに先立つ” TIPS 2 “Modelのテストは網羅的に” TIPS 3 “APIのIF設計は大事な約束” TIPS 4 “コントローラのテストはステップバイステップに” TIPS 5 “コントローラのテストは分岐を意識しよう”


Slide 42

Slide 42 text

Confidential 42 まとめ ● 資料 ○ プレゼンテーション (SpearkerDeck) ○ サンプルコードのリポジトリ

Slide 43

Slide 43 text

Confidential 43 ENECHNANGEのご紹介

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

Confidential 47 ENECHNANGEのご紹介 ● より詳しくは、以下をご覧ください! ■ エントランスBook for エンジニア

Slide 48

Slide 48 text

Confidential © ENECHANGE Ltd.