计算执行的查询数

Ian*_*nce 42 mysql rspec ruby-on-rails rails-activerecord

我想测试一段代码执行尽可能少的SQL查询.

ActiveRecord::TestCase似乎有自己的assert_queries方法,这将做到这一点.但是因为我没有修补ActiveRecord,所以对我来说没用.

RSpec或ActiveRecord是否提供任何官方的公共方法来计算代码块中执行的SQL查询的数量?

Rya*_*igg 49

我想你提到你回答了你自己的问题assert_queries,但是这里有:

我建议看看后面的代码assert_queries并使用它来构建自己的方法,您可以使用它来计算查询.这里涉及的主要魔力是这一行:

ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
Run Code Online (Sandbox Code Playgroud)

今天早上我有一点修补工作,并扯掉了ActiveRecord的部分进行查询计数并得出了这个:

module ActiveRecord
  class QueryCounter
    cattr_accessor :query_count do
      0
    end

    IGNORED_SQL = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/]

    def call(name, start, finish, message_id, values)
      # FIXME: this seems bad. we should probably have a better way to indicate
      # the query was cached
      unless 'CACHE' == values[:name]
        self.class.query_count += 1 unless IGNORED_SQL.any? { |r| values[:sql] =~ r }
      end
    end
  end
end

ActiveSupport::Notifications.subscribe('sql.active_record', ActiveRecord::QueryCounter.new)

module ActiveRecord
  class Base
    def self.count_queries(&block)
      ActiveRecord::QueryCounter.query_count = 0
      yield
      ActiveRecord::QueryCounter.query_count
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

您将能够在ActiveRecord::Base.count_queries任何地方引用该方法.将它传递给运行查询的块,它将返回已执行的查询数:

ActiveRecord::Base.count_queries do
  Ticket.first
end
Run Code Online (Sandbox Code Playgroud)

对我来说返回"1".要使其工作:将其放在文件中,lib/active_record/query_counter.rb并在文件中要求它,config/application.rb如下所示:

require 'active_record/query_counter'
Run Code Online (Sandbox Code Playgroud)

嘿presto!


可能需要一些解释.我们称这行为:

    ActiveSupport::Notifications.subscribe('sql.active_record', ActiveRecord::QueryCounter.new)
Run Code Online (Sandbox Code Playgroud)

我们挂钩了Rails 3的小通知框架.这是Rails的最新主要版本的一个闪亮的小补充,没有人真正知道.它允许我们使用该subscribe方法订阅Rails中的事件通知.我们将要订阅的事件作为第一个参数传递,然后将任何响应的对象传递call给第二个参数.

在这种情况下,当执行查询时,我们的小查询计数器将尽职地增加ActiveRecord :: QueryCounter.query_count变量,但仅用于真实查询.

无论如何,这很有趣.我希望它对你有用.

  • 好剧本.如果您仅将其用于测试,则可以将其放在{spec | test} /support/query_counter.rb文件中.保留应用程序逻辑的lib文件夹. (2认同)
  • 对于那些寻找 RSpec 匹配器的人来说,这个答案已经变成了宝石:[`rspec-sqlimit`](https://github.com/nepalez/rspec-sqlimit)。 (2认同)

Yur*_*nko 22

我对Ryan剧本的看法(清理了一下并用一个匹配器包裹起来),希望它对某些人来说仍然是真实的:

我把它放到spec/support/query_counter.rb

module ActiveRecord
  class QueryCounter

    attr_reader :query_count

    def initialize
      @query_count = 0
    end

    def to_proc
      lambda(&method(:callback))
    end

    def callback(name, start, finish, message_id, values)
      @query_count += 1 unless %w(CACHE SCHEMA).include?(values[:name])
    end

  end
end
Run Code Online (Sandbox Code Playgroud)

这是spec/support/matchers/beyond_query_limit.rb

RSpec::Matchers.define :exceed_query_limit do |expected|

  match do |block|
    query_count(&block) > expected
  end

  failure_message_for_should_not do |actual|
    "Expected to run maximum #{expected} queries, got #{@counter.query_count}"
  end

  def query_count(&block)
    @counter = ActiveRecord::QueryCounter.new
    ActiveSupport::Notifications.subscribed(@counter.to_proc, 'sql.active_record', &block)
    @counter.query_count
  end

end
Run Code Online (Sandbox Code Playgroud)

用法:

expect { MyModel.do_the_queries }.to_not exceed_query_limit(2)
Run Code Online (Sandbox Code Playgroud)

  • [this gist](https://gist.github.com/rsutphin/af06c9e3dadf658d2293) 中 RSpec 3 的小更新。 (4认同)

Jai*_*ham 12

这是Ryan和Yuriy解决方案的另一个表述,它只是您添加到您的功能test_helper.rb:

def count_queries &block
  count = 0

  counter_f = ->(name, started, finished, unique_id, payload) {
    unless payload[:name].in? %w[ CACHE SCHEMA ]
      count += 1
    end
  }

  ActiveSupport::Notifications.subscribed(counter_f, "sql.active_record", &block)

  count
end
Run Code Online (Sandbox Code Playgroud)

用法只是:

c = count_queries do
  SomeModel.first
end
Run Code Online (Sandbox Code Playgroud)


gro*_*ser 6

  • 有用的错误信息
  • 执行后删除订户

(基于Jaime Cham的回答)

class ActiveSupport::TestCase
  def sql_queries(&block)
    queries = []
    counter = ->(*, payload) {
      queries << payload.fetch(:sql) unless ["CACHE", "SCHEMA"].include?(payload.fetch(:name))
    }

    ActiveSupport::Notifications.subscribed(counter, "sql.active_record", &block)

    queries
  end

  def assert_sql_queries(expected, &block)
    queries = sql_queries(&block)
    queries.count.must_equal(
      expected,
      "Expected #{expected} queries, but found #{queries.count}:\n#{queries.join("\n")}"
    )
  end
end
Run Code Online (Sandbox Code Playgroud)