11

属性のアクセシビリティ テストに関する別の StackOverflow の質問に答えてshoulda-matchersについて学習した後(そして、それらは非常に素晴らしいと思いました)、Rails チュートリアルで行ったモデル テストをリファクタリングして、より簡潔で完全なものにすることを試みることにしました。これを行ったのは、モジュールとモジュールのドキュメントからのインスピレーションと、モデル内のテストの構造化に関するこの StackOverflow の回答のおかげです。しかし、まだよくわからない点がいくつかあり、これらのテストをどのように改善できるかを考えています。 Shoulda::Matchers::ActiveRecordShoulda::Matchers::ActiveModel

Rails チュートリアルのユーザー仕様を例として使用します。これは最も詳細であり、改善できる多くの領域をカバーしています。次のコード例は、元のuser_spec.rbから変更されており、行までコードを置き換えていますdescribe "micropost associations"。仕様はuser.rbモデルに対してテストされ、そのファクトリは factory.rb で定義されます

仕様/モデル/user_spec.rb

# == Schema Information
#
# Table name: users
#
#  id              :integer          not null, primary key
#  name            :string(255)
#  email           :string(255)
#  created_at      :datetime         not null
#  updated_at      :datetime         not null
#  password_digest :string(255)
#  remember_token  :string(255)
#  admin           :boolean          default(FALSE)
#
# Indexes
#
#  index_users_on_email           (email) UNIQUE
#  index_users_on_remember_token  (remember_token)
#

require 'spec_helper'

describe User do

  let(:user) { FactoryGirl.create(:user) }

  subject { user }

  describe "database schema" do
    it { should have_db_column(:id).of_type(:integer)
                              .with_options(null: false) }
    it { should have_db_column(:name).of_type(:string) }
    it { should have_db_column(:email).of_type(:string) }
    it { should have_db_column(:created_at).of_type(:datetime)
                              .with_options(null: false) }
    it { should have_db_column(:updated_at).of_type(:datetime)
                              .with_options(null: false) }
    it { should have_db_column(:password_digest).of_type(:string) }
    it { should have_db_column(:remember_token).of_type(:string) }
    it { should have_db_column(:admin).of_type(:boolean)
                              .with_options(default: false) }
    it { should have_db_index(:email).unique(true) }
    it { should have_db_index(:remember_token) }
  end

  describe "associations" do
    it { should have_many(:microposts).dependent(:destroy) }
    it { should have_many(:relationships).dependent(:destroy) }
    it { should have_many(:followed_users).through(:relationships) }
    it { should have_many(:reverse_relationships).class_name("Relationship")
                         .dependent(:destroy) }
    it { should have_many(:followers).through(:reverse_relationships) }
  end

  describe "model attributes" do
    it { should respond_to(:name) }
    it { should respond_to(:email) }
    it { should respond_to(:password_digest) }
    it { should respond_to(:remember_token) }
    it { should respond_to(:admin) }
    it { should respond_to(:microposts) }
    it { should respond_to(:relationships) }
    it { should respond_to(:followed_users) }
    it { should respond_to(:reverse_relationships) }
    it { should respond_to(:followers) }
  end

  describe "virtual attributes and methods from has_secure_password" do
    it { should respond_to(:password) }
    it { should respond_to(:password_confirmation) }
    it { should respond_to(:authenticate) }
  end

  describe "accessible attributes" do
    it { should_not allow_mass_assignment_of(:password_digest) }
    it { should_not allow_mass_assignment_of(:remember_token) }
    it { should_not allow_mass_assignment_of(:admin) }
  end

  describe "instance methods" do
    it { should respond_to(:feed) }
    it { should respond_to(:following?) }
    it { should respond_to(:follow!) }
    it { should respond_to(:unfollow!) }
  end

  describe "initial state" do
    it { should be_valid }
    it { should_not be_admin }
    its(:remember_token) { should_not be_blank }
    its(:email) { should_not =~ /\p{Upper}/ }
  end

  describe "validations" do
    context "for name" do
      it { should validate_presence_of(:name) }
      it { should_not allow_value(" ").for(:name) }
      it { should ensure_length_of(:name).is_at_most(50) }
    end

    context "for email" do
      it { should validate_presence_of(:email) }
      it { should_not allow_value(" ").for(:email) }
      it { should validate_uniqueness_of(:email).case_insensitive }

      context "when email format is invalid" do
        addresses = %w[user@foo,com user_at_foo.org example.user@foo.]
        addresses.each do |invalid_address|
          it { should_not allow_value(invalid_address).for(:email) }
        end
      end

      context "when email format is valid" do
        addresses = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn]
        addresses.each do |valid_address|
          it { should allow_value(valid_address).for(:email) }
        end
      end
    end

    context "for password" do
      it { should ensure_length_of(:password).is_at_least(6) }
      it { should_not allow_value(" ").for(:password) }

      context "when password doesn't match confirmation" do
        it { should_not allow_value("mismatch").for(:password) }
      end
    end

    context "for password_confirmation" do
      it { should validate_presence_of(:password_confirmation) }
    end
  end

  # ...
end

これらのテストに関する具体的な質問:

  1. データベーススキーマをテストする価値はありますか? 上記の StackOverflow の回答のコメントには、「動作に関連するものだけをテストし、列の存在やインデックスの動作は考慮していません。誰かが意図的に削除しない限り、データベースの列は消えるだけではありませんが、コードレビューと信頼でそれを防ぐことができます」と私は同意しますが、データベーススキーマの構造がテストされ、Shoulda::Matchers::ActiveRecordモジュールの存在が正当化される正当な理由はあり ますか? おそらく、重要なインデックスだけをテストする価値があります...?
  2. 下のテストは、should have_many下の対応するテストを"associations"置き換えますか? テストがモデル ファイル内の関連する宣言を検索するだけなのか、それとも実際に と同じ機能を実行するのかはわかりません。should respond_to"model attributes"should have_manyhas_manyshould respond_to
  3. コンテンツと構造の両方で、これらのテストをより簡潔/読みやすく/完全にするために、他にコメント/提案はありますか?
4

5 に答える 5

4

1) Shoulda::Matchers::ActiveRecord モジュールには、列とインデックスのマッチャー以外にも多くの機能があります。含まれているクラスを少し掘り下げて、何が見つかるか見てみましょう。これはhave_manybelong_toなどの由来です。記録のために、私はそこにあるもののほとんどにほとんど価値がないと思います.

2) はい、 などのマクロhave_manyは、モデルがメソッドに応答するかどうかよりも多くのことをテストします。ソース コードから、何をテストしているのかを正確に確認できます。

def matches?(subject)
  @subject = subject
  association_exists? &&
    macro_correct? &&
    foreign_key_exists? &&
    through_association_valid? &&
    dependent_correct? &&
    class_name_correct? &&
    order_correct? &&
    conditions_correct? &&
    join_table_exists? &&
    validate_correct?
end

3) テストをより読みやすく簡潔にすることは、間違いなく主観的な質問です。バックグラウンドや経験に応じて、誰もがこれに対して異なる答えを出すでしょう。個人的には、すべてのテストを取り除き、respond_to価値のあるテストに置き換えます。誰かがあなたのテストを見たとき、彼らはそのクラスの公開 API を理解できるはずです。オブジェクトが "following?" のような応答をするのを見ると、推測はできますが、それが何を意味するのかはよくわかりません。引数が必要ですか?ブール値を返しますか? オブジェクトは何かを追っていますか、それとも何かがオブジェクトを追っていますか?

于 2012-09-03T00:34:17.283 に答える
1

あなたの質問はいくつかの点に触れていましたが、そのうちの 2 つを取り上げたいと思います。

主観的な回答になりますので、個人的な見解でお願いします。

1) ActiveRecord をそのようにテストしますか?
私の答えはイエスです。実際のデータを使用して複雑なテストを作成することもできますが、基本的に ActiveRecord を信頼している場合は、この方法で実行できます。また、tdd を実行できる場合は、最初にこれらのテストを使用してそのプロセスを支援できます。

2) モデル テストを作成しますか?
私の答えはイエスです。私がやっていることは、コントローラーとリクエストの仕様をハッピー パスにフォーカスし、検証などが必要な場合に備えて、ユニット モデル テストを作成することです。これは、私にとって良い責任分担であることが判明しました。

于 2012-09-03T00:54:11.837 に答える
0

この全体を仕様の観点から見るべきだと思います。

特定のモデルに必要なデータベース列をカバーするコンポーネント テスト レベルの仕様がある場合は、そうする必要があります。

カバーされていなくても、責任ある開発者として持つことが重要だと感じている場合 (sw とその品質特性はその方が優れています)、この情報を仕様に含めるように手配する必要があります。その後、これらのテストをテスト スイートに入れることができます。 .

于 2012-08-24T05:54:46.907 に答える
0

データベースの列が存在するかどうかのテストを作成することには、ある程度の価値があることがわかりました。理由は次のとおりです。

1) それらを書くことで、TDD のリズムを保つことができます。
2) 移行は、そうでなくなるまでは、通常、非常に素晴らしいものです。既存の移行を編集するべきではないことはわかっていますが、自分で何かに取り組んでいるときは、とにかく編集することがあります。また、他の誰かが同じアプリケーションで作業していて、新しい移行を作成する代わりに既存の移行を変更した場合、これらのテストで問題がすぐに切り分けられました。

列の名前と型が多すぎて行き詰まる場合は、次のようにして入力の手間を省くことができます。

describe User do

  describe 'database' do 

    describe 'columns' do 

      %w[reset_password_sent_at remember_created_at current_sign_in_at 
        last_sign_in_at confirmed_at confirmation_sent_at 
        created_at updated_at
        ].each do |column|
        it { should have_db_column(column.to_sym).of_type(:datetime) }
      end
    end

    describe 'indexes' do 

      %w[confirmation_token email reset_password_token
      ].each do |index|
        it { should have_db_index(index.to_sym).unique(true)}
      end
    end
  end  
end

それが役立つことを願っています。

于 2013-01-15T03:50:38.603 に答える
0

下位のテスト レベルの要件は、ほとんどが組織内 (内部ドキュメント) からのものであり、顧客はほとんどの場合、顧客の要件仕様のみを提供します (これが V モデルのテストの最高レベルであるとしましょう)。組織が設計を開始すると、sw は下位のテスト レベルの仕様を段階的に作成します。

「これが本当に必要なのか」という質問については、アプリの複雑さ、安全性が重要かどうか、従うべき基準、契約/法律/業界の規制など、さまざまな要因によって異なります。

一般的に言えば、ユニットテストを担当する正しい理想的なアプリケーション要件のためには、ユニットレベルの仕様を記述し、テスターはこの仕様に基づいてテストを実装する必要があります。

「have_many and Respond_to」については、それらがどのように実装されているかについての背景情報がないため、お答えできません。

于 2012-08-29T14:07:21.393 に答える