【minitest】input要素の文字列はvalue属性の値を検証しなければならない

結論

input要素で表示されている文字列の検証はvalue属性の値を検証する必要がある。

  • input要素のvalue属性の値の検証は、[value='値']で検証対象を取得できる
  • textarea要素の文字列の検証は、text: 'テキスト' でタグで囲まれた文字列を検出できる

input要素とtextarea要素のテキストの表示方法の違い

  • input要素: value属性で表示テキストの値を持つ
  • textarea要素: 開始タグと終了タグで囲まれたテキストが表示される

具体例

例) 以下のフォーム内に表示されている文字列の表示テストを行う

<form action="..." accept-charset="UTF-8" method="post">>
  <div>
    <input type="text" value="インプットの文字列" name="..." id="input">
    <textarea name="..." id="textarea">テキストエリアの文字列</textarea>
  </div>
  <div>
    <input type="submit" name="commit" value="更新する">
  </div>
</form>

失敗するテスト

input要素の文字列はassert_text では検出できない(input内の文字列はテキストでは無いため)。

class DisplayTest < ApplicationSystemTestCase
  test 'インプットの文字列が表示される' do
    # テスト対象の文字列を取得できないためエラー
    assert_text "インプットの文字列"
    # パスする
    assert_text "テキストエリアの文字列"
  end
end

成功するテスト

input要素のvalueの値を検証することで文字列を検出できる。

class DisplayTest < ApplicationSystemTestCase
  test '' do
    # input idを持つインプット要素のvalue要素の値を検証
    assert_selector "input#input[value='インプットの文字列']"
    assert_text "テキストエリアの文字列"
  end
end

参照

Rails テスティングガイド - Railsガイド

感想

input要素の文字列が検出できずにハマった... HTMLもテストも奥が深い。

Turboを使ったRailsのシステムテストではJavaScriptの処理完了のタイミングに注意

test-unitでのシステムテストでちょっとハマったのでメモ。

症状

テスト実行時にログイン処理が不安定(ログインできる/できない)という症状が発生。

class BooksTest < ApplicationSystemTestCase
  setup do
    @book = books(:one)

    # ログイン処理
    visit root_url
    fill_in 'Eメール', with: 'alice@example.com'
    fill_in 'パスワード', with: 'password'
    click_on 'ログイン'
  end

  test 'visiting the index' do
    visit books_url 
    assert_selector 'h1', text: 'Books' # ここでテストがパスしない
  end
end

原因

Railsアプリが描画処理にTurboを使用しており、JavaScriptの描画処理(画面遷移)の完了を待たずにCapybaraが動作してしまうため。

  • テスト検証のタイミングに描画処理の完了が間に合っていない(テストのフライング)
  • Turboに限らず、Ajaxでの非同期処理でも発生するらしい
  • ブラウザによってはパスすることもあるっぽい

対策

assert_xxx 系のメソッドでJavaScriptの描画処理の完了を待たせる。

  • assert_textassert_selector等が使える
  • デフォルトで最大2秒待ってくれる
class BooksTest < ApplicationSystemTestCase
  setup do
    @book = books(:one)

    # ログイン処理
    visit root_url
    fill_in 'Eメール', with: 'alice@example.com'
    fill_in 'パスワード', with: 'password'
    click_on 'ログイン'
    assert_text 'ログインしました。' # JavaScriptの処理完了を待たせる
  end
  
  test 'visiting the index' do
    visit books_url
    assert_selector 'h1', text: 'Books' # JavaScriptの処理完了後に実行される
  end
end

その他の対策

  • sleep を使って処理を待たせる
    • テスト実行が遅くなるので微妙かも...
  • Turboの無効化
    • アプリ自体の描画処理が遅くなるっぽい
    • React、Vue等では対策できない

感想

久々に時間溶かしてしまった... システムテストは描画処理の完了と検証開始のタイミングは必ずしも一致していないことに注意していこう。

Chromeでフルスクリーンのスクリーンショット(キャプチャ)を撮影する

Chrome DevToolsを使用してフルスクリーンのスクリーンショット(キャプチャ)を撮影する方法。 この方法で縦スクロールのページも撮影できる。

手順

  1. DevTools起動
  2. レスポンシブ設定をResponsive に設定
  3. 右上の3点リーダーのCapture full size Screenshot で撮影
  4. ダウンロードフォルダに保存される

1. DevTools起動

以下のショートカットでDevToolsを起動。

  • Maccommand + opt + i
  • Windowscontrol + shift + i

2. レスポンシブ設定をResponsiveに設定

DevTools内のレスポンシブ設定ボタンを押下後、レスポンシブ設定をResponsive に設定。

3. 右上の3点リーダーのCapture full size screenshotで撮影

DevTools内の右上の3点リーダーをクリックすると表示されるCapture full size screenshot をクリックすると撮影される。

4. ダウンロードフォルダに保存される

ダウンロードフォルダにpng で保存される。

test-unitについて

test-unitの基礎知識。

test-unitとは?

単体テスト

  • 単一のプログラム部品(モジュール)を対象としたテスト
    • クラス、メソッド、関数などの最小単位が対象
  • 問題の早期発見が目的
  • モジュール単位の動作の保証する

単体テストツール

  • ドライバ

    • driver
    • 最小単位の処理(関数等)を呼び出すツール
  • スタブ

    • stub
    • 最小単位の処理(関数等)のプログラムのダミー

test-unitの使い方

  1. Test::Unit::TestCase を継承したテスト用クラスを用意する
  2. test_xxx という名前でテスト用メソッドを用意する
  3. ファイルを実行する

例) モデルUser のメソッドgreetingHello! を返すことを確認するテスト

下記の内容でファイルを作成する。

# user.rb
require 'test/unit'

class User
  def self.greeting
    'Hello!'
  end
end

class TestUser < Test::Unit::TestCase
  def test_greeting
    assert_equal 'Hello!', User.greeting
  end
end

メソッドの動作をテストする。

# テストが成功
$ ruby user.rb
Loaded suite test
Started
.
Finished in 0.000289 seconds.
-------------------------------------------------------------------------------------
1 tests, 1 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-------------------------------------------------------------------------------------
3460.21 tests/s, 3460.21 assertions/s
# テストが失敗
# 大文字Hが小文字hになっている
$ ruby test.rb
Loaded suite test
Started
F
=====================================================================================
Failure: test_greeting(TestUser)
test.rb:11:in `test_greeting'
      8:
      9: class TestUser < Test::Unit::TestCase
     10:   def test_greeting
  => 11:     assert_equal 'Hello!', User.greeting
     12:   end
     13: end
<"Hello!"> expected but was
<"hello!">

diff:
? Hello!
? h
? ?
=====================================================================================
Finished in 0.004249 seconds.
-------------------------------------------------------------------------------------
1 tests, 1 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
0% passed
-------------------------------------------------------------------------------------
235.35 tests/s, 235.35 assertions/s

test-unitの便利メソッド

  • starup

    • テスト郡実行前に処理を実行する
  • shutdown

    • テスト郡実行後に処理を実行する
  • setup

    • 各テストの実行前に処理を実行する
  • cleanup

    • テストがpassedの場合にテスト実行後に処理を実行する
  • teardown

    • 各テストの実行後に処理を実行する

モックオブジェクトとは?

  • テスト対象の処理を実行せず、引数の検証とテストを動かす最低限の出力を行う
  • Rubyでモックオブジェクトを作るライブラリは複数存在する
    • FlexMock
    • MiniTest::Mock
    • RR
    • WebMock

参照

Rails テスティングガイド - Railsガイド

配列同士の比較はSetを使うと順序の考慮が不要になる

バリデーションの実装で配列同士の比較で要素の順序を考慮せず比較する方法としてSetを教えてもらいました。

結論

Rubyでは集合を表すSetを使うことで配列の順序の考慮が不要になる。

class Set (Ruby 3.2 リファレンスマニュアル)

Set(集合)とは?

RubyのSetは一意な値の集合を表現するためのデータ構造です。 配列やハッシュと同じオブジェクトの集合を表すものですが以下のような特徴があります。

  • 要素が重複しない
  • 要素の順序は保持されない

配列、ハッシュとの違い

  • 配列との違い

    • 重複要素の扱い
      • 配列: 複数持てる
      • 集合: 一意な要素のみ
    • 順序の扱い
      • 配列: 保持する
      • 集合: 保持しない
  • ハッシュとの違い

    • 要素の組み合わせ
      • ハッシュ: キーと値の組み合わせ
      • 集合: 単一の要素の集合
    • 一意性の保証
      • ハッシュ: キーが一意である必要がある
      • 集合: 要素自体が一意である必要がある

ざっくりですが集合は「そのオブジェクトの集まりが順序を考慮せず、一意性を保証したい」といった場合に活用できそうです。

コンソールで動かしてみる

集合のオブジェクトはSet.new(集合の初期要素) で作成できる。 集合の初期要素としては以下が該当する。

  • 配列
  • ハッシュ

たぶん他にもあるっぽい。

library set (Ruby 3.2 リファレンスマニュアル)

require 'set'
Set.new()
=> #<Set: {}> # 空の集合
Set.new([1,2])
=> #<Set: {1, 2}> # 配列を元にした集合
Set.new({a: 1, b: 2})
=> #<Set: {[:a, 1], [:b, 2]}> # ハッシュを元にした集合

例) 配列は順序を保持するが、集合は保持しない

# 配列
array_1 = [1,2]
=> [1, 2]
array_2 = [2,1]
=> [2, 1]
array_1 == array_2
=> false # 要素の順序を保持されるのでfalse

# 集合
require 'set'
set_1 = Set.new([1,2])
=> #<Set: {1, 2}>
set_2 = Set.new([2,1])
=> #<Set: {2, 1}>
set_1 == set_2
=> true # 要素の順序が保持されないのでtrue

感想

Setの存在すら知らなかったので良い機会になった。 Rubyは便利だなーと改めて感じた。

参照

class Set (Ruby 3.2 リファレンスマニュアル) library set (Ruby 3.2 リファレンスマニュアル)