32

トランザクションを使用することを確認したいモデル関数があります。例えば:

class Model 
  def method
    Model.transaction do
      # do stuff
    end
  end
end

私の現在のアプローチは、ブロック内でメソッド呼び出しをスタブしてActiveRecord::Rollback例外を発生させ、データベースが実際に変更されたかどうかを確認することです。ただし、これは、何らかの理由でブロック内の実装が変更された場合、テストが中断されることを意味します。

これをどのようにテストしますか?

4

7 に答える 7

39

問題を別の視点から見る必要があります。関数がトランザクションを使用しているかどうかをテストすることは、動作の観点からは役に立ちません。関数が期待どおりに動作するかどうかについての情報は提供しません。

テストする必要があるのは動作です。つまり、期待される結果は正しいです。わかりやすくするために、関数内で操作 A と操作 B を実行するとします (1 つのトランザクション内で実行されます)。オペレーション A は、アプリでユーザーに 100 米ドルを入金します。操作 B は、ユーザーのクレジット カードから 100 USD を引き落とします。

ユーザーのクレジット カードの引き落としが失敗するように、テストに無効な入力情報を提供する必要があります。関数呼び出し全体をexpect { ... }.not_to change(User, :balance).

このようにして、予想される動作をテストします。クレジット カードによる引き落としが失敗した場合、ユーザーに金額を入金しないでください。また、コードをリファクタリングするだけであれば (たとえば、トランザクションの使用を停止し、手動でロールバックするなど)、テスト ケースの結果は影響を受けません。

そうは言っても、@luacassusが述べたように、両方の操作を分離してテストする必要があります。また、@ rb512 が述べたように、ソースコードに「互換性のない」変更を加えた (つまり、動作を変更した) 場合に、テスト ケースが失敗するのはまったく正しいことです。

于 2012-05-29T13:46:43.290 に答える
17

大きな落とし穴について言及する必要があります。トランザクションをテストするときは、transactional_fixturesをオフにする必要があります。これは、テストフレームワーク(Rspecなど)がテストケースをトランザクションブロックでラップするためです。実際には何もコミットされていないため、after_commitが呼び出されることはありません。:requires_new => trueを使用しても、トランザクション内でのロールバックの期待は機能しません。代わりに、テストの実行後にトランザクションがロールバックされます。http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.htmlネストされたトランザクションを参照してください。

于 2012-12-28T21:18:54.600 に答える
6

一般に、「純粋な」rspec テストを使用して、アプリケーション (クラスとメソッド) のチャンクを分離してテストする必要があります。たとえば、次のコードがあるとします。

class Model 
  def method
   Model.transaction do
     first_operation
     second_operation
   end
end

理想的にはデータベースにアクセスせずに、別々のテストシナリオでfirst_operationテストする必要があります。second_operation後で、Model#methodこれら 2 つのメソッドのテストを作成してモックできます。

次のステップでは、 https://www.relishapp.com/rspec/rspec-rails/docs/request-specs/request-specを使用して高レベルの統合テストを作成し、このコードがさまざまな条件でデータベースにどのように影響するかを確認できます。 、たとえばいつsecond_method失敗するか。

私の意見では、これは複雑なデータベース クエリを生成するコードをテストするための最も実用的なアプローチです。

于 2012-05-26T08:18:53.200 に答える
2

Model.transaction do ... endまず、ブロックをブロックで囲む必要がありますbegin rescue end

トランザクションのロールバックをチェックする唯一の方法は、例外を発生させることです。したがって、現在のアプローチはすべて良好です。あなたの懸念に関しては、実装の変更は常にそれに応じてテストケースを変更することを意味します。メソッドの実装が変わっても、変更の必要がない一般的な単体テストケースはあり得ないと思います。

お役に立てば幸いです。

于 2012-05-25T10:10:24.033 に答える
1

私は同じことをしてきましたが、おそらく、あるスペックのモデルで「トランザクション」メソッドが呼び出されたことをテストしてから、別の別のスペックでブロックの本体をテストするだけでよいと思います。ただし、現在のテストのようにトランザクションがメソッド呼び出しをラップすることは保証されず、そこにある可能性のある他のコードは保証されません。

于 2012-05-26T21:29:19.890 に答える
0

そのような方法が与えられた場合:

class ApplicationJob < ActiveJob::Base 
  private

  def transaction(&block)
    # yield
    ActiveRecord::Base.transaction(&block)
  end
end

仕様では、データベースを変更してから発生させるブロックを渡すプライベート メソッドを呼び出します。

RSpec.describe ApplicationJob do
  describe 'helper methods' do
    subject(:job) { Class.new(described_class).new }

    describe '#transaction' do
      it 'runs block in transaction' do
        block = lambda { FactoryBot.create(:user); raise; }
        expect { job.send(:transaction, &block) rescue nil }.not_to change(User, :count)
      end
    end
  end
end

transactionメソッドを justに変更するyieldと、引き上げた後でもデータベースが変更されていることがわかります。

于 2021-11-30T16:51:45.957 に答える
-6

Railsコンソールのサンドボックスモードでテストしてみる

レールコンソール --サンドボックス

于 2012-06-01T07:33:52.417 に答える