【Rails備忘録】テストってなんやねん


わし「行き詰まってきたし、ポートフォリオ作成よりも基礎固めの方が大事そうやな…」
  「オススメっぽいし、Railsチュートリアルとチェリー本と現場Railsやっとくか」

〜1週間後〜

わし「いままでほとんど知らなかったけど急に"テスト"とかいうのがたくさん出てきたんだけど…」
  「これどういう時に使うの…( ᵕ̩̩ㅅᵕ̩̩ )」

プログラミングスクールとかの教材では触れられないし、技術本でもおまけ程度にしか扱われないテスト。
そもそもいつも学習するときは、正しいと思われるコードを書いて、もし間違えてたらエラーが吐き出されて、また修正して、ローカル環境で実際に使用してみてバグがないかチェックしてみて、めでたく機能実装完了、めでたしめでたし……これどこでテスト使うねん!!!!

そこで、信頼できる情報筋(主にエンジニアやっている友達とか)にお話を聞いてみたので内容を備忘録としてまとめます。
いつも覚えの悪い僕に色々教えてくれてありがとう…

テストは後々のために書いておくもの

例えば、別の機能を追加したときに今まで動いていた機能が何故か動かなくなったり、バージョンアップしたら急にバグになってしまったりすることがありますよね。
でもいちいちローカル環境で機能を実際に使ってみてバグを探すのは面倒…。
そんなとき、コマンド一発ですべての機能をテストして、バグを発見できたらとても便利です。
今後の機能の拡大や更新作業のためにも、テストは書いておくにこしたことはないのです。(めんどうだけど…)
あと、コードのリファクタリングするときにもテストを書いておくことで、いちいちlocalhost:3000と行き来しなくても、簡単安全安心にコードの改善をすることができます。

どう書くの?

基本的には「その機能が正しく動作したらどうなるはずなのか」を元に書いていきます。
Rails標準のテストフレームワークであるMinitestでは、「どうなるのか」という部分をassertion(主張)という形で表現できます。

assert 引数  →「引数がtrueのハズだよ!trueならテスト通過だよ!」って主張
assert_not 引数 →「引数がfalseになるはずだよ!falseならテスト通過だよ!」って主張

他にも、いろいろなassertionがあって、時と場合に合わせた主張ができる。
assert_select "a[href=?]", login_path →「(href=login_path)なaタグがあればテスト通過だよ!」
assert_select 'div.alert' →「class="alert"なdivタグがあればテスト通過だよ!」
assert_equal current_user, @user →「@userがcurrent_userであればテスト通過だよ!」

これらのassertionとRESTfulな操作を使ってテストを実装していきます。

【単体テスト】

モデルやヘルパーに備わっている機能が正しく動作するのかを調べる。

ex.ユーザーモデルにて、メールアドレスのバリデーションを実装した場合、
  正しいアドレスを渡した場合、@userは期待通りvalidになればテストは通る。
  正しくないアドレスを渡した場合、@userは期待通りinvalidになればテストは通る。

user_test.rb

 test "email validation should accept valid addresses" do
    valid_addresses = %w[[email protected] [email protected] [email protected]
      [email protected] [email protected]]
    valid_addresses.each do |valid_address|
       @user.email = valid_address
       assert @user.valid?, "#{valid_address.inspect} should be valid"
    end
  end

  test "email validation should reject invalid addresses" do
    invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
      foo@bar_baz.com foo@bar+baz.com [email protected]]
    invalid_addresses.each do |invalid_address|
      @user.email = invalid_address
      assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
    end
  end

【コントローラーテスト・結合テスト】

単体、もしくは複数のコントローラーにまたがり、ユーザーが実際に取るであろう行動に基づいてテストする。

①その機能を使う行動を追う
②追う中で機能が実際に働く部分を書く

ex. 正しくないフォーム入力でログインを試みた場合、しっかりと'sessions/new'へレンダーされるのかのテスト
login_pathにアクセスする
'sessions/new'が読み込まれるはず
login_pathに正しくない内容でpostする
'sessions/new'が読み込まれるはず

users_login_test.rb
class UsersLoginTest < ActionDispatch::IntegrationTest

  test "login with invalid information" do
    get login_path
    assert_template 'sessions/new'
    post login_path, params: {session: {email: "", password: ""}}
    assert_template 'sessions/new'
  end

end

【fixtures】

例えばログイン機能を調べたい場合、テスト用に登録済みのユーザーが必要になります。
そんなときはfixtures機能を使いましょう。
yml形式でテスト用のユーザーを定義できます。

test/fixtures/users.yml
michael:
  name: Michael Example
  email: [email protected]
  password_digest: <%= User.digest('password') %>

※railsチュートリアルではUserモデルにpassword_digestカラムを設定しているため、
digestを生成するためにUserモデル内にクラスメソッドdigestを追加しています。fixtureではerbも使えますよって話。

いつ書くの?

テストを書くタイミングは2通りあります。
アプリのコードを書くより「先」に書くか、それよりも「後」に書くかです。

先に書く場合

先にテスト書いて開発を行う手法をTDD(テスト駆動開発)と言います。
①テストを書く
②コードも何も書いてないので当然テストに失敗する
③テストに失敗しないようにコーディングする
④またテストする
⑤テストがクリアするようになるまでコーディング

後に書く場合

①とりあえず考えながら機能を実装する
②機能実装が一段落ついたのでテストを書く
③テストが通らなかったらエラーメッセージに沿って改善

タイミングの使い分け

この議論はかなり根深いみたいなんですけど、主に以下のような感じらしい(Railsチュート参照)

【先に書く】
・バグが見つかったとき
・セキュリティに関わるような機能
・リファクタリングするとき

【後に書く】
・動作仕様が固まっていない、実装が大変な機能
・すぐに変わってしまいそうな箇所(htmlの内容など)

結論:テストは大事

勉強していたらよく耳にするRSpecなどをすんなりと理解するためにも、
将来的に現場で困らないようにするためにも、
テストしっかり書いていこうな!ということでした。僕もがんばります…



Qiitaもっと書きたいんだけど、色々復習してたらめっちゃ時間かかっちゃうんですよね…(結局90分くらいかかった)