ほげほげパッチ

有る事無い事 徒然なるままに

単体テストコード #3 (コントローラーのテスト)

テスト方針

modelのテスト: インスタンス生成 -> validチェック (データベースとのやりとりを確認)

controller: リクエストを送ってみて -> レスポンスをチェック (クライアントとのやりとりを確認)

Request Spec

RSpec内のコントローラー用のテストの手法

% rails g rspec:request tweets

spec/requests/tweets_spec.rb生成

createメソッド (≒ build)

build: インスタンス生成のみ
create: 1回DBに保存。(からの毎回ロールバック。多用すると重くなるらしい。)

getメソッド

テスト内でリクエストを生成

(直後にbinding.pryしてみたが、すぐにレスポンスまで行われるっぽい)

request
get root_path
binding.pry

# requestでリクエストの中身を確認
[1] pry > request
=> 
<ActionDispatch::Request GET "http://www.example.com/" for 127.0.0.1>


response
# response でレスポンスを確認
[2] pry > response
=>
 @cache_control={:max_age=>"0", :private=>true, :must_revalidate=>true},
 @committed=false,
 @cv=#<MonitorMixin::ConditionVariable:0x000000011587ae68 @cond=#<Thread::ConditionVariable:0x000000011587ae40>, @monitor=#<Monitor:0x000000011587b020>>,
 @header=
  {"X-Frame-Options"=>"SAMEORIGIN", ...(以下省略)


status
# response.status でステータスを確認。200が正常値。
[3] pry > response.status
=> 200
# 良く目にする「404ページが見つかりません」は、このHTTPステータスのこと(らしい)。


body
# response.body でHTML情報を(全部)確認
[4] pry > response.body
=>
 "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Pictweet</title>\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n    \n    \n\n    <link rel=\"stylesheet\" href=\"/assets/application-9b8e2e0d675fa110d5daab5bb6 ..."(以下省略)


show のルーティング

% reils routes
=>
tweet GET /tweets/:id(.:format) tweets#show

.../:id まで必要!

× get tweet_path

get tweet_path(@tweet)

=> FactoryBot.create(:tweet)で保存した@tweetを引数に指定することでidが取得される

英単語

CTO(なぜ今)

Chief Technical Officer: テクニカルな(技術)部門の責任者 -> 最高技術責任者

CEO(なぜ今)

Chief Exective Officer: 業務執行役員(エグゼクティブ)の中の責任者 -> 最高経営責任者

代表取締役(なぜ今)

会社法第349条で定められた会社の代表者法的権限を有する

(株式会社の代表)
第三百四十九条 取締役は、株式会社を代表する。ただし、他に代表取締役その他株式会社を代表する者を定めた場合は、この限りでない。
2 前項本文の取締役が二人以上ある場合には、取締役は、各自、株式会社を代表する。
3 株式会社(取締役会設置会社を除く。)は、定款、定款の定めに基づく取締役の互選又は株主総会の決議によって、取締役の中から代表取締役を定めることができる
代表取締役は、株式会社の業務に関する一切の裁判上又は裁判外の行為をする権限を有する
5 前項の権限に加えた制限は、善意の第三者に対抗することができない。

Request Spec まとめ

  • 投稿されたデータがきちんと保存されていることを確認
    -> 実際にデータをDBに保存して、HTMLにデータが出力されていることを確認

    • DBに保存 = create
    • HTMLへの出力確認 = expect(response.body).to include(@tweet.text)

  • 投稿検索フォームの存在を確認 -> 投稿を検索するという文字列 が含まれることを確認。(それでいいの?)



DB設計:中間テーブル

中間テーブル

テーブルが「多対多」の場合は、カラム同士の関係性だけを搭載したテーブルを間に作る。

user_classesテーブル
id user_id class_id
1 1 1
2 1 2
3 2 3
4 2 1


through

「多対多」のアソシエーションで使う。

usersテーブル

class User < ApplicationRecord
  has_many :user_classes
  has_many :classes, through: :user_classes
end

classesテーブル

class Class < ApplicationRecord
  has_many :user_classes
  has_many :users, through: :user_classes
end

user_classesテーブル

class UserClass < ApplicationRecord
  belongs_to :user
  belongs_to :class
end


ジェイウォーク(jaywalk

中間テーブルを使わないよくない実装の形。アンチパターンの一つ。

一つのidカラムに、複数の情報を入力した状態とか。

それを解消するための中間テーブル

  • jaywalk: 〔交通規則を無視して〕道路を横断する、横断歩道のないところを横切る◆【語源】jay(不注意な人)から。

コントーラー生成時に不要ファイルが作られないように。

config/application.rb

module HogeApp
  class Application < Rails::Application
    config.generators do |g|
      g.stylesheets false
      g.javascripts false
      g.helper false
      g.test_framework false
    end
  end
end


READ ME

アプリケーションの説明書(rails newで自動作成)

マークダウンで書く



結合テスト#2

今日の学び

新単語は先に書き出すべし

テストは基本ミスってるところを教えてくれるから、それに従う。

必要とされるのは、知識より根気。。。

have_selector

セレクタの有無を確認

expect(page).to have_selector ".content_post[style='background-image: url(#{インスタンス変数とか});']"
# !カッコではなくスペース!

(追記2023/9/2)
セレクタの指定
 要素: a, p, img (ドットなし)
 属性: .クラス名, .id名 (ドットあり)

a要素に対して、リンクの有無を確認

expect('要素').to have_link 'ボタンの文字列', href: 'リンク先のパス'


当てはまるボタンがないことを確認

expect('要素').to have_no_link 'ボタンの文字列', href: 'リンク先のパス'


all

findは要素が1個の時しか使えない。allならまとめて取得できる。

# 同名のクラス 全要素
all('クラス名')

#◯番目のhogeクラス。
#一番上に表示されているのが[0]、二番目が[1]...
all('hoge')[0]


have_field

form要素の有無を確認。

#id名で指定するときに'#'はつけない。
have_field('id名')

#入力された内容まで確認するならwith
have_field('id名', with: "hoge")


find_link().click

a要素をクリックする時に使う

find_link('リンクの文字列', href: 'URL').click


サポートモジュール

spec/support/hoge_support.rbを作成して、その中にモジュールを定義

#モジュールを定義
module HogeSupport
  def hoge(user)
    ...
  end
end


spec/rails_helper.rbコメントアウトを外す。RSpec.configureに読み込み設定を追記。

# コメントアウトを外す
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }


RSpec.configure do |config|
# 下記を追記
  config.include HogeSupport

  ...

これでテストファイル内で、hoge(user)が使えるようになる。
(インスタンスメソッドの定義に似ている)

補足

chat GPT このコードの意味を以下に解説します:

  1. Dir[Rails.root.join('spec', 'support', '**', '*.rb')]: この部分は、Railsプロジェクトのルートディレクトリから始まり、spec/supportディレクトリ以下のすべての.rbファイルを再帰的に検索します。つまり、spec/supportディレクトリ以下のすべてのサポートモジュールファイルのパスの配列を生成します。

  2. .sort.each { |f| require f }: 生成されたファイルパスの配列をソートし、各ファイルをrequireメソッドを使って読み込みます。これにより、サポートモジュールがテストコードに利用可能になります。

いざ 結合テスト

System Spec

結合テストを記述する仕組み。

  • Model Spec: モデルのテスト
  • Request Spec: コントローラーのテスト (RSpec基本機能)
  • System Spec: 結合テスト (CapybaraというGemを使う: Rails 標準搭載)

Capybara = 結合テスト用のGem

≪chat GPT先生≫

Capybaraは、Webアプリケーションのテストをシンプルで人間らしいスタイルで書くためのRuby用のライブラリです。

この名前は、テストにおいてブラウジングセッションを操作するときに、ユーザーがキャピバラ(南米に生息する巨大な水生哺乳動物)のように、ゆっくりとしたステップでウェブページを操作するような感覚を持ってテストを書けることを示しています。

Capybaraの名前は、テストコードを書くプロセスがゆっくりとした動きであること、そして開発者がウェブページを操作する際に"キャピバラのような"体験を得られることを示しています。この名前は、テストコードを書く際に自然な感覚を持ち、効果的な統合テストを行う手助けをすることを意味しています。

ほんまかいな

まずはファイル作成

% rails g rspec:system users 

spec/system/users_spec.rb が生成

example整理(急にむずない?)

基本的には、それぞれの場合を想定した時に、ユーザー操作に対応する挙動・表示を順番に確認する。

  • ユーザー新規登録ができるときのexample
    -> トップページ -> 新規登録画面 -> 情報入力 -> ユーザーモデル +1カウント -> トップページへ ...

  • ユーザー新規登録ができないときのexample
    -> トップページ -> 新規登録画面 -> 情報入力 -> ユーザーモデル +1されない -> 再び新規登録画面 ...

visitメソッド

実際にそのページを訪れる

visit root_path


page have_content

page: 可視部の情報を格納 have_content: 該当の文字列を含むかどうかを判断

expect(page).to have_content('hoge')


fill_in: 埋める

自動でフォームに入力してくれる

fill_in 'フォーム名', with: '文字列'
# !括弧じゃなくて、スペース

フォーム名 = label要素

label要素

inputとセットで持ちられることが多い。 for属性id属性 によって紐付けを確認。

<div class="example">
  <label for="hoge">Example</label>
  <input type="checkbox" id="hoge">
</div>
  • 文字列 Exampleチェックボックスを紐づける
  • label要素ををクリックしてもフォームがアクティブ化する とかのメリットがある(らしい)。

上記の例では、Exampleがフォームの名
(for = id = "hoge" でinput要素と紐づいたlabel要素を確認。)

fill_in 'Example', with '文字列'

(追記 2023/9/2)
フォーム名として使えるのは
 ①for,idで紐づいたlabel要素のテキスト(上記)
 ②name属性
 ③普通にid属性
 (クラス属性は×)

参考

style.potepan.com


find().click

findしてクリックしてくれる。 基本は要素を指定。たくさんある場合はnameで指定。

find('input[name="commit"]').click


change

ユーザーモデルのカウントが増えることを確認する時に使う。

expect{'動作'}.to change { Model.count }.by(1)

ブロックを渡すときは、{}らしい。(なんのこっちゃ)

バックエンド側の処理とかを書くときは波括弧{}になるっぽい。

sleep 1

1秒待ってくれる。(←重要。つけ忘れたらできんかった。)

expect{
  find('input[name="commit"]').click
  sleep 1
}.to change { User.count }.by(1)


current_path

今いるページがroot_pathかどうか確認

expect(page).to have_current_path(root_path)

トップページが開いたところでbinding.pryしてみた

[1] pry > current_path
=> "/"

これでもいいらしい

expect(current_path).to eq(root_path)


hover ( :空中に停止する。ホバリング

マウスを合わせた時の挙動確認

expect(
  find('対象の要素').hover
).to have_content('文字列')


クラス要素を指定するときは'.hoge'

htmlの要素を指定するときは、'span'(ドットなし)だけど、クラス属性は、'.hoge'(ドットあり)

≪by chatGPT≫
これは、CSSセレクタの記法に従ったもので、Capybaraはこれをサポートしています。

あ、CSSと一緒か。なるほど。

have_no_content

文字列を含んでいないことを確認。

expect(page).to have_no_content('文字列')


英単語

directory(いまさら)

dis-(ばらばらに)+rego(導く)

->道順を案内する(direct)内容が記録された紙(-orium)、人を指揮する(direct)内容が記録された紙(-orium)

=> 住所氏名簿、ディレクト

  • direct: ばらばらを(dis-)真っすぐに導く(rego) -> 指揮する、向ける、真っ直ぐな
  • elect: ~の上へ(ex-)真っすぐに導く(rego)こと -> 立てる、直立する (-> ホモ・エレクトス:直立歩行する人)
  • correct: con-(完全に、一緒に)+rego(真っすぐに導く、統治する) -> 訂正する、正す
  • address: ~へ(ad-)真っすぐ(directus)に向けること -> 対処する、ヒトに宛てる、宛名
  • alert: ex-(~の上へ)+rego(導く) -> イタリア語 all’(~へ)+erta(塔) -> 警戒した、警報

fill

いっぱいに(fullaz)すること -> 充満する、いっぱいに満たす。

  • fill in: 埋める、書き込む
  • full: 満ちた(fullaz) -> 満ちた、いっぱいの
  • fulfill: いっぱい(full)に満たす(fill)こと -> 満たす、果たす、遂行する

element

elementum(世界を構成するとされた四元素のうちの一つ)が語源 -> 要素、元素

  • elementum:(世界を構成するとされた四元素のうちの一つ)が語源。 -> 基礎に位置すること => 基本的な、要素の
  • pixel: picture element(写真の要素) -> pixel

inspect

in-(~の中に)+specio(見る) -> 調査する、検査する

  • expect: 外を(ex-)見る(specto)、外を見て待つ、期待する -> 期待する、予期する
  • prospect: 前方を(pro-)見る(specio)こと -> 見通し、見込み
  • aspect: ~へ(ad-)見たこと(spectus) -> 外見、様子、側面
  • conspicious: よく(con-)見つけ(specio)た(-uus)、よく(com-)見る(specio)ことの多い(-ous) -> 目立つ、人目を引く
  • despise: 見(specio)下す(de-)こと -> 見下す、軽蔑する
  • respect: 後ろを(re-)見る(specio)、振り返ってみる、特別に見る -> 尊敬、特定の点
  • perspective: 隅々を(per-)見(specio)ている(-ivus) -> 見方、遠近法、全体像
  • suspect: こっそりと見(specio)上げる(sub-)こと -> 疑わしくおもう、容疑者

ambiguous(アンビギュアス)

動き(ago)回っ(ambo)ている(-uus)こと -> 曖昧な、多義な

  • ambassador: hmbi-(あちこちに)+heg-(駆ける) -> 「お上のためにあちこち駆け回る人」 => 大使
  • ambition: あちこち(ambi-)行く(eo)こと(-ion) -> 野心、大使
  • transition: 別の場所へ(trans-)行く(eo)こと(-io) -> 遷移、推移
  • initiate: ラテン語 in-(~の中に)+eo(行く) -> 始める(initio)こと => 始める、入会させる

いつもお世話になります

gogen-ejd.info

ja.wikipedia.org

蛇足 Gemfileのグループ分け

Gemをインストールするときはどこに書くのが正解だっけ?ってなったので。

結論

開発環境でだけ使いたい

===> group :development do

テストでだけ使いたい

===> group :test do

開発環境とテスト環境で使いたい

===> group :development, :test do

本番環境でだけ使いたい

===> group :prduction do

全部の環境で使いたい

===> 上記のグループ以外の場所

参考

zenn.dev



単体テストコード#2

メソッド

context: 条件ごとにグループ分け

使い方は、describeと同じ。単なるグループ分け。見やすさ。

テストファイルを作成するときのコマンド
% rails g rspec:model tweet

spec/factories の中と
spec/models  の中にファイルができる。

Faker::Lorem.sentence
  • ランダムに文章を作成してくれる。
  • 大文字注意
  • sentence = 4単語、1文
  • paragraph = 3文、1段落

Lorem: 出版やグラフィックデザインなどに用いられるダミーテキストlorem ipsum(ロレム・イプサム)の略。

Lorem ipsumの起源は古代ローマの哲学者、詩人キケロが書いた「善と悪の究極について」にさかのぼります。この著作の中で、キケロは「Neque porro quisquam est qui dolorem ipsum quia dolor sit amet・・・」と言う文章を使っています。

出典: Lorem ipsum(ロレム・イプサム)とは | 印刷・広告・デザイン用語集 | デザイン作成依頼はASOBOAD

≪chat GPT 日本語訳≫
「実際、どのような人間であっても、痛みそのもののために苦しむことはない。なぜなら、痛み自体が苦しみであるというわけではないからである。」

原文では、dolorem ipsum(痛み自体)だが、そこから意味のない言葉としてlorem ipsumが取り出されているらしい。(なぜに)

FactoryBot内のassociation

関連づけておくと、同時生成される。

FactoryBot.define do
  factory :tweet do
    association :user
    #userのインスタンスも同時生成
  end
end


英単語

context: 文脈、文中の言葉の前後関係、事情

context: 一緒に(con-)編んだ(texus)、構造、文の構造 -> 文脈、文中の言葉の前後関係、事情

  • subtle: sub-(下に)+tela(織物;生地の縦糸) -> かすかな。繊細な。
  • text: 本文
  • textile: ラテン語 texo(織る)+-ilis(されたもの)が語源 -> 布地。織物。
  • tissue: texo(織る)、「織られたもの」がコア -> 薄い織物。細胞組織。ティッシュ



単体テストコード

今日の工夫

カリキュラムは、INDEXのタイトル部分だけを先に読んでから、上から順に中身を読む。

全体像を把握した上で、個別の内容をinputする。

徒然草

世の中にはいろんな働き方があって、いろんな技術があって、いろんな言語があり、勉強しなければいけないことが山のようにある。

将来のこととか考えて悶々としてたが、ひとまずは今を楽しむことにする。

わかりやすい目標があって、サポートしてくれる人がいて、同じ課題に取り組む人がいる。

せっかく貴重な環境に身を置けているので、その時々を一生懸命に過ごさないと勿体無い。

メソッド・単語

Gem FactoyBot(工場生産的な?)
ビルド(build)メソッド

FactoryBotから新しいインスタンスを生成。
ActiveRecordで言うところのnew

#FactoryBot内で :user インスタンスを設定した上で
FactoryBot.build(:user) 

#newと同じ意味
User.new(nickname: 'hoge', ...)


beforeメソッド

ActiveRecordで言うところのbefore_action

変数はインスタンス変数で。

  before do
    @user = FactoryBot.build(:user)
  end


Gem 'Faker'

fake: 偽造する。ふりをする。偽物。

ランダムな値を生成してくれるGem。
文字数指定とか、大文字指定とかもできるっぽい。
大文字始まり(キャメルケース?)

Faker::Name.initials(number: 2)
=> "GR"

Faker::Internet.email
=> "vida@oconnell.test"

Faker::Internet.password(min_length: 6, max_length: 20)
=> "3OVpQDpPFe"


exampleの洗い出し

バリデーションとメソッドが対象

deviseによるバリデーション = validatable
  • 以下が初期設定
    • email {存在, 一意, @がある}
    • password {存在, 6文字以上128文字以下}

正常系のテスト
  • be_valid = 「valid?->true」を期待するmatcher

英単語

argument

明らかにする(arguo)こと(-mentum)、答えや主張を明らかにすること

-> 議論、議論の根拠、論証 引数

  • argue: 明白にする(arguo)、答えや主張を明白にする -> を主張する、を議論する

まとめ

テストの流れは、

  1. インスタンス設定

  2. valid?で保存できるか確認

  3. expect().to matcher

とにかく条件に合致する配列データ(インスタンス)をゴリゴリ作っていく感じ。

可能性を全部試すと言うより、条件を満たすサンプルを1個書ければそれでいい。